From 0b3cf1a355957194cb2a1cde8f5e4cd21d9111ab Mon Sep 17 00:00:00 2001 From: Jay Kid Date: Wed, 28 Jun 2017 11:11:02 +0200 Subject: [PATCH 1/4] feat: implement custom properties for items and choices + Added unit tests for both choice and item reducers + Had to add some visually unpleasant undefineds, sorry :P --- assets/scripts/src/actions/index.js | 6 +- assets/scripts/src/choices.js | 58 ++++++++++---- assets/scripts/src/reducers/choices.js | 1 + assets/scripts/src/reducers/items.js | 1 + tests/spec/choices_spec.js | 101 ++++++++++++++++++++++++- 5 files changed, 145 insertions(+), 22 deletions(-) diff --git a/assets/scripts/src/actions/index.js b/assets/scripts/src/actions/index.js index 231f874..d12d8ae 100644 --- a/assets/scripts/src/actions/index.js +++ b/assets/scripts/src/actions/index.js @@ -1,4 +1,4 @@ -export const addItem = (value, label, id, choiceId, groupId) => { +export const addItem = (value, label, id, choiceId, groupId, customProperties) => { return { type: 'ADD_ITEM', value, @@ -6,6 +6,7 @@ export const addItem = (value, label, id, choiceId, groupId) => { id, choiceId, groupId, + customProperties }; }; @@ -25,7 +26,7 @@ export const highlightItem = (id, highlighted) => { }; }; -export const addChoice = (value, label, id, groupId, disabled, elementId) => { +export const addChoice = (value, label, id, groupId, disabled, elementId, customProperties) => { return { type: 'ADD_CHOICE', value, @@ -34,6 +35,7 @@ export const addChoice = (value, label, id, groupId, disabled, elementId) => { groupId, disabled, elementId: elementId, + customProperties }; }; diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js index ad20b01..7fae783 100644 --- a/assets/scripts/src/choices.js +++ b/assets/scripts/src/choices.js @@ -812,13 +812,16 @@ class Choices { item.label, true, false, - -1 + -1, + item.customProperties ); } else { this._addItem( item.value, item.label, - item.id + item.id, + undefined, + item.customProperties ); } } else if (itemType === 'String') { @@ -868,7 +871,7 @@ class Choices { if (foundChoice) { if (!foundChoice.selected) { - this._addItem(foundChoice.value, foundChoice.label, foundChoice.id, foundChoice.groupId); + this._addItem(foundChoice.value, foundChoice.label, foundChoice.id, foundChoice.groupId, foundChoice.customProperties); } else if (!this.config.silent) { console.warn('Attempting to select choice already selected'); } @@ -915,7 +918,9 @@ class Choices { result[value], result[label], result.selected, - result.disabled + result.disabled, + undefined, + result['customProperties'] ); } }); @@ -1134,7 +1139,8 @@ class Choices { choice.value, choice.label, choice.id, - choice.groupId + choice.groupId, + choice.customProperties ); this._triggerChange(choice.value); } @@ -1305,7 +1311,9 @@ class Choices { result[value], result[label], result.selected, - result.disabled + result.disabled, + undefined, + result['customProperties'] ); } }); @@ -2058,10 +2066,13 @@ class Choices { * Add item to store with correct value * @param {String} value Value to add to store * @param {String} label Label to add to store + * @param {Number} choiceId ID of the associated choice that was selected + * @param {Number} groupId ID of group choice is within. Negative number indicates no group + * @param {Object} customProperties Object containing user defined properties * @return {Object} Class instance * @public */ - _addItem(value, label, choiceId = -1, groupId = -1) { + _addItem(value, label, choiceId = -1, groupId = -1, customProperties = null) { let passedValue = isType('String', value) ? value.trim() : value; const items = this.store.getItems(); const passedLabel = label || passedValue; @@ -2083,7 +2094,7 @@ class Choices { passedValue += this.config.appendValue.toString(); } - this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId)); + this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId, customProperties)); if (this.passedElement.type === 'select-one') { this.removeActiveItems(id); @@ -2152,15 +2163,16 @@ class Choices { /** * Add choice to dropdown - * @param {Boolean} isSelected Whether choice is selected - * @param {Boolean} isDisabled Whether choice is disabled * @param {String} value Value of choice * @param {String} Label Label of choice + * @param {Boolean} isSelected Whether choice is selected + * @param {Boolean} isDisabled Whether choice is disabled * @param {Number} groupId ID of group choice is within. Negative number indicates no group + * @param {Object} customProperties Object containing user defined properties * @return * @private */ - _addChoice(value, label, isSelected = false, isDisabled = false, groupId = -1) { + _addChoice(value, label, isSelected = false, isDisabled = false, groupId = -1, customProperties = null) { if (typeof value === 'undefined' || value === null) { return; } @@ -2177,11 +2189,19 @@ class Choices { choiceId, groupId, isDisabled, - choiceElementId + choiceElementId, + customProperties )); if (isSelected) { - this._addItem(value, choiceLabel, choiceId); + this._addItem( + value, + choiceLabel, + choiceId, + undefined, + undefined, + customProperties + ); } } @@ -2618,7 +2638,9 @@ class Choices { choice.value, choice.label, choice.selected, - choice.disabled + choice.disabled, + undefined, + choice.customProperties ); } else { // Otherwise pre-select the first choice in the array @@ -2626,7 +2648,9 @@ class Choices { choice.value, choice.label, true, - false + false, + undefined, + choice.customProperties ); } } else { @@ -2634,7 +2658,9 @@ class Choices { choice.value, choice.label, choice.selected, - choice.disabled + choice.disabled, + undefined, + choice.customProperties ); } }); diff --git a/assets/scripts/src/reducers/choices.js b/assets/scripts/src/reducers/choices.js index cf6f67c..d6dd003 100644 --- a/assets/scripts/src/reducers/choices.js +++ b/assets/scripts/src/reducers/choices.js @@ -16,6 +16,7 @@ const choices = (state = [], action) => { selected: false, active: true, score: 9999, + customProperties: action.customProperties }]; } diff --git a/assets/scripts/src/reducers/items.js b/assets/scripts/src/reducers/items.js index 9ff419c..61b6a4d 100644 --- a/assets/scripts/src/reducers/items.js +++ b/assets/scripts/src/reducers/items.js @@ -10,6 +10,7 @@ const items = (state = [], action) => { label: action.label, active: true, highlighted: false, + customProperties: action.customProperties }]; return newState.map((item) => { diff --git a/tests/spec/choices_spec.js b/tests/spec/choices_spec.js index 6756124..1959b46 100644 --- a/tests/spec/choices_spec.js +++ b/tests/spec/choices_spec.js @@ -1,6 +1,9 @@ import 'whatwg-fetch'; import 'es6-promise'; import Choices from '../../assets/scripts/src/choices.js'; +import itemReducer from '../../assets/scripts/src/reducers/items.js'; +import choiceReducer from '../../assets/scripts/src/reducers/choices.js'; +import { addItem as addItemAction, addChoice as addChoiceAction } from '../../assets/scripts/src/actions/index.js'; if (typeof Object.assign != 'function') { Object.assign = function (target, varArgs) { // .length of function is 2 @@ -28,10 +31,6 @@ if (typeof Object.assign != 'function') { describe('Choices', () => { - afterEach(function() { - this.choices.destroy(); - }); - describe('should initialize Choices', () => { beforeEach(function() { @@ -43,6 +42,10 @@ describe('Choices', () => { this.choices = new Choices(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should be defined', function() { expect(this.choices).toBeDefined(); }); @@ -164,6 +167,10 @@ describe('Choices', () => { document.body.appendChild(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should accept a user inputted value', function() { this.choices = new Choices(this.input); @@ -272,6 +279,10 @@ describe('Choices', () => { document.body.appendChild(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should open the choice list on focussing', function() { this.choices = new Choices(this.input); this.choices.input.focus(); @@ -567,6 +578,10 @@ describe('Choices', () => { }); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should add any pre-defined values', function() { expect(this.choices.currentState.items.length).toBeGreaterThan(1); }); @@ -604,6 +619,10 @@ describe('Choices', () => { this.choices = new Choices(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should handle highlightItem()', function() { const items = this.choices.currentState.items; const randomItem = items[Math.floor(Math.random() * items.length)]; @@ -839,6 +858,10 @@ describe('Choices', () => { this.choices = new Choices(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should handle disable()', function() { this.choices.disable(); @@ -863,6 +886,10 @@ describe('Choices', () => { this.choices = new Choices(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should handle clearInput()', function() { this.choices.clearInput(); expect(this.choices.input.value).toBe(''); @@ -899,6 +926,10 @@ describe('Choices', () => { document.body.appendChild(this.input); }); + afterEach(function() { + this.choices.destroy(); + }); + it('should flip the dropdown', function() { this.choices = new Choices(this.input, { position: 'top' @@ -919,4 +950,66 @@ describe('Choices', () => { expect(container.classList.contains(this.choices.config.classNames.flippedState)).toBe(false); }); }); + + describe('should allow custom properties provided by the user on items or choices', function() { + it('should allow the user to supply custom properties for an item', function() { + var randomItem = { + id: 8999, + choiceId: 9000, + groupId: 9001, + value: 'value', + label: 'label', + customProperties: { + foo: 'bar' + } + } + + var expectedState = [{ + id: randomItem.id, + choiceId: randomItem.choiceId, + groupId: randomItem.groupId, + value: randomItem.value, + label: randomItem.label, + active: true, + highlighted: false, + customProperties: randomItem.customProperties + }]; + + var action = addItemAction(randomItem.value, randomItem.label, randomItem.id, randomItem.choiceId, randomItem.groupId, randomItem.customProperties); + + expect(itemReducer([], action)).toEqual(expectedState); + }); + + it('should allow the user to supply custom properties for a choice', function() { + var randomChoice = { + id: 123, + elementId: 321, + groupId: 213, + value: 'value', + label: 'label', + disabled: false, + customProperties: { + foo: 'bar' + } + } + + var expectedState = [{ + id: randomChoice.id, + elementId: randomChoice.elementId, + groupId: randomChoice.groupId, + value: randomChoice.value, + label: randomChoice.label, + disabled: randomChoice.disabled, + selected: false, + active: true, + score: 9999, + customProperties: randomChoice.customProperties + }]; + + var action = addChoiceAction(randomChoice.value, randomChoice.label, randomChoice.id, randomChoice.groupId, randomChoice.disabled, randomChoice.elementId, randomChoice.customProperties); + + expect(choiceReducer([], action)).toEqual(expectedState); + }); + }); + }); From 600f59a44002082e44d1f6274360c2a46a872701 Mon Sep 17 00:00:00 2001 From: Jay Kid Date: Wed, 28 Jun 2017 11:12:45 +0200 Subject: [PATCH 2/4] fix: add pause between "Remove item" and the value of the item by adding a colon Helps understanding when using VoiceOver --- assets/scripts/src/choices.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js index 7fae783..280d552 100644 --- a/assets/scripts/src/choices.js +++ b/assets/scripts/src/choices.js @@ -2369,7 +2369,7 @@ class Choices { type="button" class="${globalClasses.button}" data-button - aria-label="Remove item '${data.value}'" + aria-label="Remove item: '${data.value}'" > Remove item From 271d2a20b3e7e9c2e64c50fb1107dc0a6ec0b7db Mon Sep 17 00:00:00 2001 From: Jay Kid Date: Wed, 28 Jun 2017 11:58:41 +0200 Subject: [PATCH 3/4] test: added a more "integration"-like test for the custom properties feature --- tests/spec/choices_spec.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/spec/choices_spec.js b/tests/spec/choices_spec.js index 1959b46..e1b0169 100644 --- a/tests/spec/choices_spec.js +++ b/tests/spec/choices_spec.js @@ -1012,4 +1012,39 @@ describe('Choices', () => { }); }); + describe('should allow custom properties provided by the user on items or choices', function() { + beforeEach(function() { + this.input = document.createElement('select'); + this.input.className = 'js-choices'; + this.input.setAttribute('multiple', ''); + + document.body.appendChild(this.input); + }); + + afterEach(function() { + this.choices.destroy(); + }); + + it('should allow the user to supply custom properties for a choice that will be inherited by the item when the user selects the choice', function() { + + var expectedCustomProperties = { + isBestOptionEver: true + }; + + this.choices = new Choices(this.input); + this.choices.setChoices([{ + value: '42', + label: 'My awesome choice', + selected: false, + disabled: false, + customProperties: expectedCustomProperties + }], 'value', 'label', true); + + this.choices.setValueByChoice('42'); + var selectedItems = this.choices.getValue(); + + expect(selectedItems.length).toBe(1); + expect(selectedItems[0].customProperties).toBe(expectedCustomProperties); + }); + }); }); From 4370616519ff58b8c6d31cc5a48a21af15fdc799 Mon Sep 17 00:00:00 2001 From: Josh Johnson Date: Thu, 29 Jun 2017 08:40:56 +0100 Subject: [PATCH 4/4] Documentation + housekeeping + ensure custom props are passed to preset items --- .gitignore | 2 +- README.md | 203 +++++++++++++++++--------------- assets/scripts/src/choices.js | 16 ++- index.html | 18 ++- package.json | 1 + tests/spec/choices_spec.js | 216 ++++++++++++++++------------------ 6 files changed, 241 insertions(+), 215 deletions(-) diff --git a/.gitignore b/.gitignore index df34fbd..fbd3d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ node_modules npm-debug.log .DS_Store .vscode -.package-lock.json +package-lock.json # Test tests/reports diff --git a/README.md b/README.md index a5d4b91..1b1ad0e 100644 --- a/README.md +++ b/README.md @@ -92,29 +92,29 @@ Or include Choices directly: return `Only ${maxItemCount} values can be added.`; }, classNames: { - containerOuter: 'choices', - containerInner: 'choices__inner', - input: 'choices__input', - inputCloned: 'choices__input--cloned', - list: 'choices__list', - listItems: 'choices__list--multiple', - listSingle: 'choices__list--single', - listDropdown: 'choices__list--dropdown', - item: 'choices__item', - itemSelectable: 'choices__item--selectable', - itemDisabled: 'choices__item--disabled', - itemChoice: 'choices__item--choice', - group: 'choices__group', - groupHeading : 'choices__heading', - button: 'choices__button', - activeState: 'is-active', - focusState: 'is-focused', - openState: 'is-open', - disabledState: 'is-disabled', - highlightedState: 'is-highlighted', - hiddenState: 'is-hidden', - flippedState: 'is-flipped', - loadingState: 'is-loading', + containerOuter: 'choices', + containerInner: 'choices__inner', + input: 'choices__input', + inputCloned: 'choices__input--cloned', + list: 'choices__list', + listItems: 'choices__list--multiple', + listSingle: 'choices__list--single', + listDropdown: 'choices__list--dropdown', + item: 'choices__item', + itemSelectable: 'choices__item--selectable', + itemDisabled: 'choices__item--disabled', + itemChoice: 'choices__item--choice', + group: 'choices__group', + groupHeading : 'choices__heading', + button: 'choices__button', + activeState: 'is-active', + focusState: 'is-focused', + openState: 'is-open', + disabledState: 'is-disabled', + highlightedState: 'is-highlighted', + hiddenState: 'is-hidden', + flippedState: 'is-flipped', + loadingState: 'is-loading', }, // Choices uses the great Fuse library for searching. You // can find more options here: https://github.com/krisk/Fuse#options @@ -165,7 +165,10 @@ Pass an array of objects: { value: 'Value 2', label: 'Label 2', - id: 2 + id: 2, + customProperties: { + random: 'I am a custom property' + } }] ``` @@ -190,6 +193,10 @@ Pass an array of objects: label: 'Option 2', selected: false, disabled: true, + customProperties: { + description: 'Custom description about Option 2', + random: 'Another random custom property' + }, }] ``` @@ -269,7 +276,7 @@ Pass an array of objects: **Input types affected:**`select-one`, `select-multiple` -**Usage:** Specify which fields should be used when a user is searching. +**Usage:** Specify which fields should be used when a user is searching. If you have added custom properties to your choices, you can add these values thus: `['label', 'value', 'customProperties.example']`. ### searchFloor **Type:** `Number` **Default:** `1` @@ -325,9 +332,9 @@ Pass an array of objects: ```js // Sorting via length of label from largest to smallest const example = new Choices(element, { - sortFilter: function(a, b) { - return b.label.length - a.label.length; - }, + sortFilter: function(a, b) { + return b.label.length - a.label.length; + }, }; ``` @@ -406,29 +413,29 @@ const example = new Choices(element, { ``` classNames: { - containerOuter: 'choices', - containerInner: 'choices__inner', - input: 'choices__input', - inputCloned: 'choices__input--cloned', - list: 'choices__list', - listItems: 'choices__list--multiple', - listSingle: 'choices__list--single', - listDropdown: 'choices__list--dropdown', - item: 'choices__item', - itemSelectable: 'choices__item--selectable', - itemDisabled: 'choices__item--disabled', - itemOption: 'choices__item--choice', - group: 'choices__group', - groupHeading : 'choices__heading', - button: 'choices__button', - activeState: 'is-active', - focusState: 'is-focused', - openState: 'is-open', - disabledState: 'is-disabled', - highlightedState: 'is-highlighted', - hiddenState: 'is-hidden', - flippedState: 'is-flipped', - selectedState: 'is-highlighted', + containerOuter: 'choices', + containerInner: 'choices__inner', + input: 'choices__input', + inputCloned: 'choices__input--cloned', + list: 'choices__list', + listItems: 'choices__list--multiple', + listSingle: 'choices__list--single', + listDropdown: 'choices__list--dropdown', + item: 'choices__item', + itemSelectable: 'choices__item--selectable', + itemDisabled: 'choices__item--disabled', + itemOption: 'choices__item--choice', + group: 'choices__group', + groupHeading : 'choices__heading', + button: 'choices__button', + activeState: 'is-active', + focusState: 'is-focused', + openState: 'is-open', + disabledState: 'is-disabled', + highlightedState: 'is-highlighted', + hiddenState: 'is-hidden', + flippedState: 'is-flipped', + selectedState: 'is-highlighted', } ``` @@ -465,14 +472,14 @@ const example = new Choices(element, {
${data.label}
- `); + `); }, choice: (data) => { return template(`
0 ? 'role="treeitem"' : 'role="option"'}> - ${data.label} -
- `); + ${data.label} + + `); }, }; } @@ -506,7 +513,6 @@ example.passedElement.addEventListener('addItem', function(event) { console.log(event.detail.label); console.log(event.detail.groupValue); }, false); - ``` ### addItem @@ -574,12 +580,14 @@ Methods can be called either directly or by chaining: const choices = new Choices(element, { addItems: false, removeItems: false, -}).setValue(['Set value 1', 'Set value 2']).disable(); + }) + .setValue(['Set value 1', 'Set value 2']) + .disable(); // Calling a method directly const choices = new Choices(element, { - addItems: false, - removeItems: false, + addItems: false, + removeItems: false, }); choices.setValue(['Set value 1', 'Set value 2']) @@ -648,7 +656,7 @@ choices.disable(); ### setChoices(choices, value, label, replaceChoices); **Input types affected:** `select-one`, `select-multiple` -**Usage:** Set choices of select input via an array of objects, a value name and a label name. This behaves the same as passing items via the `choices` option but can be called after initialising Choices. This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. +**Usage:** Set choices of select input via an array of objects, a value name and a label name. This behaves the same as passing items via the `choices` option but can be called after initialising Choices. This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). **Example 1:** @@ -656,9 +664,9 @@ choices.disable(); const example = new Choices(element); example.setChoices([ - {value: 'One', label: 'Label One', disabled: true}, - {value: 'Two', label: 'Label Two', selected: true}, - {value: 'Three', label: 'Label Three'}, + {value: 'One', label: 'Label One', disabled: true}, + {value: 'Two', label: 'Label Two', selected: true}, + {value: 'Three', label: 'Label Three'}, ], 'value', 'label', false); ``` @@ -668,24 +676,27 @@ example.setChoices([ const example = new Choices(element); example.setChoices([{ - label: 'Group one', - id: 1, - disabled: false, - choices: [ - {value: 'Child One', label: 'Child One', selected: true}, - {value: 'Child Two', label: 'Child Two', disabled: true}, - {value: 'Child Three', label: 'Child Three'}, - ] + label: 'Group one', + id: 1, + disabled: false, + choices: [ + {value: 'Child One', label: 'Child One', selected: true}, + {value: 'Child Two', label: 'Child Two', disabled: true}, + {value: 'Child Three', label: 'Child Three'}, + ] }, { - label: 'Group two', - id: 2, - disabled: false, - choices: [ - {value: 'Child Four', label: 'Child Four', disabled: true}, - {value: 'Child Five', label: 'Child Five'}, - {value: 'Child Six', label: 'Child Six'}, - ] + label: 'Group two', + id: 2, + disabled: false, + choices: [ + {value: 'Child Four', label: 'Child Four', disabled: true}, + {value: 'Child Five', label: 'Child Five'}, + {value: 'Child Six', label: 'Child Six', customProperties: { + description: 'Custom description about child six', + random: 'Another random custom property' + }}, + ] }], 'value', 'label', false); ``` @@ -714,9 +725,9 @@ const example = new Choices(element); // via an array of objects example.setValue([ - {value: 'One', label: 'Label One'}, - {value: 'Two', label: 'Label Two'}, - {value: 'Three', label: 'Label Three'}, + {value: 'One', label: 'Label One'}, + {value: 'Two', label: 'Label Two'}, + {value: 'Three', label: 'Label Three'}, ]); // or via an array of strings @@ -732,11 +743,11 @@ example.setValue(['Four','Five','Six']); ```js const example = new Choices(element, { - choices: [ - {value: 'One', label: 'Label One'}, - {value: 'Two', label: 'Label Two', disabled: true}, - {value: 'Three', label: 'Label Three'}, - ], + choices: [ + {value: 'One', label: 'Label One'}, + {value: 'Two', label: 'Label Two', disabled: true}, + {value: 'Three', label: 'Label Three'}, + ], }); example.setValueByChoice('Two'); // Choice with value of 'Two' has now been selected. @@ -776,15 +787,15 @@ example.setValueByChoice('Two'); // Choice with value of 'Two' has now been sele var example = new Choices(element); example.ajax(function(callback) { - fetch(url) - .then(function(response) { - response.json().then(function(data) { - callback(data, 'value', 'label'); - }); - }) - .catch(function(error) { - console.log(error); - }); + fetch(url) + .then(function(response) { + response.json().then(function(data) { + callback(data, 'value', 'label'); + }); + }) + .catch(function(error) { + console.log(error); + }); }); ``` diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js index 280d552..399f30b 100644 --- a/assets/scripts/src/choices.js +++ b/assets/scripts/src/choices.js @@ -871,7 +871,13 @@ class Choices { if (foundChoice) { if (!foundChoice.selected) { - this._addItem(foundChoice.value, foundChoice.label, foundChoice.id, foundChoice.groupId, foundChoice.customProperties); + this._addItem( + foundChoice.value, + foundChoice.label, + foundChoice.id, + foundChoice.groupId, + foundChoice.customProperties + ); } else if (!this.config.silent) { console.warn('Attempting to select choice already selected'); } @@ -2673,7 +2679,13 @@ class Choices { if (!item.value) { return; } - this._addItem(item.value, item.label, item.id); + this._addItem( + item.value, + item.label, + item.id, + undefined, + item.customProperties + ); } else if (itemType === 'String') { this._addItem(item); } diff --git a/index.html b/index.html index 330fa51..806713c 100644 --- a/index.html +++ b/index.html @@ -63,7 +63,7 @@ - + @@ -208,7 +208,8 @@ - + +

Try searching for 'fantastic'

@@ -306,7 +307,13 @@ }).removeActiveItems(); var textPresetVal = new Choices('#choices-text-preset-values', { - items: ['josh@joshuajohnson.co.uk', { value: 'joe@bloggs.co.uk', label: 'Joe Bloggs' } ], + items: ['Josh Johnson', { + value: 'joe@bloggs.co.uk', + label: 'Joe Bloggs', + customProperties: { + description: 'Joe Blogg is such a generic name' + } + }], }); var multipleDefault = new Choices(document.getElementById('choices-multiple-groups')); @@ -433,10 +440,13 @@ }], 'value', 'label'); var singleSelectedOpt = new Choices('#choices-single-selected-option', { + searchFields: ['label', 'value', 'customProperties.description'], choices: [ {value: 'One', label: 'Label One', selected: true}, {value: 'Two', label: 'Label Two', disabled: true}, - {value: 'Three', label: 'Label Three'}, + {value: 'Three', label: 'Label Three', customProperties: { + description: 'This option is fantastic' + }}, ], }).setValueByChoice('Two'); diff --git a/package.json b/package.json index 5fdff57..4ebf3f0 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.6.0", "concurrently": "^3.1.0", + "core-js": "^2.4.1", "csso": "^1.8.2", "es6-promise": "^3.2.1", "eslint": "^3.3.0", diff --git a/tests/spec/choices_spec.js b/tests/spec/choices_spec.js index e1b0169..3afafb2 100644 --- a/tests/spec/choices_spec.js +++ b/tests/spec/choices_spec.js @@ -1,38 +1,16 @@ import 'whatwg-fetch'; import 'es6-promise'; +import 'core-js/fn/object/assign'; import Choices from '../../assets/scripts/src/choices.js'; import itemReducer from '../../assets/scripts/src/reducers/items.js'; import choiceReducer from '../../assets/scripts/src/reducers/choices.js'; -import { addItem as addItemAction, addChoice as addChoiceAction } from '../../assets/scripts/src/actions/index.js'; - -if (typeof Object.assign != 'function') { - Object.assign = function (target, varArgs) { // .length of function is 2 - if (target == null) { // TypeError if undefined or null - throw new TypeError('Cannot convert undefined or null to object'); - } - - var to = Object(target); - - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - - if (nextSource != null) { // Skip over if undefined or null - for (var nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - }; -} +import { + addItem as addItemAction, + addChoice as addChoiceAction +} from '../../assets/scripts/src/actions/index.js'; describe('Choices', () => { - describe('should initialize Choices', () => { - beforeEach(function() { this.input = document.createElement('input'); this.input.type = 'text'; @@ -952,99 +930,113 @@ describe('Choices', () => { }); describe('should allow custom properties provided by the user on items or choices', function() { - it('should allow the user to supply custom properties for an item', function() { - var randomItem = { - id: 8999, - choiceId: 9000, - groupId: 9001, - value: 'value', - label: 'label', - customProperties: { - foo: 'bar' - } + it('should allow the user to supply custom properties for an item', function() { + const randomItem = { + id: 8999, + choiceId: 9000, + groupId: 9001, + value: 'value', + label: 'label', + customProperties: { + foo: 'bar' } + } - var expectedState = [{ - id: randomItem.id, - choiceId: randomItem.choiceId, - groupId: randomItem.groupId, - value: randomItem.value, - label: randomItem.label, - active: true, - highlighted: false, - customProperties: randomItem.customProperties - }]; + const expectedState = [{ + id: randomItem.id, + choiceId: randomItem.choiceId, + groupId: randomItem.groupId, + value: randomItem.value, + label: randomItem.label, + active: true, + highlighted: false, + customProperties: randomItem.customProperties + }]; - var action = addItemAction(randomItem.value, randomItem.label, randomItem.id, randomItem.choiceId, randomItem.groupId, randomItem.customProperties); + const action = addItemAction( + randomItem.value, + randomItem.label, + randomItem.id, + randomItem.choiceId, + randomItem.groupId, + randomItem.customProperties + ); - expect(itemReducer([], action)).toEqual(expectedState); - }); - - it('should allow the user to supply custom properties for a choice', function() { - var randomChoice = { - id: 123, - elementId: 321, - groupId: 213, - value: 'value', - label: 'label', - disabled: false, - customProperties: { - foo: 'bar' - } - } - - var expectedState = [{ - id: randomChoice.id, - elementId: randomChoice.elementId, - groupId: randomChoice.groupId, - value: randomChoice.value, - label: randomChoice.label, - disabled: randomChoice.disabled, - selected: false, - active: true, - score: 9999, - customProperties: randomChoice.customProperties - }]; - - var action = addChoiceAction(randomChoice.value, randomChoice.label, randomChoice.id, randomChoice.groupId, randomChoice.disabled, randomChoice.elementId, randomChoice.customProperties); - - expect(choiceReducer([], action)).toEqual(expectedState); - }); + expect(itemReducer([], action)).toEqual(expectedState); }); - describe('should allow custom properties provided by the user on items or choices', function() { - beforeEach(function() { - this.input = document.createElement('select'); - this.input.className = 'js-choices'; - this.input.setAttribute('multiple', ''); + it('should allow the user to supply custom properties for a choice', function() { + const randomChoice = { + id: 123, + elementId: 321, + groupId: 213, + value: 'value', + label: 'label', + disabled: false, + customProperties: { + foo: 'bar' + } + } - document.body.appendChild(this.input); - }); + const expectedState = [{ + id: randomChoice.id, + elementId: randomChoice.elementId, + groupId: randomChoice.groupId, + value: randomChoice.value, + label: randomChoice.label, + disabled: randomChoice.disabled, + selected: false, + active: true, + score: 9999, + customProperties: randomChoice.customProperties + }]; - afterEach(function() { - this.choices.destroy(); - }); + const action = addChoiceAction( + randomChoice.value, + randomChoice.label, + randomChoice.id, + randomChoice.groupId, + randomChoice.disabled, + randomChoice.elementId, + randomChoice.customProperties + ); - it('should allow the user to supply custom properties for a choice that will be inherited by the item when the user selects the choice', function() { - - var expectedCustomProperties = { - isBestOptionEver: true - }; - - this.choices = new Choices(this.input); - this.choices.setChoices([{ - value: '42', - label: 'My awesome choice', - selected: false, - disabled: false, - customProperties: expectedCustomProperties - }], 'value', 'label', true); - - this.choices.setValueByChoice('42'); - var selectedItems = this.choices.getValue(); - - expect(selectedItems.length).toBe(1); - expect(selectedItems[0].customProperties).toBe(expectedCustomProperties); - }); + expect(choiceReducer([], action)).toEqual(expectedState); }); + }); + + describe('should allow custom properties provided by the user on items or choices', function() { + beforeEach(function() { + this.input = document.createElement('select'); + this.input.className = 'js-choices'; + this.input.setAttribute('multiple', ''); + + document.body.appendChild(this.input); + }); + + afterEach(function() { + this.choices.destroy(); + }); + + it('should allow the user to supply custom properties for a choice that will be inherited by the item when the user selects the choice', function() { + const expectedCustomProperties = { + isBestOptionEver: true + }; + + this.choices = new Choices(this.input); + this.choices.setChoices([{ + value: '42', + label: 'My awesome choice', + selected: false, + disabled: false, + customProperties: expectedCustomProperties + }], 'value', 'label', true); + + this.choices.setValueByChoice('42'); + const selectedItems = this.choices.getValue(); + + expect(selectedItems.length).toBe(1); + expect(selectedItems[0].customProperties).toBe(expectedCustomProperties); + }); + }); });