Terminology updates (options -> choices) && documentation

This commit is contained in:
Josh Johnson 2016-06-27 14:46:12 +01:00
parent 2fa45b2eee
commit a2e45209a7
9 changed files with 223 additions and 173 deletions

173
README.md
View file

@ -1,4 +1,4 @@
# Choices.js - in development
# Choices.js - beta
A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
Coming soon.
@ -17,35 +17,70 @@ Coming soon.
var choice = new Choices('[data-choice']);
var choice = new Choices('.js-choice');
// Passing options
// Passing options (with default options)
var choices = new Choices(elements, {
items: [],
maxItemCount: -1,
addItems: true,
removeItems: true,
removeButton: false,
removeItemButton: false,
editItems: false,
maxItems: false,
duplicateItems: true,
delimiter: ',',
allowDuplicates: true,
allowPaste: true,
allowSearch: true,
regexFilter: false,
paste: true,
searchOptions: true,
regexFilter: null,
placeholder: true,
placeholderValue: '',
prependValue: false,
appendValue: false,
highlightAll: true,
placeholderValue: null,
prependValue: null,
appendValue: null,
loadingText: 'Loading...',
templates: {},
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--option',
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-selected',
},
callbackOnInit: () => {},
callbackOnAddItem: (id, value, passedInput) => {},
callbackOnRemoveItem: (id, value, passedInput) => {},
});
</script>
```
## Installation
To install via NPM, run `npm install --save-dev choices.js`
To install via NPM, run `npm install --save-dev choices.js`
## Terminology
| Word | Definition |
| ------ | ---------- |
| Choice | A choice is a value a user can select. A choice would be equivelant to the `<option></option>` element within a select input. |
| Group | A group is a collection of choices. A group should be seen as equivalent to a `<optgroup></optgroup>` element within a select input.|
| Item | An item is an inputted value (if you are using Choices with a text input) or a selected choice (if you are using Choices with a select element). |
## Options
#### items
<strong>Type:</strong> `Array` <strong>Default:</strong> `[]`
### items
<strong>Type:</strong> <strong>Default:</strong> `[]`
<strong>Usage:</strong> Add pre-selected items to input.
@ -68,87 +103,89 @@ Pass an array of objects:
}]
```
#### addItems
### maxItemCount
<strong>Type:</strong> `Number` <strong>Default:</strong>`-1`
<strong>Usage:</strong> The amount of items a user can input/select ("-1" indicates no limit).
### addItems
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether a user can add items.
<strong>Usage:</strong> Whether a user can add items to the passed input's value.
#### removeItems
### removeItems
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether a user can remove items (only affects text and multiple select input types).
#### removeButton
### removeButton
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`false`
<strong>Usage:</strong> Whether a button should show that, when clicked, will remove an item (only affects text and multiple select input types).
#### editItems
### editItems
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`false`
<strong>Usage:</strong> Whether a user can edit selected items (only affects text input types).
#### maxItems
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`null`
<strong>Usage:</strong> Optionally set an item limit (`-1` indicates no limit).
<strong>Usage:</strong> Optionally set an item limit.
#### delimiter
### delimiter
<strong>Type:</strong> `String` <strong>Default:</strong>`,`
<strong>Usage:</strong> What divides each value (only affects text input types).
#### allowDuplicates
### allowDuplicates
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether a user can input a duplicate item (only affects text input types).
#### allowPaste
### allowPaste
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether a user can paste into the input.
#### allowSearch
### allowSearch
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether a user can filter options by searching (only affects select input types).
#### regexFilter
### regexFilter
<strong>Type:</strong> `Regex` <strong>Default:</strong>`null`
<strong>Usage:</strong> A filter that will need to pass for a user to successfully add an item (only affects text input types).
#### placeholder
### placeholder
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value.
#### placeholderValue
### placeholderValue
<strong>Type:</strong> `String` <strong>Default:</strong>`null`
<strong>Usage:</strong> The value of the inputs placeholder.
#### prependValue
### prependValue
<strong>Type:</strong> `String` <strong>Default:</strong>`null`
<strong>Usage:</strong> Prepend a value to each item added to input (only affects text input types).
#### appendValue
### appendValue
<strong>Type:</strong> `String` <strong>Default:</strong>`null`
<strong>Usage:</strong> Append a value to each item added to input (only affects text input types).
#### highlightAll
### highlightAll
<strong>Type:</strong> `Boolean` <strong>Default:</strong>`true`
<strong>Usage:</strong> Whether a user can highlight items.
#### loadingText
### loadingText
<strong>Type:</strong> `String` <strong>Default:</strong>`Loading...`
<strong>Usage:</strong> The loading text that is shown when options are populated via an AJAX callback.
#### classNames
### classNames
<strong>Type:</strong> `Object` <strong>Default:</strong>
```
@ -181,72 +218,86 @@ classNames: {
<strong>Usage:</strong> Classes added to HTML generated by Choices.
#### callbackOnInit
### callbackOnInit
<strong>Type:</strong> `Function` <strong>Default:</strong>`() => {}`
<strong>Usage:</strong> Function to run once Choices initialises.
#### callbackOnAddItem
### callbackOnAddItem
<strong>Type:</strong> `Function` <strong>Default:</strong>`(id, value, passedInput) => {}`
<strong>Usage:</strong> Function to run each time an item is added.
#### callbackOnRemoveItem
### callbackOnRemoveItem
<strong>Type:</strong> `Function` <strong>Default:</strong>`(id, value, passedInput) => {}`
<strong>Usage:</strong> Function to run each time an item is removed.
## Methods
#### highlightAll();
Methods can be called either directly or by chaining:
```js
// Calling a method by chaining
const choices = new Choices(element, {
addItems: false,
removeItems: false,
}).setValue(['Set value 1', 'Set value 2']).disable();
// Calling a method directly
choices.setValue(['Set value 1', 'Set value 2'])
choices.disable();
```
### highlightAll();
<strong>Usage:</strong> Highlight each chosen item (selected items can be removed).
#### unhighlightAll();
### unhighlightAll();
<strong>Usage:</strong> Un-highlight each chosen item.
#### removeItemsByValue(value);
### removeItemsByValue(value);
<strong>Usage:</strong> Remove each item by a given value.
#### removeActiveItems(excludedId);
### removeActiveItems(excludedId);
<strong>Usage:</strong> Remove each selectable item.
#### removeSelectedItems();
### removeSelectedItems();
<strong>Usage:</strong> Remove each item the user has selected.
#### showDropdown();
<strong>Usage:</strong> Show option list dropdown.
### showDropdown();
<strong>Usage:</strong> Show option list dropdown (only affects select inputs).
#### hideDropdown();
<strong>Usage:</strong> Hide option list dropdown.
### hideDropdown();
<strong>Usage:</strong> Hide option list dropdown (only affects select inputs).
#### toggleDropdown();
### toggleDropdown();
<strong>Usage:</strong> Toggle dropdown between showing/hidden.
#### setValue(args);
<strong>Usage:</strong> Set value of input based on an array of objects or strings.
### setValue(args);
<strong>Usage:</strong> Set value of input based on an array of objects or strings. This behaves exactly the same as passing items via the `items` option but can be called after initialising Choices on an text input (only affects text inputs).
#### clearValue();
### clearValue();
<strong>Usage:</strong> Clear value of input.
#### clearInput();
<strong>Usage:</strong> Clear input.
### clearInput();
<strong>Usage:</strong> Clear input of any user inputted text (only affects text inputs).
#### disable();
### disable();
<strong>Usage:</strong> Disable input from selecting further options.
#### ajax(fn);
### ajax(fn);
<strong>Usage:</strong> Populate options via a callback.
@ -259,10 +310,12 @@ To setup a local environment: clone this repo, navigate into it's directory in a
```npm install```
### NPM tasks
* ```npm start```
* ```npm run js:build```
* ```npm run css:watch```
* ```npm run css:build```
| Task | Usage |
| ------------------- | ------------------------------------------------------------ |
| `npm start` | Fire up local server for development |
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
## Contributions
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using npm scripts...bla bla bla

File diff suppressed because one or more lines are too long

View file

@ -1,18 +1,18 @@
export const addItem = (value, label, id, optionId) => {
export const addItem = (value, label, id, choiceId) => {
return {
type: 'ADD_ITEM',
value,
label,
id,
optionId,
choiceId,
}
};
export const removeItem = (id, optionId) => {
export const removeItem = (id, choiceId) => {
return {
type: 'REMOVE_ITEM',
id,
optionId,
choiceId,
}
};
@ -24,9 +24,9 @@ export const selectItem = (id, selected) => {
}
};
export const addOption = (value, label, id, groupId, disabled) => {
export const addChoice = (value, label, id, groupId, disabled) => {
return {
type: 'ADD_OPTION',
type: 'ADD_CHOICE',
value,
label,
id,
@ -35,14 +35,14 @@ export const addOption = (value, label, id, groupId, disabled) => {
}
};
export const filterOptions = (results) => {
export const filterChoices = (results) => {
return {
type: 'FILTER_OPTIONS',
type: 'FILTER_CHOICES',
results,
}
};
export const activateOptions = (active = true) => {
export const activateChoices = (active = true) => {
return {
type: 'ACTIVATE_OPTIONS',
active,

View file

@ -1,6 +1,6 @@
'use strict';
import { addItem, removeItem, selectItem, addOption, filterOptions, activateOptions, addGroup, clearAll } from './actions/index';
import { addItem, removeItem, selectItem, addChoice, filterChoices, activateChoices, addGroup, clearAll } from './actions/index';
import { isScrolledIntoView, getAdjacentEl, findAncestor, wrap, isType, strToEl, extend, getWidthOfInput, debounce } from './lib/utils.js';
import Fuse from 'fuse.js';
import Store from './store/index.js';
@ -29,21 +29,20 @@ export class Choices {
const defaultOptions = {
items: [],
maxItemCount: -1,
addItems: true,
removeItems: true,
removeButton: false,
removeItemButton: false,
editItems: false,
maxItems: null,
duplicateItems: true,
delimiter: ',',
allowDuplicates: true,
allowPaste: true,
allowSearch: true,
paste: true,
searchOptions: true,
regexFilter: null,
placeholder: true,
placeholderValue: null,
prependValue: null,
appendValue: null,
highlightAll: true,
loadingText: 'Loading...',
templates: {},
classNames: {
@ -72,8 +71,8 @@ export class Choices {
selectedState: 'is-selected',
},
callbackOnInit: () => {},
callbackOnRemoveItem: () => {},
callbackOnAddItem: () => {}
callbackOnAddItem: (id, value, passedInput) => {},
callbackOnRemoveItem: (id, value, passedInput) => {},
};
// Merge options with user options
@ -92,7 +91,7 @@ export class Choices {
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
this.highlightPosition = 0;
this.canSearch = this.options.allowSearch;
this.canSearch = this.options.searchOptions;
// Assign preset items from passed object first
this.presetItems = this.options.items;
@ -371,13 +370,13 @@ export class Choices {
// If we are dealing with a select input, we need to create an option first
// that is then selected. For text inputs we can just add items normally.
if(this.passedElement.type !== 'text') {
this._addOption(true, false, item.value, item.label, -1);
this._addChoice(true, false, item.value, item.label, -1);
} else {
this._addItem(item.value, item.label, item.id);
}
} else if(isType('String', item)) {
if(this.passedElement.type !== 'text') {
this._addOption(true, false, item, item, -1);
this._addChoice(true, false, item, item, -1);
} else {
this._addItem(item);
}
@ -433,7 +432,7 @@ export class Choices {
if(index === 0) {
this._addItem(result[value], result[label], index);
}
this._addOption(false, false, result[value], result[label]);
this._addChoice(false, false, result[value], result[label]);
});
}
};
@ -466,11 +465,11 @@ export class Choices {
let canUpdate = true;
if(this.options.addItems) {
if (this.options.maxItems && this.options.maxItems <= this.itemList.children.length) {
if (this.options.maxItemCount && this.options.maxItemCount > 0 && this.options.maxItemCount <= this.itemList.children.length) {
// If there is a max entry limit and we have reached that limit
// don't update
canUpdate = false;
} else if(this.options.allowDuplicates === false && this.passedElement.value) {
} else if(this.options.duplicateItems === false && this.passedElement.value) {
// If no duplicates are allowed, and the value already exists
// in the array, don't update
canUpdate = !activeItems.some((item) => item.value === value );
@ -539,7 +538,7 @@ export class Choices {
const downKey = 40;
const activeItems = this.store.getItemsFilteredByActive();
const activeOptions = this.store.getOptionsFilteredByActive();
const activeOptions = this.store.getChoicesFilteredByActive();
const hasFocusedInput = this.input === document.activeElement;
const hasActiveDropdown = this.dropdown.classList.contains(this.options.classNames.activeState);
@ -551,14 +550,14 @@ export class Choices {
this.showDropdown();
}
this.canSearch = this.options.allowSearch;
this.canSearch = this.options.searchOptions;
switch (e.keyCode) {
case aKey:
// If CTRL + A or CMD + A have been pressed and there are items to select
if(ctrlDownKey && hasItems) {
this.canSearch = false;
if(this.options.removeItems && !this.input.value && this.options.highlightAll && this.input === document.activeElement) {
if(this.options.removeItems && !this.input.value && this.input === document.activeElement) {
// Highlight items
this.highlightAll(this.itemList.children);
}
@ -584,7 +583,7 @@ export class Choices {
if(this.passedElement.type === 'select-one') {
this.isSearching = false;
this.store.dispatch(activateOptions());
this.store.dispatch(activateChoices());
this.toggleDropdown();
}
}
@ -616,10 +615,10 @@ export class Choices {
if(nextEl) {
// We prevent default to stop the cursor moving
// when pressing the arrow
if(!isScrolledIntoView(nextEl, this.optionList, directionInt)) {
this._scrollToOption(nextEl, directionInt);
if(!isScrolledIntoView(nextEl, this.choiceList, directionInt)) {
this._scrollToChoice(nextEl, directionInt);
}
this._highlightOption(nextEl);
this._highlightChoice(nextEl);
}
// Prevent default to maintain cursor position whilst
@ -661,9 +660,9 @@ export class Choices {
const activeItems = this.store.getItemsFilteredByActive();
const isUnique = !activeItems.some((item) => item.value === this.input.value);
if (this.options.maxItems && this.options.maxItems <= this.itemList.children.length) {
dropdownItem = this._getTemplate('notice', `Only ${ this.options.maxItems } options can be added.`);
} else if(!this.options.allowDuplicates && !isUnique) {
if (this.options.maxItemCount && this.options.maxItemCount > 0 && this.options.maxItemCount <= this.itemList.children.length) {
dropdownItem = this._getTemplate('notice', `Only ${ this.options.maxItemCount } options can be added.`);
} else if(!this.options.duplicateItems && !isUnique) {
dropdownItem = this._getTemplate('notice', `Only unique values can be added.`);
} else {
dropdownItem = this._getTemplate('notice', `Add "${ this.input.value }"`);
@ -684,7 +683,7 @@ export class Choices {
// If we have enabled text search
if(this.canSearch) {
if(this.input === document.activeElement) {
const options = this.store.getOptions();
const options = this.store.getChoices();
const hasUnactiveOptions = options.some((option) => option.active !== true);
// Check that we have a value to search and the input was an alphanumeric character
@ -694,7 +693,7 @@ export class Choices {
const currentValue = this.currentValue.trim();
if(newValue.length >= 1 && newValue !== currentValue + ' ') {
const haystack = this.store.getOptionsFiltedBySelectable();
const haystack = this.store.getChoicesFiltedBySelectable();
const needle = newValue;
const fuse = new Fuse(haystack, {
keys: ['label', 'value'],
@ -706,7 +705,7 @@ export class Choices {
this.currentValue = newValue;
this.highlightPosition = 0;
this.isSearching = true;
this.store.dispatch(filterOptions(results));
this.store.dispatch(filterChoices(results));
}
};
@ -714,7 +713,7 @@ export class Choices {
} else if(hasUnactiveOptions) {
// Otherwise reset options to active
this.isSearching = false;
this.store.dispatch(activateOptions());
this.store.dispatch(activateChoices());
}
}
}
@ -763,7 +762,7 @@ export class Choices {
}
if(e.target.hasAttribute('data-button')) {
if(this.options.removeItems && this.options.removeButton) {
if(this.options.removeItems && this.options.removeItemButton) {
const itemId = e.target.parentNode.getAttribute('data-id');
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
this._removeItem(itemToRemove);
@ -786,7 +785,7 @@ export class Choices {
}
} else if(e.target.hasAttribute('data-option')) {
// If we are clicking on an option
const options = this.store.getOptionsFilteredByActive();
const options = this.store.getChoicesFilteredByActive();
const id = e.target.getAttribute('data-id');
const option = options.find((option) => option.id === parseInt(id));
@ -795,7 +794,7 @@ export class Choices {
if(this.passedElement.type === 'select-one') {
this.input.value = "";
this.isSearching = false;
this.store.dispatch(activateOptions(true));
this.store.dispatch(activateChoices(true));
this.toggleDropdown();
}
}
@ -828,7 +827,7 @@ export class Choices {
// If the dropdown is either the target or one of its children is the target
if((e.target === this.dropdown || findAncestor(e.target, this.options.classNames.listDropdown))) {
if(e.target.hasAttribute('data-option')) {
this._highlightOption(e.target);
this._highlightChoice(e.target);
}
}
}
@ -842,7 +841,7 @@ export class Choices {
_onPaste(e) {
if(e.target !== this.input) return;
// Disable pasting into the input if option has been set
if(!this.options.allowPaste) {
if(!this.options.paste) {
e.preventDefault();
}
}
@ -899,20 +898,20 @@ export class Choices {
* @return
* @private
*/
_scrollToOption(option, direction) {
_scrollToChoice(option, direction) {
if(!option) return;
const dropdownHeight = this.optionList.offsetHeight;
const dropdownHeight = this.choiceList.offsetHeight;
const optionHeight = option.offsetHeight;
// Distance from bottom of element to top of parent
const optionPos = option.offsetTop + optionHeight;
const choicePos = option.offsetTop + optionHeight;
// Scroll position of dropdown
const containerScrollPos = this.optionList.scrollTop + dropdownHeight;
const containerScrollPos = this.choiceList.scrollTop + dropdownHeight;
// Difference between the option and scroll position
let endPoint = direction > 0 ? ((this.optionList.scrollTop + optionPos) - containerScrollPos) : option.offsetTop;
let endPoint = direction > 0 ? ((this.choiceList.scrollTop + choicePos) - containerScrollPos) : option.offsetTop;
const animateScroll = (time, endPoint, direction) => {
let continueAnimation = false;
@ -920,19 +919,19 @@ export class Choices {
const strength = 4;
if(direction > 0) {
easing = (endPoint - this.optionList.scrollTop)/strength;
easing = (endPoint - this.choiceList.scrollTop)/strength;
distance = easing > 1 ? easing : 1;
this.optionList.scrollTop = this.optionList.scrollTop + distance;
if(this.optionList.scrollTop < endPoint) {
this.choiceList.scrollTop = this.choiceList.scrollTop + distance;
if(this.choiceList.scrollTop < endPoint) {
continueAnimation = true;
}
} else {
easing = (this.optionList.scrollTop - endPoint)/strength;
easing = (this.choiceList.scrollTop - endPoint)/strength;
distance = easing > 1 ? easing : 1;
this.optionList.scrollTop = this.optionList.scrollTop - distance;
if(this.optionList.scrollTop > endPoint) {
this.choiceList.scrollTop = this.choiceList.scrollTop - distance;
if(this.choiceList.scrollTop > endPoint) {
continueAnimation = true;
}
}
@ -955,11 +954,11 @@ export class Choices {
* @return
* @private
*/
_highlightOption(el) {
_highlightChoice(el) {
// Highlight first element in dropdown
const options = Array.from(this.dropdown.querySelectorAll('[data-option-selectable]'));
if(options.length) {
if(options && options.length) {
const highlightedOptions = Array.from(this.dropdown.querySelectorAll(`.${this.options.classNames.highlightedState}`));
// Remove any highlighted options
@ -996,11 +995,11 @@ export class Choices {
* @return {Object} Class instance
* @public
*/
_addItem(value, label, optionId = -1, callback = this.options.callbackOnAddItem) {
_addItem(value, label, choiceId = -1, callback = this.options.callbackOnAddItem) {
const items = this.store.getItems();
let passedValue = value.trim();
let passedLabel = label || passedValue;
let passedOptionId = optionId || -1;
let passedOptionId = choiceId || -1;
// If a prepended value has been passed, prepend it
if(this.options.prependValue) {
@ -1047,9 +1046,9 @@ export class Choices {
const id = item.id;
const value = item.value;
const optionId = item.optionId;
const choiceId = item.choiceId;
this.store.dispatch(removeItem(id, optionId));
this.store.dispatch(removeItem(id, choiceId));
// Run callback
if(callback){
@ -1061,22 +1060,20 @@ export class Choices {
}
/**
* Add option to dropdown
* @param {Object} option Option to add
* @param {Number} groupId ID of the options group
* Add choice to dropdoww
* @return
* @private
*/
_addOption(isSelected, isDisabled, value, label, groupId = -1) {
_addChoice(isSelected, isDisabled, value, label, groupId = -1) {
if(!value) return
if(!label) { label = value; }
// Generate unique id
const options = this.store.getOptions();
const id = options.length + 1;
const choices = this.store.getChoices();
const id = choices ? choices.length + 1 : 1;
this.store.dispatch(addOption(value, label, id, groupId, isDisabled));
this.store.dispatch(addChoice(value, label, id, groupId, isDisabled));
if(isSelected && !isDisabled) {
this._addItem(value, label, id);
@ -1098,7 +1095,7 @@ export class Choices {
this.store.dispatch(addGroup(group.label, groupId, true, group.disabled));
groupOptions.forEach((option, optionIndex) => {
const isDisabled = option.disabled || option.parentNode.disabled;
this._addOption(option.selected, isDisabled, option.value, option.innerHTML, groupId);
this._addChoice(option.selected, isDisabled, option.value, option.innerHTML, groupId);
});
} else {
this.store.dispatch(addGroup(group.label, group.id, false, group.disabled));
@ -1135,7 +1132,7 @@ export class Choices {
itemList: () => {
return strToEl(`<div class="${ classNames.list } ${ this.passedElement.type === 'select-one' ? classNames.listSingle : classNames.listItems }"></div>`);
},
optionList: () => {
choiceList: () => {
return strToEl(`<div class="${ classNames.list }"></div>`);
},
input: () => {
@ -1165,7 +1162,7 @@ export class Choices {
`);
},
item: (data) => {
if(this.options.removeButton && this.passedElement.type !== 'select-one') {
if(this.options.removeItemButton && this.passedElement.type !== 'select-one') {
return strToEl(`
<div class="${ classNames.item } ${ data.selected ? classNames.selectedState : ''} ${ !data.disabled ? classNames.itemSelectable : '' }" data-item data-id="${ data.id }" data-value="${ data.value }" data-deletable>
${ data.label }
@ -1194,14 +1191,14 @@ export class Choices {
const containerOuter = this._getTemplate('containerOuter');
const containerInner = this._getTemplate('containerInner');
const itemList = this._getTemplate('itemList');
const optionList = this._getTemplate('optionList');
const choiceList = this._getTemplate('choiceList');
const input = this._getTemplate('input');
const dropdown = this._getTemplate('dropdown');
this.containerOuter = containerOuter;
this.containerInner = containerInner;
this.input = input;
this.optionList = optionList;
this.choiceList = choiceList;
this.itemList = itemList;
this.dropdown = dropdown;
@ -1232,11 +1229,11 @@ export class Choices {
containerOuter.appendChild(containerInner);
containerOuter.appendChild(dropdown);
containerInner.appendChild(itemList);
dropdown.appendChild(optionList);
dropdown.appendChild(choiceList);
if(this.passedElement.type === 'select-multiple' || this.passedElement.type === 'text') {
containerInner.appendChild(input);
} else if(this.options.allowSearch) {
} else if(this.options.searchOptions) {
dropdown.insertBefore(input, dropdown.firstChild);
}
@ -1256,7 +1253,7 @@ export class Choices {
const passedOptions = Array.from(this.passedElement.options);
passedOptions.forEach((option) => {
const isDisabled = option.disabled || option.parentNode.disabled;
this._addOption(option.selected, isDisabled, option.value, option.innerHTML);
this._addChoice(option.selected, isDisabled, option.value, option.innerHTML);
});
}
@ -1391,12 +1388,12 @@ export class Choices {
if(this.passedElement.type === 'select-multiple' || this.passedElement.type === 'select-one') {
// Get active groups/options
const activeGroups = this.store.getGroupsFilteredByActive();
const activeOptions = this.store.getOptionsFilteredByActive();
const activeOptions = this.store.getChoicesFilteredByActive();
let optListFragment = document.createDocumentFragment();
// Clear options
this.optionList.innerHTML = '';
this.choiceList.innerHTML = '';
// If we have grouped options
if(activeGroups.length >= 1 && this.isSearching !== true) {
@ -1408,12 +1405,12 @@ export class Choices {
if(optListFragment.children.length) {
// If we actually have anything to add to our dropdown
// append it and highlight the first option
this.optionList.appendChild(optListFragment);
this._highlightOption();
this.choiceList.appendChild(optListFragment);
this._highlightChoice();
} else {
// Otherwise show a notice
const dropdownItem = this.isSearching ? this._getTemplate('notice', 'No results found') : this._getTemplate('notice', 'No options to select');
this.optionList.appendChild(dropdownItem);
this.choiceList.appendChild(dropdownItem);
}
}
}

View file

@ -1,6 +1,6 @@
const options = (state = [], action) => {
const choices = (state = [], action) => {
switch (action.type) {
case 'ADD_OPTION':
case 'ADD_CHOICE':
return [...state, {
id: action.id,
groupId: action.groupId,
@ -15,9 +15,9 @@ const options = (state = [], action) => {
case 'ADD_ITEM':
// When an item is added and it has an associated option,
// we want to disable it so it can't be chosen again
if(action.optionId > -1) {
if(action.choiceId > -1) {
return state.map((option) => {
if(option.id === parseInt(action.optionId)) {
if(option.id === parseInt(action.choiceId)) {
option.selected = true;
}
return option;
@ -29,9 +29,9 @@ const options = (state = [], action) => {
case 'REMOVE_ITEM':
// When an item is removed and it has an associated option,
// we want to re-enable it so it can be chosen again
if(action.optionId > -1) {
if(action.choiceId > -1) {
return state.map((option) => {
if(option.id === parseInt(action.optionId)) {
if(option.id === parseInt(action.choiceId)) {
option.selected = false;
}
return option;
@ -40,7 +40,7 @@ const options = (state = [], action) => {
return state;
}
case 'FILTER_OPTIONS':
case 'FILTER_CHOICES':
const filteredResults = action.results;
const filteredState = state.map((option, index) => {
// Set active state based on whether option is
@ -60,7 +60,7 @@ const options = (state = [], action) => {
return filteredState;
case 'ACTIVATE_OPTIONS':
case 'ACTIVATE_CHOICES':
return state.map((option) => {
option.active = action.active;
@ -73,4 +73,4 @@ const options = (state = [], action) => {
}
}
export default options;
export default choices;

View file

@ -1,12 +1,12 @@
import { combineReducers } from 'redux';
import items from './items';
import groups from './groups';
import options from './options';
import choices from './choices';
const appReducer = combineReducers({
items,
groups,
options
choices
});
const rootReducer = (state, action) => {

View file

@ -4,7 +4,7 @@ const items = (state = [], action) => {
// Add object to items array
let newState = [...state, {
id: action.id,
optionId: action.optionId,
choiceId: action.choiceId,
value: action.value,
label: action.label,
active: true,

View file

@ -75,35 +75,35 @@ export class Store {
}
/**
* Get options from store
* Get choices from store
* @return {Array} Option objects
*/
getOptions() {
getChoices() {
const state = this.store.getState();
return state.options;
return state.choices;
}
/**
* Get active options from store
* Get active choices from store
* @return {Array} Option objects
*/
getOptionsFilteredByActive() {
const options = this.getOptions();
const values = options.filter((option) => {
return option.active === true;
getChoicesFilteredByActive() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.active === true;
},[]);
return values;
}
/**
* Get selectable options from store
* Get selectable choices from store
* @return {Array} Option objects
*/
getOptionsFiltedBySelectable() {
const options = this.getOptions();
const values = options.filter((option) => {
return option.selected === false && option.disabled !== true;
getChoicesFiltedBySelectable() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.selected === false && choice.disabled !== true;
},[]);
return values;
@ -124,11 +124,11 @@ export class Store {
*/
getGroupsFilteredByActive() {
const groups = this.getGroups();
const options = this.getOptions();
const choices = this.getChoices();
const values = groups.filter((group) => {
const isActive = group.active === true && group.disabled === false;
const hasActiveOptions = options.some((option) => {
const hasActiveOptions = choices.some((option) => {
return option.active === true && option.disabled === false;
});
return isActive && hasActiveOptions ? true : false;

View file

@ -141,13 +141,13 @@
});
const choices2 = new Choices('#choices-2', {
allowPaste: false,
allowDuplicates: false,
paste: false,
duplicateItems: false,
editItems: true,
});
const choices3 = new Choices('#choices-3', {
allowDuplicates: false,
duplicates: false,
editItems: true,
regexFilter: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
});
@ -166,7 +166,7 @@
items: ['josh@joshuajohnson.co.uk', { value: 'joe@bloggs.co.uk', label: 'Joe Bloggs' } ],
});
const choices7 = new Choices('#choices-7', { allowSearch: false }).setValue(['Set value 1', 'Set value 2']);
const choices7 = new Choices('#choices-7', { Search: false }).setValue(['Set value 1', 'Set value 2']);
const choicesAjax = new Choices('#choices-12').ajax((callback) => {
fetch('https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')