Documentation + housekeeping + ensure custom props are passed to preset items

This commit is contained in:
Josh Johnson 2017-06-29 08:40:56 +01:00
parent 271d2a20b3
commit 4370616519
6 changed files with 241 additions and 215 deletions

2
.gitignore vendored
View file

@ -2,7 +2,7 @@ node_modules
npm-debug.log npm-debug.log
.DS_Store .DS_Store
.vscode .vscode
.package-lock.json package-lock.json
# Test # Test
tests/reports tests/reports

203
README.md
View file

@ -92,29 +92,29 @@ Or include Choices directly:
return `Only ${maxItemCount} values can be added.`; return `Only ${maxItemCount} values can be added.`;
}, },
classNames: { classNames: {
containerOuter: 'choices', containerOuter: 'choices',
containerInner: 'choices__inner', containerInner: 'choices__inner',
input: 'choices__input', input: 'choices__input',
inputCloned: 'choices__input--cloned', inputCloned: 'choices__input--cloned',
list: 'choices__list', list: 'choices__list',
listItems: 'choices__list--multiple', listItems: 'choices__list--multiple',
listSingle: 'choices__list--single', listSingle: 'choices__list--single',
listDropdown: 'choices__list--dropdown', listDropdown: 'choices__list--dropdown',
item: 'choices__item', item: 'choices__item',
itemSelectable: 'choices__item--selectable', itemSelectable: 'choices__item--selectable',
itemDisabled: 'choices__item--disabled', itemDisabled: 'choices__item--disabled',
itemChoice: 'choices__item--choice', itemChoice: 'choices__item--choice',
group: 'choices__group', group: 'choices__group',
groupHeading : 'choices__heading', groupHeading : 'choices__heading',
button: 'choices__button', button: 'choices__button',
activeState: 'is-active', activeState: 'is-active',
focusState: 'is-focused', focusState: 'is-focused',
openState: 'is-open', openState: 'is-open',
disabledState: 'is-disabled', disabledState: 'is-disabled',
highlightedState: 'is-highlighted', highlightedState: 'is-highlighted',
hiddenState: 'is-hidden', hiddenState: 'is-hidden',
flippedState: 'is-flipped', flippedState: 'is-flipped',
loadingState: 'is-loading', loadingState: 'is-loading',
}, },
// Choices uses the great Fuse library for searching. You // Choices uses the great Fuse library for searching. You
// can find more options here: https://github.com/krisk/Fuse#options // can find more options here: https://github.com/krisk/Fuse#options
@ -165,7 +165,10 @@ Pass an array of objects:
{ {
value: 'Value 2', value: 'Value 2',
label: 'Label 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', label: 'Option 2',
selected: false, selected: false,
disabled: true, 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` **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 ### searchFloor
**Type:** `Number` **Default:** `1` **Type:** `Number` **Default:** `1`
@ -325,9 +332,9 @@ Pass an array of objects:
```js ```js
// Sorting via length of label from largest to smallest // Sorting via length of label from largest to smallest
const example = new Choices(element, { const example = new Choices(element, {
sortFilter: function(a, b) { sortFilter: function(a, b) {
return b.label.length - a.label.length; return b.label.length - a.label.length;
}, },
}; };
``` ```
@ -406,29 +413,29 @@ const example = new Choices(element, {
``` ```
classNames: { classNames: {
containerOuter: 'choices', containerOuter: 'choices',
containerInner: 'choices__inner', containerInner: 'choices__inner',
input: 'choices__input', input: 'choices__input',
inputCloned: 'choices__input--cloned', inputCloned: 'choices__input--cloned',
list: 'choices__list', list: 'choices__list',
listItems: 'choices__list--multiple', listItems: 'choices__list--multiple',
listSingle: 'choices__list--single', listSingle: 'choices__list--single',
listDropdown: 'choices__list--dropdown', listDropdown: 'choices__list--dropdown',
item: 'choices__item', item: 'choices__item',
itemSelectable: 'choices__item--selectable', itemSelectable: 'choices__item--selectable',
itemDisabled: 'choices__item--disabled', itemDisabled: 'choices__item--disabled',
itemOption: 'choices__item--choice', itemOption: 'choices__item--choice',
group: 'choices__group', group: 'choices__group',
groupHeading : 'choices__heading', groupHeading : 'choices__heading',
button: 'choices__button', button: 'choices__button',
activeState: 'is-active', activeState: 'is-active',
focusState: 'is-focused', focusState: 'is-focused',
openState: 'is-open', openState: 'is-open',
disabledState: 'is-disabled', disabledState: 'is-disabled',
highlightedState: 'is-highlighted', highlightedState: 'is-highlighted',
hiddenState: 'is-hidden', hiddenState: 'is-hidden',
flippedState: 'is-flipped', flippedState: 'is-flipped',
selectedState: 'is-highlighted', selectedState: 'is-highlighted',
} }
``` ```
@ -465,14 +472,14 @@ const example = new Choices(element, {
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''}> <div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''}>
<span>&bigstar;</span> ${data.label} <span>&bigstar;</span> ${data.label}
</div> </div>
`); `);
}, },
choice: (data) => { choice: (data) => {
return template(` return template(`
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="${this.config.itemSelectText}" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}" ${data.groupId > 0 ? 'role="treeitem"' : 'role="option"'}> <div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="${this.config.itemSelectText}" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}" ${data.groupId > 0 ? 'role="treeitem"' : 'role="option"'}>
<span>&bigstar;</span> ${data.label} <span>&bigstar;</span> ${data.label}
</div> </div>
`); `);
}, },
}; };
} }
@ -506,7 +513,6 @@ example.passedElement.addEventListener('addItem', function(event) {
console.log(event.detail.label); console.log(event.detail.label);
console.log(event.detail.groupValue); console.log(event.detail.groupValue);
}, false); }, false);
``` ```
### addItem ### addItem
@ -574,12 +580,14 @@ Methods can be called either directly or by chaining:
const choices = new Choices(element, { const choices = new Choices(element, {
addItems: false, addItems: false,
removeItems: false, removeItems: false,
}).setValue(['Set value 1', 'Set value 2']).disable(); })
.setValue(['Set value 1', 'Set value 2'])
.disable();
// Calling a method directly // Calling a method directly
const choices = new Choices(element, { const choices = new Choices(element, {
addItems: false, addItems: false,
removeItems: false, removeItems: false,
}); });
choices.setValue(['Set value 1', 'Set value 2']) choices.setValue(['Set value 1', 'Set value 2'])
@ -648,7 +656,7 @@ choices.disable();
### setChoices(choices, value, label, replaceChoices); ### setChoices(choices, value, label, replaceChoices);
**Input types affected:** `select-one`, `select-multiple` **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:** **Example 1:**
@ -656,9 +664,9 @@ choices.disable();
const example = new Choices(element); const example = new Choices(element);
example.setChoices([ example.setChoices([
{value: 'One', label: 'Label One', disabled: true}, {value: 'One', label: 'Label One', disabled: true},
{value: 'Two', label: 'Label Two', selected: true}, {value: 'Two', label: 'Label Two', selected: true},
{value: 'Three', label: 'Label Three'}, {value: 'Three', label: 'Label Three'},
], 'value', 'label', false); ], 'value', 'label', false);
``` ```
@ -668,24 +676,27 @@ example.setChoices([
const example = new Choices(element); const example = new Choices(element);
example.setChoices([{ example.setChoices([{
label: 'Group one', label: 'Group one',
id: 1, id: 1,
disabled: false, disabled: false,
choices: [ choices: [
{value: 'Child One', label: 'Child One', selected: true}, {value: 'Child One', label: 'Child One', selected: true},
{value: 'Child Two', label: 'Child Two', disabled: true}, {value: 'Child Two', label: 'Child Two', disabled: true},
{value: 'Child Three', label: 'Child Three'}, {value: 'Child Three', label: 'Child Three'},
] ]
}, },
{ {
label: 'Group two', label: 'Group two',
id: 2, id: 2,
disabled: false, disabled: false,
choices: [ choices: [
{value: 'Child Four', label: 'Child Four', disabled: true}, {value: 'Child Four', label: 'Child Four', disabled: true},
{value: 'Child Five', label: 'Child Five'}, {value: 'Child Five', label: 'Child Five'},
{value: 'Child Six', label: 'Child Six'}, {value: 'Child Six', label: 'Child Six', customProperties: {
] description: 'Custom description about child six',
random: 'Another random custom property'
}},
]
}], 'value', 'label', false); }], 'value', 'label', false);
``` ```
@ -714,9 +725,9 @@ const example = new Choices(element);
// via an array of objects // via an array of objects
example.setValue([ example.setValue([
{value: 'One', label: 'Label One'}, {value: 'One', label: 'Label One'},
{value: 'Two', label: 'Label Two'}, {value: 'Two', label: 'Label Two'},
{value: 'Three', label: 'Label Three'}, {value: 'Three', label: 'Label Three'},
]); ]);
// or via an array of strings // or via an array of strings
@ -732,11 +743,11 @@ example.setValue(['Four','Five','Six']);
```js ```js
const example = new Choices(element, { const example = new Choices(element, {
choices: [ choices: [
{value: 'One', label: 'Label One'}, {value: 'One', label: 'Label One'},
{value: 'Two', label: 'Label Two', disabled: true}, {value: 'Two', label: 'Label Two', disabled: true},
{value: 'Three', label: 'Label Three'}, {value: 'Three', label: 'Label Three'},
], ],
}); });
example.setValueByChoice('Two'); // Choice with value of 'Two' has now been selected. 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); var example = new Choices(element);
example.ajax(function(callback) { example.ajax(function(callback) {
fetch(url) fetch(url)
.then(function(response) { .then(function(response) {
response.json().then(function(data) { response.json().then(function(data) {
callback(data, 'value', 'label'); callback(data, 'value', 'label');
}); });
}) })
.catch(function(error) { .catch(function(error) {
console.log(error); console.log(error);
}); });
}); });
``` ```

View file

@ -871,7 +871,13 @@ class Choices {
if (foundChoice) { if (foundChoice) {
if (!foundChoice.selected) { 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) { } else if (!this.config.silent) {
console.warn('Attempting to select choice already selected'); console.warn('Attempting to select choice already selected');
} }
@ -2673,7 +2679,13 @@ class Choices {
if (!item.value) { if (!item.value) {
return; return;
} }
this._addItem(item.value, item.label, item.id); this._addItem(
item.value,
item.label,
item.id,
undefined,
item.customProperties
);
} else if (itemType === 'String') { } else if (itemType === 'String') {
this._addItem(item); this._addItem(item);
} }

View file

@ -63,7 +63,7 @@
<input class="form-control" id="choices-text-prepend-append-value" type="text" value="preset-1, preset-2" placeholder="This is a placeholder"> <input class="form-control" id="choices-text-prepend-append-value" type="text" value="preset-1, preset-2" placeholder="This is a placeholder">
<label for="choices-text-preset-values">Preset values passed through options</label> <label for="choices-text-preset-values">Preset values passed through options</label>
<input class="form-control" id="choices-text-preset-values" type="text" value="olivia@benson.com" placeholder="This is a placeholder"> <input class="form-control" id="choices-text-preset-values" type="text" value="Michael Smith" placeholder="This is a placeholder">
<label for="choices-text-i18n">I18N labels</label> <label for="choices-text-i18n">I18N labels</label>
<input class="form-control" data-trigger id="choices-text-i18n" type="text"> <input class="form-control" data-trigger id="choices-text-i18n" type="text">
@ -208,7 +208,8 @@
<label for="choices-single-preset-options">Option and option groups added via config</label> <label for="choices-single-preset-options">Option and option groups added via config</label>
<select class="form-control" name="choices-single-preset-options" id="choices-single-preset-options" placeholder="This is a placeholder"></select> <select class="form-control" name="choices-single-preset-options" id="choices-single-preset-options" placeholder="This is a placeholder"></select>
<label for="choices-single-selected-option">Option selected via config</label> <label for="choices-single-selected-option">Option selected via config with custom properties</label>
<p><small>Try searching for 'fantastic'</small></p>
<select class="form-control" name="choices-single-selected-option" id="choices-single-selected-option" placeholder="This is a placeholder"></select> <select class="form-control" name="choices-single-selected-option" id="choices-single-selected-option" placeholder="This is a placeholder"></select>
<label for="choices-single-no-sorting">Options without sorting</label> <label for="choices-single-no-sorting">Options without sorting</label>
@ -306,7 +307,13 @@
}).removeActiveItems(); }).removeActiveItems();
var textPresetVal = new Choices('#choices-text-preset-values', { 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')); var multipleDefault = new Choices(document.getElementById('choices-multiple-groups'));
@ -433,10 +440,13 @@
}], 'value', 'label'); }], 'value', 'label');
var singleSelectedOpt = new Choices('#choices-single-selected-option', { var singleSelectedOpt = new Choices('#choices-single-selected-option', {
searchFields: ['label', 'value', 'customProperties.description'],
choices: [ choices: [
{value: 'One', label: 'Label One', selected: true}, {value: 'One', label: 'Label One', selected: true},
{value: 'Two', label: 'Label Two', disabled: 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'); }).setValueByChoice('Two');

View file

@ -33,6 +33,7 @@
"babel-loader": "^6.2.4", "babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0", "babel-preset-es2015": "^6.6.0",
"concurrently": "^3.1.0", "concurrently": "^3.1.0",
"core-js": "^2.4.1",
"csso": "^1.8.2", "csso": "^1.8.2",
"es6-promise": "^3.2.1", "es6-promise": "^3.2.1",
"eslint": "^3.3.0", "eslint": "^3.3.0",

View file

@ -1,38 +1,16 @@
import 'whatwg-fetch'; import 'whatwg-fetch';
import 'es6-promise'; import 'es6-promise';
import 'core-js/fn/object/assign';
import Choices from '../../assets/scripts/src/choices.js'; import Choices from '../../assets/scripts/src/choices.js';
import itemReducer from '../../assets/scripts/src/reducers/items.js'; import itemReducer from '../../assets/scripts/src/reducers/items.js';
import choiceReducer from '../../assets/scripts/src/reducers/choices.js'; import choiceReducer from '../../assets/scripts/src/reducers/choices.js';
import { addItem as addItemAction, addChoice as addChoiceAction } from '../../assets/scripts/src/actions/index.js'; import {
addItem as addItemAction,
if (typeof Object.assign != 'function') { addChoice as addChoiceAction
Object.assign = function (target, varArgs) { // .length of function is 2 } from '../../assets/scripts/src/actions/index.js';
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;
};
}
describe('Choices', () => { describe('Choices', () => {
describe('should initialize Choices', () => { describe('should initialize Choices', () => {
beforeEach(function() { beforeEach(function() {
this.input = document.createElement('input'); this.input = document.createElement('input');
this.input.type = 'text'; this.input.type = 'text';
@ -952,99 +930,113 @@ describe('Choices', () => {
}); });
describe('should allow custom properties provided by the user on items or choices', function() { 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() { it('should allow the user to supply custom properties for an item', function() {
var randomItem = { const randomItem = {
id: 8999, id: 8999,
choiceId: 9000, choiceId: 9000,
groupId: 9001, groupId: 9001,
value: 'value', value: 'value',
label: 'label', label: 'label',
customProperties: { customProperties: {
foo: 'bar' foo: 'bar'
}
} }
}
var expectedState = [{ const expectedState = [{
id: randomItem.id, id: randomItem.id,
choiceId: randomItem.choiceId, choiceId: randomItem.choiceId,
groupId: randomItem.groupId, groupId: randomItem.groupId,
value: randomItem.value, value: randomItem.value,
label: randomItem.label, label: randomItem.label,
active: true, active: true,
highlighted: false, highlighted: false,
customProperties: randomItem.customProperties 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); 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);
});
}); });
describe('should allow custom properties provided by the user on items or choices', function() { it('should allow the user to supply custom properties for a choice', function() {
beforeEach(function() { const randomChoice = {
this.input = document.createElement('select'); id: 123,
this.input.className = 'js-choices'; elementId: 321,
this.input.setAttribute('multiple', ''); 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() { const action = addChoiceAction(
this.choices.destroy(); 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() { expect(choiceReducer([], action)).toEqual(expectedState);
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);
});
}); });
});
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);
});
});
}); });