Merge branch 'master' of github.com:jshjohnson/Choices into gh-pages

This commit is contained in:
Josh Johnson 2016-08-14 17:20:30 +01:00
commit b288261b86
8 changed files with 345 additions and 242 deletions

View file

@ -1,5 +1,5 @@
# Choices.js [![Build Status](https://travis-ci.org/jshjohnson/Choices.svg?branch=master)](https://travis-ci.org/jshjohnson/Choices)
A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
[Demo](https://joshuajohnson.co.uk/Choices/)
@ -48,6 +48,8 @@ A lightweight, configurable select box/text input plugin. Similar to Select2 and
prependValue: null,
appendValue: null,
loadingText: 'Loading...',
noResultsText: 'No results round',
noChoicesText: 'No choices to choose from',
classNames: {
containerOuter: 'choices',
containerInner: 'choices__inner',
@ -181,6 +183,13 @@ Pass an array of objects:
<strong>Usage:</strong> Whether a user can edit items. An items value can be edited by pressing the backspace.
### duplicateItems
<strong>Type:</strong> `Boolean` <strong>Default:</strong> `true`
<strong>Input types affected:</strong> `text`, `select-multiple`
<strong>Usage:</strong> Whether a user can input/choose a duplicate item.
### delimiter
<strong>Type:</strong> `String` <strong>Default:</strong> `,`
@ -188,13 +197,6 @@ Pass an array of objects:
<strong>Usage:</strong> What divides each value. By default the delimited value would be `"Value 1, Value 2, Value 3"`.
### duplicates
<strong>Type:</strong> `Boolean` <strong>Default:</strong> `true`
<strong>Input types affected:</strong> `text`
<strong>Usage:</strong> Whether a user can input a duplicate item.
### paste
<strong>Type:</strong> `Boolean` <strong>Default:</strong> `true`
@ -274,14 +276,28 @@ const example = new Choices(element, {
<strong>Input types affected:</strong> `text`, `select-one`, `select-multiple`
<strong>Usage:</strong> Append a value to each item added/selected
<strong>Usage:</strong> Append a value to each item added/selected.
### loadingText
<strong>Type:</strong> `String` <strong>Default:</strong> `Loading...`
<strong>Input types affected:</strong> `select-one`, `select-multiple`
<strong>Usage:</strong> The loading text that is shown when options are populated via an AJAX callback.
<strong>Usage:</strong> The text that is shown whilst choices are being populated via AJAX.
### noResultsText
<strong>Type:</strong> `String` <strong>Default:</strong> `No results round`
<strong>Input types affected:</strong> `select-one`, `select-multiple`
<strong>Usage:</strong> The text that is shown when a user's search has returned no results.
### noChoicesText
<strong>Type:</strong> `String` <strong>Default:</strong> `No choices to choose from`
<strong>Input types affected:</strong> `select-multiple`
<strong>Usage:</strong> The text that is shown when a user has selected all possible choices.
### classNames
<strong>Type:</strong> `Object` <strong>Default:</strong>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"version":3,"file":"choices.min.js","sources":[],"mappings":";;","sourceRoot":""}

View file

@ -45,6 +45,8 @@ export class Choices {
prependValue: null,
appendValue: null,
loadingText: 'Loading...',
noResultsText: 'No results round',
noChoicesText: 'No choices to choose from',
classNames: {
containerOuter: 'choices',
containerInner: 'choices__inner',
@ -407,7 +409,6 @@ export class Choices {
return this;
}
/**
* Get value(s) of input (i.e. inputted items (text) or selected choices (select))
* @param {Boolean} valueOnly Get only values of selected items, otherwise return selected items
@ -508,8 +509,8 @@ export class Choices {
/**
* Direct populate choices
* @param {Array} choices - Choices to insert
* @param {string} value - Name of 'value' property
* @param {string} label - Name of 'label' property
* @param {String} value - Name of 'value' property
* @param {String} label - Name of 'label' property
* @return {Object} Class instance
* @public
*/
@ -545,6 +546,19 @@ export class Choices {
return this;
}
/**
* Set value of input to blank
* @return {Object} Class instance
* @public
*/
clearInput() {
if (this.input.value) this.input.value = '';
if(this.passedElement.type !== 'select-one') {
this.input.style.width = getWidthOfInput(this.input);
}
return this;
}
/**
* Disable interaction with Choices
* @return {Object} Class instance
@ -596,13 +610,20 @@ export class Choices {
if(this.passedElement.type === 'select-one') {
const placeholderItem = this._getTemplate('item', { id: -1, value: 'Loading', label: this.config.loadingText, active: true});
this.itemList.appendChild(placeholderItem);
} else {
this.input.placeholder = this.config.loadingText;
}
const callback = (results, value, label) => {
if(!isType('Array', results) || !value) return;
if(results && results.length) {
// Remove loading states/text
this.containerOuter.classList.remove(this.config.classNames.loadingState);
const filter = this.config.sortFilter;
if(this.passedElement.type === 'select-multiple') {
this.input.placeholder = this.config.placeholderValue || this.passedElement.getAttribute('placeholder');
}
// Add each result as a choice
results.forEach((result, index) => {
// Select first choice in list if single select input
if(index === 0 && this.passedElement.type === 'select-one') {
@ -620,22 +641,9 @@ export class Choices {
return this;
}
/**
* Set value of input to blank
* @return {Object} Class instance
* @public
*/
clearInput() {
if (this.input.value) this.input.value = '';
if(this.passedElement.type !== 'select-one') {
this.input.style.width = getWidthOfInput(this.input);
}
return this;
}
/**
* Call change callback
* @param {string} value - last added/deleted/selected value
* @param {String} value - last added/deleted/selected value
* @return
* @private
*/
@ -659,48 +667,99 @@ export class Choices {
}
}
/**
* Process enter key event
* @param {Array} activeItems Items that are currently active
/**
* Process enter/click of an item button
* @param {Array} activeItems The currently active items
* @param {Element} element Button being interacted with
* @return
* @private
*/
_handleEnter(activeItems, value) {
let canUpdate = true;
_handleButtonAction(activeItems, element) {
if(!activeItems || !element) return;
// If we are clicking on a button
if(this.config.removeItems && this.config.removeItemButton) {
const itemId = element.parentNode.getAttribute('data-id');
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
if(this.config.addItems) {
if (this.config.maxItemCount && this.config.maxItemCount > 0 && this.config.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.config.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);
}
} else {
canUpdate = false;
// Remove item associated with button
this._removeItem(itemToRemove);
this._triggerChange(itemToRemove.value);
}
}
if (canUpdate) {
/**
* Process click of an item
* @param {Array} activeItems The currently active items
* @param {Element} element Item being interacted with
* @param {Boolean} hasShiftKey Whether the user has the shift key active
* @return
* @private
*/
_handleItemAction(activeItems, element, hasShiftKey = false) {
if(!activeItems || !element) return;
// If we are clicking on an item
if(this.config.removeItems && this.passedElement.type !== 'select-one') {
const passedId = element.getAttribute('data-id');
// We only want to select one item with a click
// so we deselect any items that aren't the target
// unless shift is being pressed
activeItems.forEach((item) => {
if(item.id === parseInt(passedId) && !item.highlighted) {
this.highlightItem(item);
} else if(!hasShiftKey) {
if(item.highlighted) {
this.unhighlightItem(item);
}
}
});
// Focus input as without focus, a user cannot do anything with a
// highlighted item
if(document.activeElement !== this.input) this.input.focus();
}
}
/**
* Process click of a choice
* @param {Array} activeItems The currently active items
* @param {Element} element Choice being interacted with
* @return {[type]} [description]
*/
_handleChoiceAction(activeItems, element) {
if(!activeItems || !element) return;
// If we are clicking on an option
const id = element.getAttribute('data-id');
const choice = this.store.getChoiceById(id);
if(choice && !choice.selected && !choice.disabled) {
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
let canAddItem = true;
// If a user has supplied a regular expression filter
if(this.config.regexFilter) {
// Determine whether we can update based on whether
// our regular expression passes
canAddItem = this._regexFilter(value);
if(this.config.maxItemCount > 0 && this.config.maxItemCount <= activeItems.length && this.passedElement.type === 'select-multiple') {
canAddItem = false;
}
// All is good, add
if(canAddItem) {
this.toggleDropdown();
this._addItem(value);
this._triggerChange(value);
this.clearInput(this.passedElement);
this._addItem(choice.value, choice.label, choice.id);
this._triggerChange(choice.value);
}
if(this.passedElement.type === 'select-one') {
if(this.canSearch) {
this.input.value = "";
}
this.isSearching = false;
this.store.dispatch(activateChoices(true));
this.hideDropdown();
}
}
};
}
/**
* Process back space event
@ -726,6 +785,50 @@ export class Choices {
}
};
/**
* Validates whether an item can be added by a user
* @param {Array} activeItems The currently active items
* @param {String} value Value of item to add
* @return {Object} Response: Whether user can add item
* Notice: Notice show in dropdown
*/
_canAddItem(activeItems, value) {
let canAddItem = true;
let notice = `Press Enter to add "${ value }"`;
if(this.passedElement.type === 'select-multiple' || this.passedElement.type === 'text') {
if (this.config.maxItemCount > 0 && this.config.maxItemCount <= this.itemList.children.length) {
// If there is a max entry limit and we have reached that limit
// don't update
canAddItem = false;
notice = `Only ${ this.config.maxItemCount } values can be added.`;
}
}
if(this.passedElement.type === 'text' && this.config.addItems) {
const isUnique = !activeItems.some((item) => item.value === value);
// If a user has supplied a regular expression filter
if(this.config.regexFilter) {
// Determine whether we can update based on whether
// our regular expression passes
canAddItem = this._regexFilter(value);
}
// If no duplicates are allowed, and the value already exists
// in the array
if(this.config.duplicateItems === false && !isUnique) {
canAddItem = false;
notice = `Only unique values can be added.`;
}
}
return {
response: canAddItem,
notice: notice,
};
}
/**
* Filter choices based on search value
* @param {String} value Value to filter by
@ -747,14 +850,13 @@ export class Choices {
if(newValue.length >= 1 && newValue !== currentValue + ' ') {
const haystack = this.store.getChoicesFilteredBySelectable();
const needle = newValue;
const keys = isType('Array', this.config.sortFields) ? this.config.sortFields : [this.config.sortFields];
const fuse = new Fuse(haystack, {
const keys = isType('Array', this.config.sortFields) ? this.config.sortFields : [this.config.sortFields];
const fuse = new Fuse(haystack, {
keys: keys,
shouldSort: true,
include: 'score',
});
const results = fuse.search(needle);
const results = fuse.search(needle);
this.currentValue = newValue;
this.highlightPosition = 0;
@ -772,6 +874,56 @@ export class Choices {
}
}
/**
* Trigger event listeners
* @return
* @private
*/
_addEventListeners() {
document.addEventListener('keyup', this._onKeyUp);
document.addEventListener('keydown', this._onKeyDown);
document.addEventListener('click', this._onClick);
document.addEventListener('touchmove', this._onTouchMove);
document.addEventListener('touchend', this._onTouchEnd);
document.addEventListener('mousedown', this._onMouseDown);
document.addEventListener('mouseover', this._onMouseOver);
if(this.passedElement.type && this.passedElement.type === 'select-one') {
this.containerOuter.addEventListener('focus', this._onFocus);
this.containerOuter.addEventListener('blur', this._onBlur);
}
this.input.addEventListener('input', this._onInput);
this.input.addEventListener('paste', this._onPaste);
this.input.addEventListener('focus', this._onFocus);
this.input.addEventListener('blur', this._onBlur);
}
/**
* Destroy event listeners
* @return
* @private
*/
_removeEventListeners() {
document.removeEventListener('keyup', this._onKeyUp);
document.removeEventListener('keydown', this._onKeyDown);
document.removeEventListener('click', this._onClick);
document.removeEventListener('touchmove', this._onTouchMove);
document.removeEventListener('touchend', this._onTouchEnd);
document.removeEventListener('mousedown', this._onMouseDown);
document.removeEventListener('mouseover', this._onMouseOver);
if(this.passedElement.type && this.passedElement.type === 'select-one') {
this.containerOuter.removeEventListener('focus', this._onFocus);
this.containerOuter.removeEventListener('blur', this._onBlur);
}
this.input.removeEventListener('input', this._onInput);
this.input.removeEventListener('paste', this._onPaste);
this.input.removeEventListener('focus', this._onFocus);
this.input.removeEventListener('blur', this._onBlur);
}
/**
* Key down event
* @param {Object} e Event
@ -820,44 +972,37 @@ export class Choices {
case enterKey:
// If enter key is pressed and the input has a value
if(target.value && this.passedElement.type === 'text') {
if(this.passedElement.type === 'text' && target.value) {
const value = this.input.value;
this._handleEnter(activeItems, value);
}
const canAddItem = this._canAddItem(activeItems, value);
// Show dropdown if focus
if(!hasActiveDropdown && this.passedElement.type === 'select-one') {
e.preventDefault();
this.showDropdown();
if(this.canSearch) {
this.input.focus();
// All is good, add
if(canAddItem.response) {
this.toggleDropdown();
this._addItem(value);
this._triggerChange(value);
this.clearInput(this.passedElement);
}
}
if(target.hasAttribute('data-button')) {
// If we are clicking on a button
if(this.config.removeItems && this.config.removeItemButton) {
const itemId = target.parentNode.getAttribute('data-id');
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
// Remove item associated with button
this._removeItem(itemToRemove);
this._triggerChange(itemToRemove.value);
e.preventDefault();
}
this._handleButtonAction(activeItems, target);
}
if(hasActiveDropdown) {
const highlighted = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
if(highlighted) {
const value = highlighted.getAttribute('data-value');
const label = highlighted.innerHTML;
const id = highlighted.getAttribute('data-id');
this._addItem(value, label, id);
this._triggerChange(value);
this.clearInput(this.passedElement);
const value = highlighted.getAttribute('data-value');
const label = highlighted.innerHTML;
const id = highlighted.getAttribute('data-id');
const canAddItem = this._canAddItem(activeItems, value);
if(canAddItem.response) {
this._addItem(value, label, id);
this._triggerChange(value);
this.clearInput(this.passedElement);
}
if(this.passedElement.type === 'select-one') {
this.isSearching = false;
@ -865,6 +1010,13 @@ export class Choices {
this.toggleDropdown();
}
}
} else if(this.passedElement.type === 'select-one') {
// Show dropdown if focus
e.preventDefault();
this.showDropdown();
if(this.canSearch) {
this.input.focus();
}
}
break;
@ -941,34 +1093,25 @@ export class Choices {
// notice. Otherwise hide the dropdown
if(this.passedElement.type === 'text') {
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
let dropdownItem;
if(this.input.value) {
const activeItems = this.store.getItemsFilteredByActive();
const isUnique = !activeItems.some((item) => item.value === this.input.value);
let canAddItem = true;
const value = this.input.value;
// If a user has supplied a regular expression filter
if(this.config.regexFilter) {
// Determine whether we can update based on whether
// our regular expression passes
canAddItem = this._regexFilter(this.input.value);
}
if(this.config.maxItemCount && this.config.maxItemCount > 0 && this.config.maxItemCount <= this.itemList.children.length) {
dropdownItem = this._getTemplate('notice', `Only ${ this.config.maxItemCount } values can be added.`);
} else if(!this.config.duplicateItems && !isUnique) {
dropdownItem = this._getTemplate('notice', `Only unique values can be added.`);
} else if(canAddItem) {
dropdownItem = this._getTemplate('notice', `Press Enter to add "${ this.input.value }"`);
}
if(canAddItem !== false) {
let dropdownItem;
if(value) {
const activeItems = this.store.getItemsFilteredByActive();
const canAddItem = this._canAddItem(activeItems, value);
if(canAddItem.notice) {
const dropdownItem = this._getTemplate('notice', canAddItem.notice);
this.dropdown.innerHTML = dropdownItem.outerHTML;
if(!this.dropdown.classList.contains(this.config.classNames.activeState)) {
}
if(canAddItem.response === true) {
if(!hasActiveDropdown) {
this.showDropdown();
}
} else {
if(hasActiveDropdown) {
if(!canAddItem.notice && hasActiveDropdown) {
this.hideDropdown();
}
}
@ -977,6 +1120,7 @@ export class Choices {
this.hideDropdown();
}
}
} else {
const backKey = 46;
const deleteKey = 8;
@ -994,6 +1138,7 @@ export class Choices {
this._searchChoices(this.input.value);
}
}
}
}
@ -1078,63 +1223,21 @@ export class Choices {
if(this.containerOuter.contains(target) && target !== this.input) {
const activeItems = this.store.getItemsFilteredByActive();
const hasShiftKey = e.shiftKey ? true : false;
// Prevent input mouse down triggering focus event
if(target !== this.input) {
e.preventDefault();
}
if(target !== this.input) e.preventDefault();
if(target.hasAttribute('data-button')) {
// If we are clicking on a button
if(this.config.removeItems && this.config.removeItemButton) {
const itemId = target.parentNode.getAttribute('data-id');
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
// Remove item associated with button
this._removeItem(itemToRemove);
this._triggerChange(itemToRemove.value);
}
this._handleButtonAction(activeItems, target);
} else if(target.hasAttribute('data-item')) {
// If we are clicking on an item
if(this.config.removeItems && this.passedElement.type !== 'select-one') {
const passedId = target.getAttribute('data-id');
const hasShiftKey = e.shiftKey ? true : false;
// We only want to select one item with a click
// so we deselect any items that aren't the target
// unless shift is being pressed
activeItems.forEach((item) => {
if(item.id === parseInt(passedId) && !item.highlighted) {
this.highlightItem(item);
} else if(!hasShiftKey) {
if(item.highlighted) {
this.unhighlightItem(item);
}
}
});
}
this._handleItemAction(activeItems, target, hasShiftKey);
} else if(target.hasAttribute('data-choice')) {
// If we are clicking on an option
const id = target.getAttribute('data-id');
const choice = this.store.getChoiceById(id);
if(choice && !choice.selected && !choice.disabled) {
this._addItem(choice.value, choice.label, choice.id);
this._triggerChange(choice.value);
if(this.passedElement.type === 'select-one') {
if(this.canSearch) {
this.input.value = "";
}
this.isSearching = false;
this.store.dispatch(activateChoices(true));
this.hideDropdown();
}
}
this._handleChoiceAction(activeItems, target);
}
}
}
/**
* Click event
* @param {Object} e Event
@ -1163,7 +1266,7 @@ export class Choices {
}
}
} else {
if(this.passedElement.type === 'select-one' && hasActiveDropdown) {
if(this.passedElement.type === 'select-one') {
if(target !== this.input) {
this.hideDropdown();
}
@ -1172,7 +1275,7 @@ export class Choices {
} else {
// Click is outside of our element so close dropdown and de-select items
const activeItems = this.store.getItemsFilteredByActive();
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
@ -1218,7 +1321,6 @@ export class Choices {
}
}
/**
* Focus event
* @param {Object} e Event
@ -1260,10 +1362,12 @@ export class Choices {
* @private
*/
_onBlur(e) {
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
// If the blurred element is this input or the outer container
if(e.target === this.input || (e.target === this.containerOuter && this.passedElement.type === 'select-one')) {
const activeItems = this.store.getItemsFilteredByActive();
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
// Remove the focus state
this.containerOuter.classList.remove(this.config.classNames.focusState);
@ -1272,10 +1376,14 @@ export class Choices {
if(hasActiveDropdown && (e.target === this.input || e.target === this.containerOuter && !this.canSearch)) {
this.hideDropdown();
}
// De-select any highlighted items
if(hasHighlightedItems) {
this.unhighlightAll();
}
}
}
/**
* Tests value against a regular expression
* @param {string} value Value to test
@ -1388,7 +1496,6 @@ export class Choices {
}
}
/**
* Add item to store with correct value
* @param {String} value Value to add to store
@ -1877,7 +1984,7 @@ export class Choices {
this._highlightChoice();
} else {
// Otherwise show a notice
const dropdownItem = this.isSearching ? this._getTemplate('notice', 'No results found') : this._getTemplate('notice', 'No choices to choose from');
const dropdownItem = this.isSearching ? this._getTemplate('notice', this.config.noResultsText) : this._getTemplate('notice', this.config.noChoicesText);
this.choiceList.appendChild(dropdownItem);
}
}
@ -1904,56 +2011,6 @@ export class Choices {
this.prevState = this.currentState;
}
}
/**
* Trigger event listeners
* @return
* @private
*/
_addEventListeners() {
document.addEventListener('keyup', this._onKeyUp);
document.addEventListener('keydown', this._onKeyDown);
document.addEventListener('click', this._onClick);
document.addEventListener('touchmove', this._onTouchMove);
document.addEventListener('touchend', this._onTouchEnd);
document.addEventListener('mousedown', this._onMouseDown);
document.addEventListener('mouseover', this._onMouseOver);
if(this.passedElement.type && this.passedElement.type === 'select-one') {
this.containerOuter.addEventListener('focus', this._onFocus);
this.containerOuter.addEventListener('blur', this._onBlur);
}
this.input.addEventListener('input', this._onInput);
this.input.addEventListener('paste', this._onPaste);
this.input.addEventListener('focus', this._onFocus);
this.input.addEventListener('blur', this._onBlur);
}
/**
* Destroy event listeners
* @return
* @private
*/
_removeEventListeners() {
document.removeEventListener('keyup', this._onKeyUp);
document.removeEventListener('keydown', this._onKeyDown);
document.removeEventListener('click', this._onClick);
document.removeEventListener('touchmove', this._onTouchMove);
document.removeEventListener('touchend', this._onTouchEnd);
document.removeEventListener('mousedown', this._onMouseDown);
document.removeEventListener('mouseover', this._onMouseOver);
if(this.passedElement.type && this.passedElement.type === 'select-one') {
this.containerOuter.removeEventListener('focus', this._onFocus);
this.containerOuter.removeEventListener('blur', this._onBlur);
}
this.input.removeEventListener('input', this._onInput);
this.input.removeEventListener('paste', this._onPaste);
this.input.removeEventListener('focus', this._onFocus);
this.input.removeEventListener('blur', this._onBlur);
}
};
window.Choices = module.exports = Choices;

View file

@ -58,13 +58,6 @@
<option value="Dropdown item 3" selected>Dropdown item 3</option>
<option value="Dropdown item 4" disabled>Dropdown item 4</option>
</select>
<label for="choices-8">With pre-selected option</label>
<select class="form-control" data-choice name="choices-8" id="choices-8" placeholder="Choose an option" multiple>
<option value="Dropdown item 1">Dropdown item 1</option>
<option value="Dropdown item 2" selected>Dropdown item 2</option>
<option value="Dropdown item 3">Dropdown item 3</option>
</select>
<label for="choices-9">Option groups</label>
<select class="form-control" data-choice name="choices-9" id="choices-9" placeholder="This is a placeholder" multiple>
@ -100,8 +93,8 @@
</optgroup>
</select>
<label for="choices-10">Options from remote source (Fetch API)</label>
<select class="form-control" name="choices-10" id="choices-10" placeholder="Pick an Arctic Monkeys record" multiple></select>
<label for="choices-10">Options from remote source (Fetch API) &amp; limited to 5</label>
<select class="form-control" name="choices-10" id="choices-10" multiple></select>
<hr>
@ -199,8 +192,6 @@
},
});
console.log(example1.getValue());
var example2 = new Choices('#choices-2', {
paste: false,
duplicateItems: false,
@ -208,7 +199,6 @@
});
var example3 = new Choices('#choices-3', {
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,}))$/,
});
@ -227,9 +217,10 @@
items: ['josh@joshuajohnson.co.uk', { value: 'joe@bloggs.co.uk', label: 'Joe Bloggs' } ],
});
var example8 = new Choices('#choices-10', {
var example9 = new Choices('#choices-10', {
placeholder: true,
placeholderValue: 'Pick an Strokes record',
maxItemCount: 5,
callbackOnChange: function(value, passedInput) { console.log(value) }
}).ajax(function(callback) {
fetch('https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
@ -243,7 +234,7 @@
});
});
var example9 = new Choices('#choices-12', {
var example10 = new Choices('#choices-12', {
placeholder: true,
placeholderValue: 'Pick an Arctic Monkeys record'
}).ajax(function(callback) {
@ -251,7 +242,7 @@
.then(function(response) {
response.json().then(function(data) {
callback(data.releases, 'title', 'title');
example9.setValueByChoice('Fake Tales Of San Francisco');
example10.setValueByChoice('Fake Tales Of San Francisco');
});
})
.catch(function(error) {
@ -259,7 +250,7 @@
});
});
var example10 = new Choices('#choices-14', {
var example11 = new Choices('#choices-14', {
removeItemButton: true,
}).ajax(function(callback) {
var request = new XMLHttpRequest();
@ -272,7 +263,7 @@
if (status == 200) {
data = JSON.parse(request.responseText);
callback(data.releases, 'title', 'title');
example10.setValueByChoice('How Soon Is Now?');
example11.setValueByChoice('How Soon Is Now?');
} else {
console.error(status);
}
@ -281,12 +272,12 @@
request.send();
});
var example11 = new Choices('[data-choice]', {
var example12 = new Choices('[data-choice]', {
placeholderValue: 'This is a placeholder set in the config',
removeButton: true,
});
var example12 = new Choices('#choices-15', {
var example13 = new Choices('#choices-15', {
search: false,
choices: [
{value: 'One', label: 'Label One'},
@ -299,7 +290,7 @@
{value: 'Six', label: 'Label Six', selected: true},
], 'value', 'label');
var example13 = new Choices('#choices-16', {
var example14 = new Choices('#choices-16', {
placeholder: true,
}).setChoices([{
label: 'Group one',
@ -322,7 +313,7 @@
]
}], 'value', 'label');
var example14 = new Choices('#choices-17', {
var example15 = new Choices('#choices-17', {
choices: [
{value: 'One', label: 'Label One'},
{value: 'Two', label: 'Label Two', disabled: true},

View file

@ -1,6 +1,6 @@
{
"name": "choices.js",
"version": "1.1.0",
"version": "1.1.1",
"description": "A vanilla JS customisable text input/select box plugin",
"main": "./assets/scripts/dist/choices.min.js",
"scripts": {
@ -48,7 +48,8 @@
"postcss-cli": "^2.5.1",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1",
"whatwg-fetch": "^1.0.0"
"whatwg-fetch": "^1.0.0",
"wrapper-webpack-plugin": "^0.1.7"
},
"dependencies": {
"redux": "^3.3.1"

View file

@ -242,6 +242,35 @@ describe('Choices', function() {
expect(this.choices.config.callbackOnChange).toHaveBeenCalledWith(jasmine.any(String), jasmine.any(HTMLElement));
});
it('should open the dropdown on click', function() {
const container = this.choices.containerOuter;
this.choices._onClick({
target: container,
ctrlKey: false,
preventDefault: () => {}
});
expect(document.activeElement === this.choices.input && container.classList.contains('is-open')).toBe(true);
});
it('should open the dropdown on click', function() {
const container = this.choices.containerOuter;
this.choices._onClick({
target: container,
ctrlKey: false,
preventDefault: () => {}
});
this.choices._onClick({
target: container,
ctrlKey: false,
preventDefault: () => {}
});
expect(document.activeElement === this.choices.input && container.classList.contains('is-open')).toBe(false);
});
it('should filter choices when searching', function() {
this.choices.input.focus();
this.choices.input.value = 'Value 3';

View file

@ -1,5 +1,8 @@
var path = require('path');
var package = require('./package.json');
var webpack = require('webpack');
var wrapperPlugin = require('wrapper-webpack-plugin');
var banner = `/*! ${ package.name } v${ package.version } | (c) ${ new Date().getFullYear() } ${ package.author } | ${ package.homepage } */ \n`
module.exports = {
devtool: 'cheap-module-source-map',
@ -29,6 +32,9 @@ module.exports = {
'NODE_ENV': JSON.stringify('production'),
}
}),
new wrapperPlugin({
header: banner,
}),
],
module: {
loaders: [{