Version 2.6.0

This commit is contained in:
Josh Johnson 2017-01-01 15:32:09 +00:00
parent b2d93572ae
commit ec0d336072
7 changed files with 329 additions and 285 deletions

115
README.md
View file

@ -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
<!-- Include base CSS (optional) -->
<link rel="stylesheet" href="assets/styles/css/base.min.css">
@ -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:

View file

@ -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);
}

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

@ -15,7 +15,7 @@
<meta name="theme-color" content="#ffffff">
<!-- Ignore these -->
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.5.2">
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.6.0">
<!-- End ignore these -->
<!-- Optional includes -->
@ -23,8 +23,8 @@
<!-- End optional includes -->
<!-- Choices includes -->
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.5.2">
<script src="assets/scripts/dist/choices.min.js?version=2.5.2"></script>
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.6.0">
<script src="assets/scripts/dist/choices.min.js?version=2.6.0"></script>
<!-- End Choices includes -->
<!--[if lt IE 9]>
@ -265,12 +265,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', {
@ -311,22 +305,12 @@
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', {
placeholder: true,
placeholderValue: '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) {
@ -335,7 +319,7 @@
});
})
.catch(function(error) {
// console.error(error);
console.error(error);
});
});
@ -355,7 +339,7 @@
});
})
.catch(function(error) {
// console.error(error);
console.error(error);
});
});
@ -374,7 +358,7 @@
callback(data.releases, 'title', 'title');
singleXhrRemove.setValueByChoice('How Soon Is Now?');
} else {
// console.error(status);
console.error(status);
}
}
}
@ -434,13 +418,13 @@
shouldSort: false,
});
var states = new Choices(document.getElementById('states'), {
callbackOnChange: function(value) {
if(value === 'New York') {
boroughs.enable();
} else {
boroughs.disable();
}
var states = new Choices(document.getElementById('states'));
states.passedElement.addEventListener('change', function(e) {
if(e.detail.value === 'New York') {
boroughs.enable();
} else {
boroughs.disable();
}
});

View file

@ -1,6 +1,6 @@
{
"name": "choices.js",
"version": "2.5.2",
"version": "2.6.0",
"description": "A vanilla JS customisable text input/select box plugin",
"main": "./assets/scripts/dist/choices.min.js",
"scripts": {

View file

@ -12,7 +12,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 +67,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 +122,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';
@ -301,9 +296,16 @@ describe('Choices', () => {
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
@ -322,7 +324,10 @@ describe('Choices', () => {
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() {
@ -358,6 +363,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';
@ -371,6 +382,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() {
@ -701,9 +713,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);