Skip to content

Commit

Permalink
fix: select support for plain options (#747)
Browse files Browse the repository at this point in the history
  • Loading branch information
czosel committed Feb 10, 2022
1 parent 2c6b957 commit a58e26d
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 57 deletions.
50 changes: 7 additions & 43 deletions addon/components/validated-input/types/select.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -20,53 +20,17 @@
{{#each this.optionGroups as |optionGroup|}}
<optgroup label={{optionGroup.groupName}}>
{{#each optionGroup.options as |opt|}}
{{#let
(if
(or @optionValuePath @optionTargetPath)
(get opt (or @optionValuePath @optionTargetPath))
opt
)
as |optionValue|
}}
<option
selected={{eq optionValue @value}}
value={{optionValue}}
>{{#if @optionLabelPath}}
{{get opt @optionLabelPath}}
{{else if @optionValuePath}}
{{get opt @optionValuePath}}
{{else if @optionTargetPath}}
{{get opt @optionTargetPath}}
{{else}}
{{opt}}
{{/if}}
</option>
{{/let}}
<option selected={{eq opt.id @value}} value={{opt.id}}>
{{opt.label}}
</option>
{{/each}}
</optgroup>
{{/each}}
{{else}}
{{#each @options as |opt|}}
{{#let
(if
(or @optionValuePath @optionTargetPath)
(get opt (or @optionValuePath @optionTargetPath))
opt
)
as |optionValue|
}}
<option selected={{eq optionValue @value}} value={{optionValue}}>{{#if
@optionLabelPath
}}
{{get opt @optionLabelPath}}
{{else if @optionValuePath}}
{{get opt @optionValuePath}}
{{else if @optionTargetPath}}
{{get opt @optionTargetPath}}
{{else}}
{{opt}}
{{/if}}</option>
{{/let}}
{{#each this.normalizedOptions as |opt|}}
<option selected={{eq opt.id @value}} value={{opt.id}}>
{{opt.label}}
</option>
{{/each}}
{{/if}}
</select>
40 changes: 33 additions & 7 deletions addon/components/validated-input/types/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ export default class SelectComponent extends Component {
return this.hasPreGroupedOptions || this.args.groupLabelPath;
}

get normalizedOptions() {
// normalize options to common data structure, only for rendering
return this.args.options.map((opt) => this.normalize(opt));
}

normalize(option) {
if (typeof option !== "object") {
return { id: option, label: option };
}
const valuePath = this.args.optionValuePath ?? this.args.optionTargetPath;
const labelPath = this.args.optionLabelPath;
return {
id: valuePath ? option[valuePath] : option.id,
label: labelPath ? option[labelPath] : option.label,
};
}

get optionGroups() {
const groupLabelPath = this.args.groupLabelPath;
if (!groupLabelPath) {
Expand All @@ -60,7 +77,7 @@ export default class SelectComponent extends Component {
groups.pushObject(group);
}

group.options.pushObject(item);
group.options.pushObject(this.normalize(item));
} else {
groups.pushObject(item);
}
Expand All @@ -72,6 +89,17 @@ export default class SelectComponent extends Component {
findOption(target) {
const targetPath = this.args.optionTargetPath;
const valuePath = this.args.optionValuePath || targetPath;

const getValue = (item) => {
if (valuePath) {
return String(item[valuePath]);
}
if (typeof item === "object") {
return String(item.id);
}
return String(item);
};

let options = this.args.options;

//flatten pre grouped options
Expand All @@ -85,19 +113,17 @@ export default class SelectComponent extends Component {
.call(target.options, (option) => option.selected)
.map((option) => option.value);

const foundOptions = options.filter((item) =>
selectedValues.includes(`${valuePath ? item[valuePath] : item}`)
);
const foundOptions = options.filter((item) => {
return selectedValues.includes(getValue(item));
});
if (targetPath) {
return foundOptions.map((item) => item[targetPath]);
}
return foundOptions;
}

//single select
const foundOption = options.find(
(item) => `${valuePath ? item[valuePath] : item.value}` === target.value
);
const foundOption = options.find((item) => getValue(item) === target.value);
if (targetPath) {
return foundOption[targetPath];
}
Expand Down
58 changes: 51 additions & 7 deletions tests/integration/components/validated-input/types/select-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ module(

test("it renders", async function (assert) {
this.set("options", [
{ key: 1, label: 1 },
{ key: 2, label: 2 },
{ id: 1, label: 1 },
{ id: 2, label: 2 },
]);

await render(
Expand All @@ -23,6 +23,25 @@ module(
assert.dom("option:first-child").hasProperty("selected", true);
});

test("it supports plain (non-object) options", async function (assert) {
assert.expect(6);
this.set("options", ["foo", "bar"]);

this.set("update", (value) => {
assert.strictEqual(value, "bar");
});
await render(
hbs`<ValidatedInput::Types::Select @options={{this.options}} @update={{this.update}} />`
);

assert.dom("select").exists();
assert.dom("option").exists({ count: 2 });
assert.dom("option:first-child").hasProperty("selected", true);
await select("select", "bar");
assert.dom("option:first-child").hasProperty("selected", false);
assert.dom("option:last-child").hasProperty("selected", true);
});

test("it works with solitary optionTargetPath property", async function (assert) {
assert.expect(2);
this.set("options", [
Expand All @@ -38,7 +57,7 @@ module(
hbs`<ValidatedInput::Types::Select @options={{this.options}} @update={{this.update}} @optionTargetPath="key" />`
);

assert.dom("option:first-child").hasText("111");
assert.dom("option:first-child").hasText("firstOption");
await select("select", "222");
});

Expand Down Expand Up @@ -130,18 +149,43 @@ module(
});

test("multiselect is working", async function (assert) {
assert.expect(3);
this.set("options", ["1", "2"]);
assert.expect(4);
this.set("options", [
{ id: 1, label: "label 1" },
{ id: 2, label: "label 2" },
{ id: 3, label: "label 3" },
]);
this.set("update", (values) => {
assert.deepEqual(values, [
{ id: 1, label: "label 1" },
{ id: 3, label: "label 3" },
]);
});

await render(
hbs`<ValidatedInput::Types::Select @options={{this.options}} @multiple={{true}} @update={{this.update}} />`
);

await select("select", ["1", "3"]);
assert.dom("option:first-child").hasProperty("selected", true);
assert.dom("option:nth-child(2)").hasProperty("selected", false);
assert.dom("option:last-child").hasProperty("selected", true);
});

test("multiselect is working with plain options", async function (assert) {
assert.expect(4);
this.set("options", ["1", "2", "3"]);
this.set("update", (values) => {
assert.deepEqual(values, this.options);
assert.deepEqual(values, ["1", "3"]);
});

await render(
hbs`<ValidatedInput::Types::Select @options={{this.options}} @multiple={{true}} @update={{this.update}} />`
);

await select("select", this.options);
await select("select", ["1", "3"]);
assert.dom("option:first-child").hasProperty("selected", true);
assert.dom("option:nth-child(2)").hasProperty("selected", false);
assert.dom("option:last-child").hasProperty("selected", true);
});

Expand Down

0 comments on commit a58e26d

Please sign in to comment.