Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: select support for plain options #747

Merged
merged 1 commit into from
Feb 10, 2022
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
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");
czosel marked this conversation as resolved.
Show resolved Hide resolved
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