Style choices/items that are assigned as placeholders

This commit is contained in:
Josh Johnson 2017-01-23 13:54:26 +00:00
commit bda230ef46
14 changed files with 776 additions and 560 deletions

128
README.md
View file

@ -15,14 +15,17 @@ A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input
## Installation
With [NPM](https://www.npmjs.com/package/choices.js):
```bash
```zsh
npm install choices.js --save
```
With [Bower](https://bower.io/):
```bash
```zsh
bower install choices.js --save
```
Or include Choices directly:
```html
<!-- Include base CSS (optional) -->
<link rel="stylesheet" href="assets/styles/css/base.min.css">
@ -64,7 +67,7 @@ Or include Choices directly:
flip: true,
regexFilter: null,
shouldSort: true,
sortFilter: sortByAlpha,
sortFilter: () => {...},
sortFields: ['label', 'value'],
placeholderValue: null,
prependValue: null,
@ -104,14 +107,13 @@ Or include Choices directly:
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
fuseOptions: {
include: 'score',
},
callbackOnInit: null,
callbackOnAddItem: null,
callbackOnRemoveItem: null,
callbackOnHighlightItem: null,
callbackOnUnhighlightItem: null,
callbackOnCreateTemplates: null,
callbackOnChange: null,
callbackOnSearch: null,
});
```
@ -293,7 +295,7 @@ const example = new Choices(element, {
### placeholderValue
**Type:** `String` **Default:** `null`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Input types affected:** `text`, `select-multiple`
**Usage:** The value of the inputs placeholder.
@ -398,44 +400,6 @@ classNames: {
**Usage:** Function to run once Choices initialises.
### callbackOnAddItem
**Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Function to run each time an item is added (programmatically or by the user).
**Example:**
```js
const example = new Choices(element, {
callbackOnAddItem: (id, value, groupValue) => {
// do something creative here...
},
};
```
### callbackOnRemoveItem
**Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Function to run each time an item is removed (programmatically or by the user).
### callbackOnHighlightItem
**Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-multiple`
**Usage:** Function to run each time an item is highlighted.
### callbackOnUnhighlightItem
**Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-multiple`
**Usage:** Function to run each time an item is unhighlighted.
### callbackOnCreateTemplates
**Type:** `Function` **Default:** `null` **Arguments:** `template`
@ -469,19 +433,73 @@ const example = new Choices(element, {
});
```
### callbackOnChange
**Type:** `Function` **Default:** `null` **Arguments:** `value`
## Events
**Note:** Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object.
**Example:**
```js
const element = document.getElementById('example');
const example = new Choices(element);
element.addEventListener('addItem', function(event) {
// do something creative here...
console.log(event.detail.id);
console.log(event.detail.value);
console.log(event.detail.groupValue);
}, false);
// or
const example = new Choices(document.getElementById('example'));
example.passedElement.addEventListener('addItem', function(event) {
// do something creative here...
console.log(event.detail.id);
console.log(event.detail.value);
console.log(event.detail.groupValue);
}, false);
```
### addItem
**Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Function to run each time an item is added/removed by a user.
**Usage:** Triggered each time an item is added (programmatically or by the user).
### callbackOnSearch
**Type:** `Function` **Default:** `null` **Arguments:** `value`
### removeItem
**Arguments:** `id, value, groupValue`
**Input types affected:** `select-one`, `select-multiple`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Function to run when a user types into an input to search choices.
**Usage:** Triggered each time an item is removed (programmatically or by the user).
### highlightItem
**Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-multiple`
**Usage:** Triggered each time an item is highlighted.
### unhighlightItem
**Arguments:** `id, value, groupValue`
**Input types affected:** `text`, `select-multiple`
**Usage:** Triggered each time an item is unhighlighted.
### change
**Arguments:** `value`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Triggered each time an item is added/removed **by a user**.
### search
**Arguments:** `value` **Input types affected:** `select-one`, `select-multiple`
**Usage:** Triggered when a user types into an input to search choices.
## Methods
Methods can be called either directly or by chaining:

View file

@ -1,4 +1,4 @@
/*! choices.js v2.4.1 | (c) 2016 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
/*! choices.js v2.6.1 | (c) 2017 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
@ -171,14 +171,11 @@ return /******/ (function(modules) { // webpackBootstrap
flippedState: 'is-flipped',
loadingState: 'is-loading'
},
fuseOptions: {
include: 'score'
},
callbackOnInit: null,
callbackOnAddItem: null,
callbackOnRemoveItem: null,
callbackOnHighlightItem: null,
callbackOnUnhighlightItem: null,
callbackOnCreateTemplates: null,
callbackOnChange: null,
callbackOnSearch: null
callbackOnCreateTemplates: null
};
// Merge options with user options
@ -296,8 +293,6 @@ return /******/ (function(modules) { // webpackBootstrap
if (callback) {
if ((0, _utils.isType)('Function', callback)) {
callback.call(this);
} else {
console.error('callbackOnInit: Callback is not a function');
}
}
}
@ -545,23 +540,27 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: 'highlightItem',
value: function highlightItem(item) {
var runEvent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (!item) return;
var id = item.id;
var groupId = item.groupId;
var callback = this.config.callbackOnHighlightItem;
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch((0, _index3.highlightItem)(id, true));
// Run callback if it is a function
if (callback) {
if ((0, _utils.isType)('Function', callback)) {
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if (group && group.value) {
callback.call(this, id, item.value, group.value);
} else {
callback.call(this, id, item.value);
}
if (runEvent) {
if (group && group.value) {
(0, _utils.triggerEvent)(this.passedElement, 'highlightItem', {
id: id,
value: item.value,
groupValue: group.value
});
} else {
console.error('callbackOnHighlightItem: Callback is not a function');
(0, _utils.triggerEvent)(this.passedElement, 'highlightItem', {
id: id,
value: item.value
});
}
}
@ -581,22 +580,21 @@ return /******/ (function(modules) { // webpackBootstrap
if (!item) return;
var id = item.id;
var groupId = item.groupId;
var callback = this.config.callbackOnUnhighlightItem;
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch((0, _index3.highlightItem)(id, false));
// Run callback if it is a function
if (callback) {
if ((0, _utils.isType)('Function', callback)) {
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if (group && group.value) {
callback.call(this, id, item.value, group.value);
} else {
callback.call(this, id, item.value);
}
} else {
console.error('callbackOnUnhighlightItem: Callback is not a function');
}
if (group && group.value) {
(0, _utils.triggerEvent)(this.passedElement, 'unhighlightItem', {
id: id,
value: item.value,
groupValue: group.value
});
} else {
(0, _utils.triggerEvent)(this.passedElement, 'unhighlightItem', {
id: id,
value: item.value
});
}
return this;
@ -704,7 +702,7 @@ return /******/ (function(modules) { // webpackBootstrap
value: function removeHighlightedItems() {
var _this9 = this;
var runCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var runEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var items = this.store.getItemsFilteredByActive();
@ -712,8 +710,8 @@ return /******/ (function(modules) { // webpackBootstrap
if (item.highlighted && item.active) {
_this9._removeItem(item);
// If this action was performed by the user
// run the callback
if (runCallback) {
// trigger the event
if (runEvent) {
_this9._triggerChange(item.value);
}
}
@ -1078,16 +1076,10 @@ return /******/ (function(modules) { // webpackBootstrap
key: '_triggerChange',
value: function _triggerChange(value) {
if (!value) return;
var callback = this.config.callbackOnChange;
// Run callback if it is a function
if (callback) {
if ((0, _utils.isType)('Function', callback)) {
callback.call(this, value);
} else {
console.error('callbackOnChange: Callback is not a function');
}
}
(0, _utils.triggerEvent)(this.passedElement, 'change', {
value: value
});
}
/**
@ -1231,7 +1223,7 @@ return /******/ (function(modules) { // webpackBootstrap
this._triggerChange(lastItem.value);
} else {
if (!hasHighlightedItems) {
this.highlightItem(lastItem);
this.highlightItem(lastItem, false);
}
this.removeHighlightedItems(true);
}
@ -1376,12 +1368,10 @@ return /******/ (function(modules) { // webpackBootstrap
var haystack = this.store.getChoicesFilteredBySelectable();
var needle = newValue;
var keys = (0, _utils.isType)('Array', this.config.sortFields) ? this.config.sortFields : [this.config.sortFields];
var fuse = new _fuse2.default(haystack, {
keys: keys,
shouldSort: true,
include: 'score'
});
var options = Object.assign(this.config.fuseOptions, { keys: keys });
var fuse = new _fuse2.default(haystack, options);
var results = fuse.search(needle);
this.currentValue = newValue;
this.highlightPosition = 0;
this.isSearching = true;
@ -1404,7 +1394,6 @@ return /******/ (function(modules) { // webpackBootstrap
var hasUnactiveChoices = choices.some(function (option) {
return option.active !== true;
});
var callback = this.config.callbackOnSearch;
// Run callback if it is a function
if (this.input === document.activeElement) {
@ -1412,14 +1401,10 @@ return /******/ (function(modules) { // webpackBootstrap
if (value && value.length > this.config.searchFloor) {
// Filter available choices
this._searchChoices(value);
// Run callback if it is a function
if (callback) {
if ((0, _utils.isType)('Function', callback)) {
callback.call(this, value);
} else {
console.error('callbackOnSearch: Callback is not a function');
}
}
// Trigger search event
(0, _utils.triggerEvent)(this.passedElement, 'search', {
value: value
});
} else if (hasUnactiveChoices) {
// Otherwise reset choices to active
this.isSearching = false;
@ -1578,6 +1563,7 @@ return /******/ (function(modules) { // webpackBootstrap
}
if (hasActiveDropdown) {
e.preventDefault();
var highlighted = _this17.dropdown.querySelector('.' + _this17.config.classNames.highlightedState);
// If we have a highlighted choice
@ -2149,7 +2135,12 @@ return /******/ (function(modules) { // webpackBootstrap
var items = this.store.getItems();
var passedLabel = label || passedValue;
var passedOptionId = parseInt(choiceId, 10) || -1;
var callback = this.config.callbackOnAddItem;
// Get group if group ID passed
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
// Generate unique id
var id = items ? items.length + 1 : 1;
// If a prepended value has been passed, prepend it
if (this.config.prependValue) {
@ -2161,27 +2152,24 @@ return /******/ (function(modules) { // webpackBootstrap
passedValue += this.config.appendValue.toString();
}
// Generate unique id
var id = items ? items.length + 1 : 1;
this.store.dispatch((0, _index3.addItem)(passedValue, passedLabel, id, passedOptionId, groupId));
if (this.passedElement.type === 'select-one') {
this.removeActiveItems(id);
}
// Run callback if it is a function
if (callback) {
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if ((0, _utils.isType)('Function', callback)) {
if (group && group.value) {
callback.call(this, id, passedValue, group.value);
} else {
callback.call(this, id, passedValue);
}
} else {
console.error('callbackOnAddItem: Callback is not a function');
}
// Trigger change event
if (group && group.value) {
(0, _utils.triggerEvent)(this.passedElement, 'addItem', {
id: id,
value: passedValue,
groupValue: group.value
});
} else {
(0, _utils.triggerEvent)(this.passedElement, 'addItem', {
id: id,
value: passedValue
});
}
return this;
@ -2207,22 +2195,21 @@ return /******/ (function(modules) { // webpackBootstrap
var value = item.value;
var choiceId = item.choiceId;
var groupId = item.groupId;
var callback = this.config.callbackOnRemoveItem;
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch((0, _index3.removeItem)(id, choiceId));
// Run callback
if (callback) {
if ((0, _utils.isType)('Function', callback)) {
var group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if (group && group.value) {
callback.call(this, id, value, group.value);
} else {
callback.call(this, id, value);
}
} else {
console.error('callbackOnRemoveItem: Callback is not a function');
}
if (group && group.value) {
(0, _utils.triggerEvent)(this.passedElement, 'removeItem', {
id: id,
value: value,
groupValue: group.value
});
} else {
(0, _utils.triggerEvent)(this.passedElement, 'removeItem', {
id: id,
value: value
});
}
return this;
@ -2244,7 +2231,7 @@ return /******/ (function(modules) { // webpackBootstrap
value: function _addChoice(isSelected, isDisabled, value, label) {
var groupId = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : -1;
if (!value) return;
if (typeof value === 'undefined' || value === null) return;
// Generate unique id
var choices = this.store.getChoices();
@ -2394,6 +2381,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (callbackTemplate && (0, _utils.isType)('Function', callbackTemplate)) {
userTemplates = callbackTemplate.call(this, _utils.strToEl);
}
this.config.templates = (0, _utils.extend)(templates, userTemplates);
}
@ -5300,6 +5288,14 @@ return /******/ (function(modules) { // webpackBootstrap
return width + "px";
};
/**
* Sorting function for current and previous string
* @param {String} a Current value
* @param {String} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
var sortByAlpha = exports.sortByAlpha = function sortByAlpha(a, b) {
var labelA = (a.label || a.value).toLowerCase();
var labelB = (b.label || b.value).toLowerCase();
@ -5309,10 +5305,37 @@ return /******/ (function(modules) { // webpackBootstrap
return 0;
};
/**
* Sort by numeric score
* @param {Object} a Current value
* @param {Object} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
var sortByScore = exports.sortByScore = function sortByScore(a, b) {
return a.score - b.score;
};
/**
* Trigger native event
* @param {NodeElement} element Element to trigger event on
* @param {String} type Type of event to trigger
* @param {Object} customArgs Data to pass with event
* @return {Object} Triggered event
*/
var triggerEvent = exports.triggerEvent = function triggerEvent(element, type) {
var customArgs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var event = new CustomEvent(type, {
detail: customArgs,
bubbles: true,
cancelable: true
});
return element.dispatchEvent(event);
};
/***/ },
/* 25 */
/***/ function(module, exports) {
@ -5320,118 +5343,135 @@ return /******/ (function(modules) { // webpackBootstrap
'use strict';
/* eslint-disable */
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = function () {
var toStr = Object.prototype.toString;
(function () {
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = function () {
var toStr = Object.prototype.toString;
var isCallable = function isCallable(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var isCallable = function isCallable(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function toInteger(value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var toInteger = function toInteger(value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function toLength(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
var toLength = function toLength(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}();
}
// Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Array.prototype.find = function (predicate) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
return undefined;
};
}();
}
}
// Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Array.prototype.find = function (predicate) {
'use strict';
function CustomEvent(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
CustomEvent.prototype = window.Event.prototype;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
window.CustomEvent = CustomEvent;
})();
/***/ }
/******/ ])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
export const addItem = (value, label, id, choiceId, groupId) => {
export const addItem = (value, label, id, choiceId, groupId, placeholder) => {
return {
type: 'ADD_ITEM',
value,
@ -6,6 +6,7 @@ export const addItem = (value, label, id, choiceId, groupId) => {
id,
choiceId,
groupId,
placeholder
};
};
@ -25,7 +26,7 @@ export const highlightItem = (id, highlighted) => {
};
};
export const addChoice = (value, label, id, groupId, disabled) => {
export const addChoice = (value, label, id, groupId, disabled, placeholder) => {
return {
type: 'ADD_CHOICE',
value,
@ -33,6 +34,7 @@ export const addChoice = (value, label, id, groupId, disabled) => {
id,
groupId,
disabled,
placeholder
};
};

View file

@ -23,6 +23,7 @@ import {
getWidthOfInput,
sortByAlpha,
sortByScore,
triggerEvent,
}
from './lib/utils.js';
import './lib/polyfills.js';
@ -104,14 +105,11 @@ class Choices {
flippedState: 'is-flipped',
loadingState: 'is-loading',
},
fuseOptions: {
include: 'score',
},
callbackOnInit: null,
callbackOnAddItem: null,
callbackOnRemoveItem: null,
callbackOnHighlightItem: null,
callbackOnUnhighlightItem: null,
callbackOnCreateTemplates: null,
callbackOnChange: null,
callbackOnSearch: null,
};
// Merge options with user options
@ -133,9 +131,11 @@ class Choices {
this.isTextElement = this.passedElement.type === 'text';
this.highlightPosition = 0;
this.canSearch = this.config.search;
this.placeholder = false;
// Placeholder
this.placeholder = (this.config.placeholderValue || this.passedElement.getAttribute('placeholder')) || false;
if (this.passedElement.type !== 'select-one') {
this.placeholder = (this.config.placeholderValue || this.passedElement.getAttribute('placeholder')) || false
}
// Monitor touch taps/scrolls
this.wasTap = true;
@ -220,8 +220,6 @@ class Choices {
if (callback) {
if (isType('Function', callback)) {
callback.call(this);
} else {
console.error('callbackOnInit: Callback is not a function');
}
}
}
@ -450,24 +448,26 @@ class Choices {
* @return {Object} Class instance
* @public
*/
highlightItem(item) {
highlightItem(item, runEvent = true) {
if (!item) return;
const id = item.id;
const groupId = item.groupId;
const callback = this.config.callbackOnHighlightItem;
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch(highlightItem(id, true));
// Run callback if it is a function
if (callback) {
if (isType('Function', callback)) {
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if(group && group.value) {
callback.call(this, id, item.value, group.value);
} else {
callback.call(this, id, item.value)
}
if (runEvent) {
if(group && group.value) {
triggerEvent(this.passedElement, 'highlightItem', {
id,
value: item.value,
groupValue: group.value
});
} else {
console.error('callbackOnHighlightItem: Callback is not a function');
triggerEvent(this.passedElement, 'highlightItem', {
id,
value: item.value,
});
}
}
@ -484,22 +484,21 @@ class Choices {
if (!item) return;
const id = item.id;
const groupId = item.groupId;
const callback = this.config.callbackOnUnhighlightItem;
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch(highlightItem(id, false));
// Run callback if it is a function
if (callback) {
if (isType('Function', callback)) {
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if(group && group.value) {
callback.call(this, id, item.value, group.value);
} else {
callback.call(this, id, item.value)
}
} else {
console.error('callbackOnUnhighlightItem: Callback is not a function');
}
if(group && group.value) {
triggerEvent(this.passedElement, 'unhighlightItem', {
id,
value: item.value,
groupValue: group.value
});
} else {
triggerEvent(this.passedElement, 'unhighlightItem', {
id,
value: item.value,
});
}
return this;
@ -581,15 +580,15 @@ class Choices {
* @return {Object} Class instance
* @public
*/
removeHighlightedItems(runCallback = false) {
removeHighlightedItems(runEvent = false) {
const items = this.store.getItemsFilteredByActive();
items.forEach((item) => {
if (item.highlighted && item.active) {
this._removeItem(item);
// If this action was performed by the user
// run the callback
if (runCallback) {
// trigger the event
if (runEvent) {
this._triggerChange(item.value);
}
}
@ -754,7 +753,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.placeholder);
} else {
console.warn('Attempting to select choice already selected');
}
@ -789,10 +788,12 @@ class Choices {
choices.forEach((result, index) => {
const isSelected = result.selected ? result.selected : false;
const isDisabled = result.disabled ? result.disabled : false;
const isPlaceholder = result.placeholder ? result.placeholder : false;
if (result.choices) {
this._addGroup(result, index, value, label);
} else {
this._addChoice(isSelected, isDisabled, result[value], result[label]);
this._addChoice(isSelected, isDisabled, result[value], result[label], -1, isPlaceholder);
}
});
}
@ -896,16 +897,10 @@ class Choices {
*/
_triggerChange(value) {
if (!value) return;
const callback = this.config.callbackOnChange;
// Run callback if it is a function
if (callback) {
if (isType('Function', callback)) {
callback.call(this, value);
} else {
console.error('callbackOnChange: Callback is not a function');
}
}
triggerEvent(this.passedElement, 'change', {
value
});
}
/**
@ -981,7 +976,7 @@ class Choices {
const canAddItem = this._canAddItem(activeItems, choice.value);
if (canAddItem.response) {
this._addItem(choice.value, choice.label, choice.id, choice.groupId);
this._addItem(choice.value, choice.label, choice.id, choice.groupId, choice.placeholder);
this._triggerChange(choice.value);
}
}
@ -1015,7 +1010,7 @@ class Choices {
this._triggerChange(lastItem.value);
} else {
if (!hasHighlightedItems) {
this.highlightItem(lastItem);
this.highlightItem(lastItem, false);
}
this.removeHighlightedItems(true);
}
@ -1115,10 +1110,11 @@ class Choices {
parsedResults.forEach((result, index) => {
const isSelected = result.selected ? result.selected : false;
const isDisabled = result.disabled ? result.disabled : false;
const isPlaceholder = result.placeholder ? result.placeholder : false;
if (result.choices) {
this._addGroup(result, index, value, label);
} else {
this._addChoice(isSelected, isDisabled, result[value], result[label]);
this._addChoice(isSelected, isDisabled, result[value], result[label], isPlaceholder);
}
});
}
@ -1141,12 +1137,10 @@ class Choices {
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, {
keys,
shouldSort: true,
include: 'score',
});
const options = Object.assign(this.config.fuseOptions, { keys });
const fuse = new Fuse(haystack, options);
const results = fuse.search(needle);
this.currentValue = newValue;
this.highlightPosition = 0;
this.isSearching = true;
@ -1164,7 +1158,6 @@ class Choices {
if (!value) return;
const choices = this.store.getChoices();
const hasUnactiveChoices = choices.some((option) => option.active !== true);
const callback = this.config.callbackOnSearch;
// Run callback if it is a function
if (this.input === document.activeElement) {
@ -1172,14 +1165,10 @@ class Choices {
if (value && value.length > this.config.searchFloor) {
// Filter available choices
this._searchChoices(value);
// Run callback if it is a function
if (callback) {
if (isType('Function', callback)) {
callback.call(this, value);
} else {
console.error('callbackOnSearch: Callback is not a function');
}
}
// Trigger search event
triggerEvent(this.passedElement, 'search', {
value,
});
} else if (hasUnactiveChoices) {
// Otherwise reset choices to active
this.isSearching = false;
@ -1322,12 +1311,14 @@ class Choices {
}
if (hasActiveDropdown) {
e.preventDefault();
const highlighted = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
// If we have a highlighted choice
if (highlighted) {
this._handleChoiceAction(activeItems, highlighted);
}
} else if (passedElementType === 'select-one') {
// Open single select dropdown if it's not active
if (!hasActiveDropdown) {
@ -1835,12 +1826,17 @@ class Choices {
* @return {Object} Class instance
* @public
*/
_addItem(value, label, choiceId = -1, groupId = -1) {
_addItem(value, label, choiceId = -1, groupId = -1, isPlaceholder = false) {
let passedValue = isType('String', value) ? value.trim() : value;
const items = this.store.getItems();
const passedLabel = label || passedValue;
const passedOptionId = parseInt(choiceId, 10) || -1;
const callback = this.config.callbackOnAddItem;
// Get group if group ID passed
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
// Generate unique id
const id = items ? items.length + 1 : 1;
// If a prepended value has been passed, prepend it
if (this.config.prependValue) {
@ -1852,27 +1848,24 @@ class Choices {
passedValue += this.config.appendValue.toString();
}
// Generate unique id
const id = items ? items.length + 1 : 1;
this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId));
this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId, isPlaceholder));
if (this.passedElement.type === 'select-one') {
this.removeActiveItems(id);
}
// Run callback if it is a function
if (callback) {
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if (isType('Function', callback)) {
if(group && group.value) {
callback.call(this, id, passedValue, group.value);
} else {
callback.call(this, id, passedValue);
}
} else {
console.error('callbackOnAddItem: Callback is not a function');
}
// Trigger change event
if(group && group.value) {
triggerEvent(this.passedElement, 'addItem', {
id,
value: passedValue,
groupValue: group.value,
});
} else {
triggerEvent(this.passedElement, 'addItem', {
id,
value: passedValue,
});
}
return this;
@ -1895,22 +1888,21 @@ class Choices {
const value = item.value;
const choiceId = item.choiceId;
const groupId = item.groupId;
const callback = this.config.callbackOnRemoveItem;
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
this.store.dispatch(removeItem(id, choiceId));
// Run callback
if (callback) {
if (isType('Function', callback)) {
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
if(group && group.value) {
callback.call(this, id, value, group.value);
} else {
callback.call(this, id, value);
}
} else {
console.error('callbackOnRemoveItem: Callback is not a function');
}
if(group && group.value) {
triggerEvent(this.passedElement, 'removeItem', {
id,
value,
groupValue: group.value,
});
} else {
triggerEvent(this.passedElement, 'removeItem', {
id,
value,
});
}
return this;
@ -1926,7 +1918,7 @@ class Choices {
* @return
* @private
*/
_addChoice(isSelected, isDisabled, value, label, groupId = -1) {
_addChoice(isSelected, isDisabled, value, label, groupId = -1, isPlaceholder = false) {
if (typeof value === 'undefined' || value === null) return;
// Generate unique id
@ -1934,10 +1926,10 @@ class Choices {
const choiceLabel = label || value;
const choiceId = choices ? choices.length + 1 : 1;
this.store.dispatch(addChoice(value, choiceLabel, choiceId, groupId, isDisabled));
this.store.dispatch(addChoice(value, choiceLabel, choiceId, groupId, isDisabled, isPlaceholder));
if (isSelected) {
this._addItem(value, choiceLabel, choiceId);
this._addItem(value, choiceLabel, choiceId, groupId, isPlaceholder);
}
}
@ -2008,76 +2000,154 @@ class Choices {
const templates = {
containerOuter: (direction) => {
return strToEl(`
<div class="${classNames.containerOuter}" data-type="${this.passedElement.type}" ${this.passedElement.type === 'select-one' ? 'tabindex="0"' : ''} aria-haspopup="true" aria-expanded="false" dir="${direction}"></div>
`);
<div
class="${classNames.containerOuter}"
data-type="${this.passedElement.type}" ${this.passedElement.type === 'select-one' ? 'tabindex="0"' : ''}
aria-haspopup="true"
aria-expanded="false"
dir="${direction}"
>
</div>
`);
},
containerInner: () => {
return strToEl(`
<div class="${classNames.containerInner}"></div>
`);
<div class="${classNames.containerInner}"></div>
`);
},
itemList: () => {
return strToEl(`
<div class="${classNames.list} ${this.passedElement.type === 'select-one' ? classNames.listSingle : classNames.listItems}"></div>
`);
<div class="${classNames.list} ${this.passedElement.type === 'select-one' ? classNames.listSingle : classNames.listItems}"></div>
`);
},
placeholder: (value) => {
return strToEl(`
<div class="${classNames.placeholder}">${value}</div>
`);
<div class="${classNames.placeholder}">${value}</div>
`);
},
item: (data) => {
if (this.config.removeItemButton) {
return strToEl(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : ''} ${!data.disabled ? classNames.itemSelectable : ''}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''} data-deletable>
${data.label}<button class="${classNames.button}" data-button>Remove item</button>
</div>
`);
<div
class="
${classNames.item}
${data.highlighted ? classNames.highlightedState : ''}
${!data.disabled ? classNames.itemSelectable : ''}
${data.placeholder ? classNames.placeholder : ''}
"
data-item
data-id="${data.id}"
data-value="${data.value}"
${data.active ? 'aria-selected="true"' : ''}
${data.disabled ? 'aria-disabled="true"' : ''}
data-deletable
>
${data.label}
<button class="${classNames.button}" data-button>Remove item</button>
</div>
`);
}
return strToEl(`
<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.placeholder ? classNames.placeholder : ''}
"
data-item
data-id="${data.id}"
data-value="${data.value}"
${data.active ? 'aria-selected="true"' : ''}
${data.disabled ? 'aria-disabled="true"' : ''}
>
${data.label}
</div>
`);
`);
},
choiceList: () => {
return strToEl(`
<div class="${classNames.list}" dir="ltr" role="listbox" ${this.passedElement.type !== 'select-one' ? 'aria-multiselectable="true"' : ''}></div>
`);
<div
class="${classNames.list}"
dir="ltr"
role="listbox" ${this.passedElement.type !== 'select-one' ? 'aria-multiselectable="true"' : ''}
>
</div>
`);
},
choiceGroup: (data) => {
return strToEl(`
<div class="${classNames.group} ${data.disabled ? classNames.itemDisabled : ''}" data-group data-id="${data.id}" data-value="${data.value}" role="group" ${data.disabled ? 'aria-disabled="true"' : ''}>
<div class="${classNames.groupHeading}">${data.value}</div>
</div>
`);
<div
class="
${classNames.group}
${data.disabled ? classNames.itemDisabled : ''}
"
data-group
data-id="${data.id}"
data-value="${data.value}"
role="group"
${data.disabled ? 'aria-disabled="true"' : ''}
>
<div class="${classNames.groupHeading}">${data.value}</div>
</div>
`);
},
choice: (data) => {
return strToEl(`
<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"'}>
${data.label}
</div>
`);
<div
class="
${classNames.item}
${classNames.itemChoice}
${data.placeholder ? classNames.placeholder : ''}
${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"'}
>
${data.label}
</div>
`);
},
input: () => {
return strToEl(`
<input type="text" class="${classNames.input} ${classNames.inputCloned}" autocomplete="off" autocapitalize="off" spellcheck="false" role="textbox" aria-autocomplete="list">
`);
<input
type="text"
class="
${classNames.input}
${classNames.inputCloned}
"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
role="textbox"
aria-autocomplete="list"
>
`);
},
dropdown: () => {
return strToEl(`
<div class="${classNames.list} ${classNames.listDropdown}" aria-expanded="false"></div>
`);
<div
class="
${classNames.list}
${classNames.listDropdown}
"
aria-expanded="false"
>
</div>
`);
},
notice: (label) => {
return strToEl(`
<div class="${classNames.item} ${classNames.itemChoice}">${label}</div>
`);
<div class="${classNames.item} ${classNames.itemChoice}">${label}</div>
`);
},
option: (data) => {
return strToEl(`
<option value="${data.value}" selected>${data.label}</option>
`);
<option value="${data.value}" selected>${data.label}</option>
`);
},
};
@ -2087,6 +2157,7 @@ class Choices {
if (callbackTemplate && isType('Function', callbackTemplate)) {
userTemplates = callbackTemplate.call(this, strToEl);
}
this.config.templates = extend(templates, userTemplates);
}
@ -2185,6 +2256,7 @@ class Choices {
label: o.innerHTML,
selected: o.selected,
disabled: o.disabled || o.parentNode.disabled,
placeholder: o.hasAttribute('placeholder')
});
});
@ -2207,13 +2279,13 @@ class Choices {
if (hasSelectedChoice || (!hasSelectedChoice && index > 0)) {
// If there is a selected choice already or the choice is not
// the first in the array, add each choice normally
this._addChoice(isSelected, isDisabled, choice.value, choice.label);
this._addChoice(isSelected, isDisabled, choice.value, choice.label, -1, choice.placeholder);
} else {
// Otherwise pre-select the first choice in the array
this._addChoice(true, false, choice.value, choice.label);
this._addChoice(true, false, choice.value, choice.label, -1, choice.placeholder);
}
} else {
this._addChoice(isSelected, isDisabled, choice.value, choice.label);
this._addChoice(isSelected, isDisabled, choice.value, choice.label, -1, choice.placeholder);
}
});
}

View file

@ -1,112 +1,129 @@
/* eslint-disable */
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = (function() {
var toStr = Object.prototype.toString;
(function () {
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = (function() {
var toStr = Object.prototype.toString;
var isCallable = function(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var isCallable = function(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function(value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var toInteger = function(value) {
var number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
var toLength = function(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */ ) {
// 1. Let C be the this value.
var C = this;
// The length property of the from method is 1.
return function from(arrayLike /*, mapFn, thisArg */ ) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
// Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
return undefined;
};
}());
}
}
// Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
function CustomEvent (event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();

View file

@ -30,7 +30,7 @@ export const isNode = (o) => {
return (
typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"
);
);
};
/**
@ -42,7 +42,7 @@ export const isElement = (o) => {
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"
);
);
};
/**
@ -58,7 +58,7 @@ export const extend = function() {
* Merge one object into another
* @param {Object} obj Object to merge into extended object
*/
let merge = function(obj) {
let merge = function(obj) {
for (let prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// If deep merge and property is an object, merge properties
@ -91,7 +91,7 @@ export const extend = function() {
*/
export const whichTransitionEvent = function() {
var t,
el = document.createElement("fakeelement");
el = document.createElement("fakeelement");
var transitions = {
"transition": "transitionend",
@ -113,7 +113,7 @@ export const whichTransitionEvent = function() {
*/
export const whichAnimationEvent = function() {
var t,
el = document.createElement('fakeelement');
el = document.createElement('fakeelement');
var animations = {
'animation': 'animationend',
@ -259,7 +259,7 @@ export const debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
@ -459,6 +459,14 @@ export const getWidthOfInput = (input) => {
return `${width}px`;
};
/**
* Sorting function for current and previous string
* @param {String} a Current value
* @param {String} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
export const sortByAlpha = (a, b) => {
const labelA = (a.label || a.value).toLowerCase();
const labelB = (b.label || b.value).toLowerCase();
@ -468,6 +476,31 @@ export const sortByAlpha = (a, b) => {
return 0;
};
/**
* Sort by numeric score
* @param {Object} a Current value
* @param {Object} b Next value
* @return {Number} -1 for after previous,
* 1 for before,
* 0 for same location
*/
export const sortByScore = (a, b) => {
return a.score - b.score;
};
/**
* Trigger native event
* @param {NodeElement} element Element to trigger event on
* @param {String} type Type of event to trigger
* @param {Object} customArgs Data to pass with event
* @return {Object} Triggered event
*/
export const triggerEvent = (element, type, customArgs = null) => {
const event = new CustomEvent(type, {
detail: customArgs,
bubbles: true,
cancelable: true
});
return element.dispatchEvent(event);
};

View file

@ -12,6 +12,7 @@ const choices = (state = [], action) => {
value: action.value,
label: action.label,
disabled: action.disabled,
placeholder: action.placeholder,
selected: false,
active: true,
score: 9999,

View file

@ -10,6 +10,7 @@ const items = (state = [], action) => {
label: action.label,
active: true,
highlighted: false,
placeholder: action.placeholder
}];
return newState.map((item) => {

View file

@ -1,6 +1,6 @@
{
"name": "choices.js",
"version": "2.5.0",
"version": "2.6.1",
"description": "A vanilla JS customisable text input/select box plugin",
"main": [
"./assets/scripts/dist/choices.js",
@ -17,10 +17,6 @@
"bower_components",
"test",
"tests",
"assets/images",
"webpack.config.dev.js",
"webpack.config.prod.js",
"server.js"
],
"keywords": [
"customisable",

View file

@ -15,16 +15,16 @@
<meta name="theme-color" content="#ffffff">
<!-- Ignore these -->
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.5.0">
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.6.1">
<!-- End ignore these -->
<!-- Optional includes -->
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=es5,fetch,Element.prototype.classList,requestAnimationFrame,Node.insertBefore,Node.firstChild"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=es5,fetch,Element.prototype.classList,requestAnimationFrame,Node.insertBefore,Node.firstChild,Object.assign"></script>
<!-- End optional includes -->
<!-- Choices includes -->
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.5.0">
<script src="assets/scripts/dist/choices.min.js?version=2.5.0"></script>
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.6.1">
<script src="assets/scripts/dist/choices.min.js?version=2.6.1"></script>
<!-- End Choices includes -->
<!--[if lt IE 9]>
@ -230,6 +230,14 @@
<option value="Michigan">Michigan</option>
</select>
<label for="choices-placeholder-option">Placeholder option</label>
<select class="form-control" name="choices-placeholder-option" id="choices-placeholder-option">
<option selected placeholder>Press here</option>
<option value="one">One</option>
<option value="two">Two</option>
<option value="three">Three</option>
</select>
<label for="choices-custom-templates">Custom templates</label>
<select class="form-control" name="choices-custom-templates" id="choices-custom-templates">
<option value="React">React</option>
@ -256,14 +264,6 @@
<option value="Queens">Queens</option>
<option value="Staten Island">Staten Island</option>
</select>
<label for="choices-placeholder-option">Placeholder option</label>
<select class="form-control" name="choices-placeholder-option" id="choices-placeholder-option">
<option selected placeholder>Press here</option>
<option value="one">One</option>
<option value="two">Two</option>
<option value="three">Three</option>
</select>
</div>
</div>
<script>
@ -273,12 +273,6 @@
editItems: true,
maxItemCount: 5,
removeItemButton: true,
callbackOnHighlightItem: function(id, value) {
console.log(value);
},
callbackOnUnhighlightItem: function(id, value) {
console.log(value);
},
});
var textUniqueVals = new Choices('#choices-text-unique-values', {
@ -319,21 +313,11 @@
items: ['josh@joshuajohnson.co.uk', { value: 'joe@bloggs.co.uk', label: 'Joe Bloggs' } ],
});
var multipleDefault = new Choices('#choices-multiple-groups', {
callbackOnAddItem: function(id, value, groupValue) {
console.log(arguments);
},
callbackOnRemoveItem: function(id, value, groupValue) {
console.log(arguments);
},
});
var multipleDefault = new Choices(document.getElementById('choices-multiple-groups'));
var multipleFetch = new Choices('#choices-multiple-remote-fetch', {
searchPlaceholderValue: 'Pick an Strokes record',
maxItemCount: 5,
callbackOnChange: function(value) {
console.log(value)
}
}).ajax(function(callback) {
fetch('https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
.then(function(response) {
@ -342,7 +326,7 @@
});
})
.catch(function(error) {
// console.error(error);
console.error(error);
});
});
@ -361,7 +345,7 @@
});
})
.catch(function(error) {
// console.error(error);
console.error(error);
});
});
@ -381,7 +365,7 @@
callback(data.releases, 'title', 'title');
singleXhrRemove.setValueByChoice('How Soon Is Now?');
} else {
// console.error(status);
console.error(status);
}
}
}
@ -441,18 +425,24 @@
shouldSort: false,
});
var singleStates = new Choices(document.getElementById('choices-states'), {
callbackOnChange: function(value) {
if(value === 'New York') {
singleBoroughs.enable();
} else {
singleBoroughs.disable();
}
}
var singlePlaceholderOption = new Choices('#choices-placeholder-option', {
placeholder: true,
placeholderValue: 'Please Choose…',
removeItemButton: false,
preselectItem: false,
});
var singleStates = new Choices(document.getElementById('choices-states'));
var singleBoroughs = new Choices(document.getElementById('choices-boroughs')).disable();
singleStates.passedElement.addEventListener('change', function(e) {
if(e.detail.value === 'New York') {
singleBoroughs.enable();
} else {
singleBoroughs.disable();
}
});
var singleCustomTemplates = new Choices(
document.getElementById('choices-custom-templates'), {
searchPlaceholderValue: 'Choose your favourite framework...',
@ -477,14 +467,6 @@
}
}
);
var singlePlaceholderOption = new Choices('#choices-placeholder-option', {
placeholder: true,
placeholderValue: 'Please Choose…',
removeItemButton: true,
preselectItem: false,
});
});
</script>

View file

@ -1,6 +1,6 @@
{
"name": "choices.js",
"version": "2.5.0",
"version": "2.6.1",
"description": "A vanilla JS customisable text input/select box plugin",
"main": "./assets/scripts/dist/choices.min.js",
"scripts": {
@ -12,7 +12,8 @@
"css:min": "csso assets/styles/css/base.css assets/styles/css/base.min.css && csso assets/styles/css/choices.css assets/styles/css/choices.min.css",
"js:build": "concurrently --prefix-colors yellow,green \"webpack --minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"",
"js:test": "./node_modules/karma/bin/karma start --single-run --no-auto-watch tests/karma.config.js",
"js:test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js"
"js:test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js",
"preversion": "npm run js:build"
},
"repository": {
"type": "git",

View file

@ -2,6 +2,30 @@ import 'whatwg-fetch';
import 'es6-promise';
import Choices from '../../assets/scripts/src/choices.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;
};
}
describe('Choices', () => {
afterEach(function() {
@ -12,7 +36,7 @@ describe('Choices', () => {
beforeEach(function() {
this.input = document.createElement('input');
this.input.type = "text";
this.input.type = 'text';
this.input.className = 'js-choices';
document.body.appendChild(this.input);
@ -67,12 +91,7 @@ describe('Choices', () => {
expect(this.choices.config.itemSelectText).toEqual(jasmine.any(String));
expect(this.choices.config.classNames).toEqual(jasmine.any(Object));
expect(this.choices.config.callbackOnInit).toEqual(null);
expect(this.choices.config.callbackOnAddItem).toEqual(null);
expect(this.choices.config.callbackOnRemoveItem).toEqual(null);
expect(this.choices.config.callbackOnHighlightItem).toEqual(null);
expect(this.choices.config.callbackOnUnhighlightItem).toEqual(null);
expect(this.choices.config.callbackOnChange).toEqual(null);
expect(this.choices.config.callbackOnSearch).toEqual(null);
expect(this.choices.config.callbackOnCreateTemplates).toEqual(null);
});
it('should expose public methods', function() {
@ -127,15 +146,15 @@ describe('Choices', () => {
expect(this.choices.input).toEqual(jasmine.any(HTMLElement));
});
// it('should create a dropdown', function() {
// expect(this.choices.dropdown).toEqual(jasmine.any(HTMLElement));
// });
it('should create a dropdown', function() {
expect(this.choices.dropdown).toEqual(jasmine.any(HTMLElement));
});
});
describe('should accept text inputs', function() {
beforeEach(function() {
this.input = document.createElement('input');
this.input.type = "text";
this.input.type = 'text';
this.input.className = 'js-choices';
this.input.placeholder = 'Placeholder text';
@ -294,15 +313,23 @@ describe('Choices', () => {
this.choices._onKeyDown({
target: this.choices.input,
keyCode: 13,
ctrlKey: false
ctrlKey: false,
preventDefault: () => {}
});
expect(this.choices.currentState.items.length).toBe(2);
});
it('should trigger a change callback on selection', function() {
it('should trigger add/change event on selection', function() {
this.choices = new Choices(this.input);
spyOn(this.choices.config, 'callbackOnChange');
const changeSpy = jasmine.createSpy('changeSpy');
const addSpy = jasmine.createSpy('addSpy');
const passedElement = this.choices.passedElement;
passedElement.addEventListener('change', changeSpy);
passedElement.addEventListener('addItem', addSpy);
this.choices.input.focus();
// Key down to second choice
@ -317,10 +344,14 @@ describe('Choices', () => {
this.choices._onKeyDown({
target: this.choices.input,
keyCode: 13,
ctrlKey: false
ctrlKey: false,
preventDefault: () => {}
});
expect(this.choices.config.callbackOnChange).toHaveBeenCalledWith(jasmine.any(String));
const returnValue = changeSpy.calls.mostRecent().args[0].detail.value;
expect(returnValue).toEqual(jasmine.any(String));
expect(changeSpy).toHaveBeenCalled();
expect(addSpy).toHaveBeenCalled();
});
it('should open the dropdown on click', function() {
@ -356,6 +387,12 @@ describe('Choices', () => {
it('should filter choices when searching', function() {
this.choices = new Choices(this.input);
const searchSpy = jasmine.createSpy('searchSpy');
const passedElement = this.choices.passedElement;
passedElement.addEventListener('search', searchSpy);
this.choices.input.focus();
this.choices.input.value = 'Value 3';
@ -369,6 +406,7 @@ describe('Choices', () => {
const mostAccurateResult = this.choices.currentState.choices[0];
expect(this.choices.isSearching && mostAccurateResult.value === 'Value 3').toBeTruthy;
expect(searchSpy).toHaveBeenCalled();
});
it('shouldn\'t sort choices if shouldSort is false', function() {
@ -639,6 +677,21 @@ describe('Choices', () => {
expect(choices[choices.length - 2].value).toEqual('Child Five');
});
it('should handle setChoices() with blank values', function() {
this.choices.setChoices([{
label: 'Choice one',
value: 'one'
}, {
label: 'Choice two',
value: ''
}], 'value', 'label', true);
const choices = this.choices.currentState.choices;
expect(choices[0].value).toEqual('one');
expect(choices[1].value).toEqual('');
});
it('should handle clearStore()', function() {
this.choices.clearStore();
@ -685,9 +738,9 @@ describe('Choices', () => {
describe('should handle public methods on text input types', function() {
beforeEach(function() {
this.input = document.createElement('input');
this.input.type = "text";
this.input.type = 'text';
this.input.className = 'js-choices';
this.input.value = "Value 1, Value 2, Value 3, Value 4";
this.input.value = 'Value 1, Value 2, Value 3, Value 4';
document.body.appendChild(this.input);
this.choices = new Choices(this.input);