Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ AdvancedSelect is a small Rails engine for rendering an advanced select input wi
- [Dependent Fields](#dependent-fields)
- [Custom Option Content](#custom-option-content)
- [Option Contract](#option-contract)
- [Events](#events)
- [API Reference](#api-reference)
- [Local Development](#local-development)
- [i18n](#i18n)
Expand Down Expand Up @@ -745,6 +746,51 @@ Grouped options use this shape:
]
```

### Events

Every value change dispatches two events:

| Event | Target | Purpose |
| --- | --- | --- |
| `change` | the first hidden input | native form behaviour and [dependent fields](#dependent-fields) |
| `advanced-select:change` | the root element | application listeners that need the whole selection |

Both bubble. `advanced-select:change` carries a `detail` payload:

```js
{
name: "record[item_ids][]",
value: ["3", "7"],
options: [
{ id: "3", value: "3", label: "Item A", display_label: "Item A" },
{ id: "7", value: "7", label: "Item B", display_label: "Item B" }
]
}
```

`value` is the submit value — the option's `value` when it defines one, otherwise its `id`. It is an
array for multiple selects, a string for single selects, and empty when nothing is selected. A
multiple select lists the most recently chosen option first, matching the order of its hidden
inputs.

Bind it like any other event:

```erb
data-action="advanced-select:change->my-controller#refresh"
```

Prefer it over `change` on multiple selects. `change` is dispatched from the first hidden input, so
it does not fire at all once the last value is cleared from a field rendered with
`include_hidden: false` — at that point the field has no inputs left.

The current value is also readable from the controller:

```js
const select = application.getControllerForElementAndIdentifier(element, "advanced-select")

select.currentValue // => ["3", "7"]
```

### API Reference

`advanced_select_tag`:
Expand Down
15 changes: 15 additions & 0 deletions app/javascript/advanced_select/advanced_select_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,21 @@ export default class extends Controller {
if (input) {
input.dispatchEvent(new Event("change", { bubbles: true }))
}

this.element.dispatchEvent(new CustomEvent("advanced-select:change", {
bubbles: true,
detail: {
name: this.nameValue,
value: this.currentValue,
options: [...this.selectedValue]
}
}))
}

get currentValue() {
const values = this.selectedValue.map((option) => option.value || option.id)

return this.multipleValue ? values : (values[0] || "")
}

renderOptionsState() {
Expand Down
2 changes: 1 addition & 1 deletion lib/advanced_select/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module AdvancedSelect
VERSION = "0.1.8"
VERSION = "0.1.9"
end
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,21 @@ export default class extends Controller {
if (input) {
input.dispatchEvent(new Event("change", { bubbles: true }))
}

this.element.dispatchEvent(new CustomEvent("advanced-select:change", {
bubbles: true,
detail: {
name: this.nameValue,
value: this.currentValue,
options: [...this.selectedValue]
}
}))
}

get currentValue() {
const values = this.selectedValue.map((option) => option.value || option.id)

return this.multipleValue ? values : (values[0] || "")
}

renderOptionsState() {
Expand Down
68 changes: 68 additions & 0 deletions test/system/advanced_select_interaction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,76 @@ class AdvancedSelectInteractionTest < ApplicationSystemTestCase
end
end

test "broadcasts advanced-select:change with the selected value" do
visit root_path
record_advanced_select_events

find("#example_item_id_trigger").click
find("#example_item_id_options button", text: "Local One").click

assert_selector "#example_item_id_summary", text: "Local One"

event = advanced_select_events.last

assert_equal 1, advanced_select_events.size
assert_equal "example[item_id]", event["name"]
assert_equal "local-1", event["value"]
assert_equal ["Local One"], event["options"].map { |option| option["label"] }
end

test "broadcasts the submit value rather than the option id" do
visit root_path
record_advanced_select_events

find("#example_submit_id_trigger").click
find("#example_submit_id_options button", text: "Submit Item").click

assert_selector "#example_submit_id_summary", text: "Submit Item"
assert_equal "submit-7", advanced_select_events.last["value"]
end

test "broadcasts an array of values for multiple selects" do
visit root_path
record_advanced_select_events

find("#example_multiple_ids_trigger").click
find("#example_multiple_ids_options button", text: "Multi One").click
find("#example_multiple_ids_options button", text: "Multi Two").click

assert_selector "#example_multiple_ids_summary", text: "Multi Two"
assert_equal [["multi-1"], %w[multi-2 multi-1]], advanced_select_events.map { |event| event["value"] }
end

test "broadcasts when the last value is cleared without a blank hidden field" do
visit root_path

find("#example_multiple_ids_without_blank_trigger").click
find("#example_multiple_ids_without_blank_options button", text: "Multi One").click

assert_selector "input[name='example[multiple_ids_without_blank][]'][value='multi-1']", visible: false

record_advanced_select_events
find("#example_multiple_ids_without_blank_clear").click

assert_no_selector "input[name='example[multiple_ids_without_blank][]']", visible: false
assert_equal [[]], advanced_select_events.map { |event| event["value"] }
end

private

def record_advanced_select_events
page.execute_script(<<~JS)
window.__advancedSelectEvents = []
document.addEventListener("advanced-select:change", (event) => {
window.__advancedSelectEvents.push(event.detail)
})
JS
end

def advanced_select_events
page.evaluate_script("window.__advancedSelectEvents")
end

def assert_selected_option_check(select_id, text)
option = find("##{select_id}_options button[aria-selected='true']", text: text)

Expand Down
Loading