Merge master

This commit is contained in:
Josh Johnson 2017-04-07 09:06:34 +01:00
commit df4dc82e2c
10 changed files with 112 additions and 49 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
node_modules
npm-debug.log
.DS_Store
.vscode
# Test
tests/reports

View file

@ -3,7 +3,7 @@ A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input
[Demo](https://joshuajohnson.co.uk/Choices/)
## TL;DR
## TL;DR
* Lightweight
* No jQuery dependency
* Configurable sorting
@ -14,7 +14,7 @@ A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input
* Custom templates
----
### Interested in writing your own ES6 JavaScript plugins? Check out [ES6.io](https://ES6.io/friend/JOHNSON) for great tutorials! 💪🏼
### Interested in writing your own ES6 JavaScript plugins? Check out [ES6.io](https://ES6.io/friend/JOHNSON) for great tutorials! 💪🏼
----
## Installation
@ -43,17 +43,17 @@ Or include Choices directly:
```js
// Pass multiple elements:
const choices = new Choices(elements);
// Pass single element:
const choices = new Choices(element);
// Pass reference
const choices = new Choices('[data-trigger']);
const choices = new Choices('.js-choice');
// Pass jQuery element
const choices = new Choices($('.js-choice')[0]);
// Passing options (with default options)
const choices = new Choices(elements, {
items: [],
@ -67,13 +67,15 @@ Or include Choices directly:
delimiter: ',',
paste: true,
search: true,
searchChoices: true,
searchFloor: 1,
searchFields: ['label', 'value'],
position: 'auto',
resetScrollPosition: true,
regexFilter: null,
shouldSort: true,
sortFilter: () => {...},
sortFields: ['label', 'value'],
placeholder: true,
placeholderValue: null,
prependValue: null,
appendValue: null,
@ -136,23 +138,23 @@ Or include Choices directly:
**Input types affected:** `text`
**Usage:** Add pre-selected items (see terminology) to text input.
**Usage:** Add pre-selected items (see terminology) to text input.
Pass an array of strings:
Pass an array of strings:
`['value 1', 'value 2', 'value 3']`
Pass an array of objects:
```
[{
[{
value: 'Value 1',
label: 'Label 1',
id: 1
label: 'Label 1',
id: 1
},
{
{
value: 'Value 2',
label: 'Label 2',
label: 'Label 2',
id: 2
}]
```
@ -162,7 +164,7 @@ Pass an array of objects:
**Input types affected:** `select-one`, `select-multiple`
**Usage:** Add choices (see terminology) to select input.
**Usage:** Add choices (see terminology) to select input.
Pass an array of objects:
@ -244,6 +246,21 @@ Pass an array of objects:
**Usage:** Whether a user should be allowed to search avaiable choices. Note that multiple select boxes will always show search inputs.
### searchChoices
**Type:** `Boolean` **Default:** `true`
**Input types affected:** `select-one`
**Usage:** Whether the plugin should filter the choices by input or not. If `false`, the search event will still emit.
### searchFields
**Type:** `Array/String` **Default:** `['label', 'value']`
**Input types affected:**`select-one`, `select-multiple`
**Usage:** Specify which fields should be used when a user is searching.
### searchFloor
**Type:** `Number` **Default:** `1`
@ -256,7 +273,7 @@ Pass an array of objects:
**Input types affected:** `select-one`, `select-multiple`
**Usage:** Whether the dropdown should appear above (`top`) or below (`bottom`) the input. By default, if there is not enough space within the window the dropdown will appear above the input, otherwise below it.
**Usage:** Whether the dropdown should appear above (`top`) or below (`bottom`) the input. By default, if there is not enough space within the window the dropdown will appear above the input, otherwise below it.
### resetScrollPosition
**Type:** `Boolean` **Default:** `true`
@ -277,7 +294,7 @@ Pass an array of objects:
**Input types affected:** `select-one`, `select-multiple`
**Usage:** Whether choices should be sorted. If false, choices will appear in the order they were given.
**Usage:** Whether choices should be sorted. If false, choices will appear in the order they were given.
### sortFilter
**Type:** `Function` **Default:** sortByAlpha
@ -297,12 +314,12 @@ const example = new Choices(element, {
};
```
### sortFields
**Type:** `Array/String` **Default:** `['label', 'value']`
### placeholder
**Type:** `Boolean` **Default:** `true`
**Input types affected:**`select-one`, `select-multiple`
**Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Specify which fields should be used for sorting when a user is searching. If a user is not searching and sorting is enabled, only the choice's label will be sorted.
**Usage:** Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value.
### placeholderValue
**Type:** `String` **Default:** `null`
@ -457,7 +474,7 @@ const example = new Choices(element, {
```
## 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.
**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:**
@ -473,7 +490,7 @@ element.addEventListener('addItem', function(event) {
console.log(event.detail.groupValue);
}, false);
// or
// or
const example = new Choices(document.getElementById('example'));
example.passedElement.addEventListener('addItem', function(event) {
@ -573,7 +590,7 @@ choices.disable();
**Usage:** Creates a new instance of Choices, adds event listeners, creates templates and renders a Choices element to the DOM.
**Note:** This is called implicitly when a new instance of Choices is created. This would be used after a Choices instance had already been destroyed (using `destroy()`).
**Note:** This is called implicitly when a new instance of Choices is created. This would be used after a Choices instance had already been destroyed (using `destroy()`).
### highlightAll();
**Input types affected:** `text`, `select-multiple`
@ -653,7 +670,7 @@ example.setChoices([{
{value: 'Child Two', label: 'Child Two', disabled: true},
{value: 'Child Three', label: 'Child Three'},
]
},
},
{
label: 'Group two',
id: 2,
@ -804,7 +821,7 @@ To setup a local environment: clone this repo, navigate into it's directory in a
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using npm scripts...bla bla bla
## License
MIT License
MIT License
## Misc
Thanks to [@mikefrancis](https://github.com/mikefrancis/) for [sending me on a hunt](https://twitter.com/_mikefrancis/status/701797835826667520) for a non-jQuery solution for select boxes that eventually led to this being built!

View file

@ -1,4 +1,4 @@
/*! choices.js v2.7.7 | (c) 2017 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
/*! choices.js v2.7.8 | (c) 2017 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
@ -124,13 +124,14 @@ return /******/ (function(modules) { // webpackBootstrap
delimiter: ',',
paste: true,
search: true,
searchChoices: true,
searchFloor: 1,
searchFields: ['label', 'value'],
position: 'auto',
resetScrollPosition: true,
regexFilter: null,
shouldSort: true,
sortFilter: _utils.sortByAlpha,
sortFields: ['label', 'value'],
placeholder: true,
placeholderValue: null,
prependValue: null,
@ -1045,6 +1046,9 @@ return /******/ (function(modules) { // webpackBootstrap
this.input.removeAttribute('disabled');
this.containerOuter.classList.remove(this.config.classNames.disabledState);
this.containerOuter.removeAttribute('aria-disabled');
if (this.passedElement.type === 'select-one') {
this.containerOuter.setAttribute('tabindex', '0');
}
}
return this;
}
@ -1066,6 +1070,9 @@ return /******/ (function(modules) { // webpackBootstrap
this.input.setAttribute('disabled', '');
this.containerOuter.classList.add(this.config.classNames.disabledState);
this.containerOuter.setAttribute('aria-disabled', 'true');
if (this.passedElement.type === 'select-one') {
this.containerOuter.setAttribute('tabindex', '-1');
}
}
return this;
}
@ -1402,7 +1409,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (newValue.length >= 1 && newValue !== currentValue + ' ') {
var haystack = this.store.getChoicesFilteredBySelectable();
var needle = newValue;
var keys = (0, _utils.isType)('Array', this.config.sortFields) ? this.config.sortFields : [this.config.sortFields];
var keys = (0, _utils.isType)('Array', this.config.searchFields) ? this.config.searchFields : [this.config.searchFields];
var options = Object.assign(this.config.fuseOptions, { keys: keys });
var fuse = new _fuse2.default(haystack, options);
var results = fuse.search(needle);
@ -1434,8 +1441,11 @@ return /******/ (function(modules) { // webpackBootstrap
if (this.input === document.activeElement) {
// Check that we have a value to search and the input was an alphanumeric character
if (value && value.length > this.config.searchFloor) {
// Filter available choices
this._searchChoices(value);
// Check flag to filter search input
if (this.config.searchChoices) {
// Filter available choices
this._searchChoices(value);
}
// Trigger search event
(0, _utils.triggerEvent)(this.passedElement, 'search', {
value: value

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -60,15 +60,16 @@ class Choices {
delimiter: ',',
paste: true,
search: true,
searchChoices: true,
searchFloor: 1,
searchPlaceholderValue: null,
flip: true,
searchFields: ['label', 'value'],
position: 'auto',
resetScrollPosition: true,
regexFilter: null,
shouldSort: true,
sortFilter: sortByAlpha,
sortFields: ['label', 'value'],
placeholder: true,
placeholderValue: null,
prependValue: null,
appendValue: null,
@ -1224,7 +1225,7 @@ class Choices {
if (newValue.length >= 1 && newValue !== `${currentValue} `) {
const haystack = this.store.getChoicesFilteredBySelectable();
const needle = newValue;
const keys = isType('Array', this.config.sortFields) ? this.config.sortFields : [this.config.sortFields];
const keys = isType('Array', this.config.searchFields) ? this.config.searchFields : [this.config.searchFields];
const options = Object.assign(this.config.fuseOptions, { keys });
const fuse = new Fuse(haystack, options);
const results = fuse.search(needle);
@ -1251,8 +1252,11 @@ class Choices {
if (this.input === document.activeElement) {
// Check that we have a value to search and the input was an alphanumeric character
if (value && value.length > this.config.searchFloor) {
// Filter available choices
this._searchChoices(value);
// Check flag to filter search input
if (this.config.searchChoices) {
// Filter available choices
this._searchChoices(value);
}
// Trigger search event
triggerEvent(this.passedElement, 'search', {
value,

View file

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

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.7.7">
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.7.8">
<!-- 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.7.7">
<script src="assets/scripts/dist/choices.min.js?version=2.7.7"></script>
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.7.8">
<script src="assets/scripts/dist/choices.min.js?version=2.7.8"></script>
<!-- End Choices includes -->
<!--[if lt IE 9]>

View file

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

View file

@ -77,11 +77,10 @@ describe('Choices', () => {
expect(this.choices.config.search).toEqual(jasmine.any(Boolean));
expect(this.choices.config.searchFloor).toEqual(jasmine.any(Number));
expect(this.choices.config.searchPlaceholderValue).toEqual(null);
expect(this.choices.config.flip).toEqual(jasmine.any(Boolean));
expect(this.choices.config.searchFields).toEqual(jasmine.any(Array) || jasmine.any(String));
expect(this.choices.config.position).toEqual(jasmine.any(String));
expect(this.choices.config.regexFilter).toEqual(null);
expect(this.choices.config.sortFilter).toEqual(jasmine.any(Function));
expect(this.choices.config.sortFields).toEqual(jasmine.any(Array) || jasmine.any(String));
expect(this.choices.config.shouldSort).toEqual(jasmine.any(Boolean));
expect(this.choices.config.placeholderValue).toEqual(null);
expect(this.choices.config.prependValue).toEqual(null);
@ -441,7 +440,7 @@ describe('Choices', () => {
passedElement.addEventListener('search', searchSpy);
this.choices.input.focus();
this.choices.input.value = 'Value 3';
this.choices.input.value = '3 ';
// Key down to search
this.choices._onKeyUp({
@ -450,9 +449,41 @@ describe('Choices', () => {
ctrlKey: false
});
const mostAccurateResult = this.choices.currentState.choices[0];
const mostAccurateResult = this.choices.currentState.choices.filter(function (choice) {
return choice.active;
});
expect(this.choices.isSearching && mostAccurateResult.value === 'Value 3').toBeTruthy;
expect(this.choices.isSearching && mostAccurateResult[0].value === 'Value 3').toBe(true);
expect(searchSpy).toHaveBeenCalled();
});
it('shouldn\'t filter choices when searching', function() {
this.choices = new Choices(this.input, {
searchChoices: false
});
this.choices.setValue(['Javascript', 'HTML', 'Jasmine']);
const searchSpy = jasmine.createSpy('searchSpy');
const passedElement = this.choices.passedElement;
passedElement.addEventListener('search', searchSpy);
this.choices.input.focus();
this.choices.input.value = 'Javascript';
// Key down to search
this.choices._onKeyUp({
target: this.choices.input,
keyCode: 13,
ctrlKey: false
});
const activeOptions = this.choices.currentState.choices.filter(function (choice) {
return choice.active;
});
expect(activeOptions.length).toEqual(this.choices.currentState.choices.length);
expect(searchSpy).toHaveBeenCalled();
});