diff --git a/README.md b/README.md
index 01f3cc0..3dbb224 100644
--- a/README.md
+++ b/README.md
@@ -18,11 +18,14 @@ With [NPM](https://www.npmjs.com/package/choices.js):
```zsh
npm install choices.js --save
```
+
With [Bower](https://bower.io/):
```zsh
bower install choices.js --save
```
+
Or include Choices directly:
+
```html
@@ -106,13 +109,7 @@ Or include Choices directly:
loadingState: 'is-loading',
},
callbackOnInit: null,
- callbackOnAddItem: null,
- callbackOnRemoveItem: null,
- callbackOnHighlightItem: null,
- callbackOnUnhighlightItem: null,
callbackOnCreateTemplates: null,
- callbackOnChange: null,
- callbackOnSearch: null,
});
```
@@ -406,44 +403,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`
@@ -477,19 +436,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:
diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js
index 0c28d4b..c6b858b 100644
--- a/assets/scripts/src/choices.js
+++ b/assets/scripts/src/choices.js
@@ -23,6 +23,7 @@ import {
getWidthOfInput,
sortByAlpha,
sortByScore,
+ triggerEvent,
}
from './lib/utils.js';
import './lib/polyfills.js';
@@ -105,13 +106,7 @@ class Choices {
loadingState: 'is-loading',
},
callbackOnInit: null,
- callbackOnAddItem: null,
- callbackOnRemoveItem: null,
- callbackOnHighlightItem: null,
- callbackOnUnhighlightItem: null,
callbackOnCreateTemplates: null,
- callbackOnChange: null,
- callbackOnSearch: null,
};
// Merge options with user options
@@ -223,8 +218,6 @@ class Choices {
if (callback) {
if (isType('Function', callback)) {
callback.call(this);
- } else {
- console.error('callbackOnInit: Callback is not a function');
}
}
}
@@ -449,24 +442,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,
+ });
}
}
@@ -483,22 +478,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;
@@ -580,15 +574,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);
}
}
@@ -895,16 +889,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
+ });
}
/**
@@ -1023,7 +1011,7 @@ class Choices {
this._triggerChange(lastItem.value);
} else {
if (!hasHighlightedItems) {
- this.highlightItem(lastItem);
+ this.highlightItem(lastItem, false);
}
this.removeHighlightedItems(true);
}
@@ -1173,7 +1161,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) {
@@ -1181,14 +1168,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;
@@ -1853,7 +1836,12 @@ class Choices {
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) {
@@ -1865,27 +1853,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));
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;
@@ -1908,22 +1893,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;
@@ -2100,6 +2084,7 @@ class Choices {
if (callbackTemplate && isType('Function', callbackTemplate)) {
userTemplates = callbackTemplate.call(this, strToEl);
}
+
this.config.templates = extend(templates, userTemplates);
}
diff --git a/assets/scripts/src/lib/polyfills.js b/assets/scripts/src/lib/polyfills.js
index f52a5f3..e45aed9 100644
--- a/assets/scripts/src/lib/polyfills.js
+++ b/assets/scripts/src/lib/polyfills.js
@@ -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;
- };
-}
\ No newline at end of file
+ CustomEvent.prototype = window.Event.prototype;
+
+ window.CustomEvent = CustomEvent;
+})();
diff --git a/assets/scripts/src/lib/utils.js b/assets/scripts/src/lib/utils.js
index 15e4aa7..e982acc 100644
--- a/assets/scripts/src/lib/utils.js
+++ b/assets/scripts/src/lib/utils.js
@@ -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);
+};
diff --git a/index.html b/index.html
index 8804897..e05632c 100644
--- a/index.html
+++ b/index.html
@@ -15,7 +15,7 @@
-
+
@@ -23,8 +23,8 @@
-
-
+
+