From b5a933a8917b92b7787cd526ea2f7c9dfdd0df6a Mon Sep 17 00:00:00 2001 From: Konstantin Vyatkin Date: Fri, 25 Oct 2019 09:01:39 -0400 Subject: [PATCH] rename addItemFilterFn --- README.md | 349 +++++--- public/index.html | 1425 ++++++++++++++++++++------------- public/test/text.html | 136 ++-- src/scripts/choices.js | 23 +- src/scripts/constants.js | 2 +- src/scripts/constants.test.js | 2 +- types/index.d.ts | 25 +- 7 files changed, 1247 insertions(+), 715 deletions(-) diff --git a/README.md b/README.md index 0b47caed3..81df82801 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,36 @@ # Choices.js [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Unit%20Tests/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![npm](https://img.shields.io/npm/v/choices.js.svg)](https://www.npmjs.com/package/choices.js) [![codebeat badge](https://codebeat.co/badges/55120150-5866-42d8-8010-6aaaff5d3fa1)](https://codebeat.co/projects/jackfan.us.kg-jshjohnson-choices-master) + A vanilla, lightweight (~19kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency. [Demo](https://joshuajohnson.co.uk/Choices/) ## TL;DR -* Lightweight -* No jQuery dependency -* Configurable sorting -* Flexible styling -* Fast search/filtering -* Clean API -* Right-to-left support -* Custom templates - ----- + +- Lightweight +- No jQuery dependency +- Configurable sorting +- Flexible styling +- Fast search/filtering +- Clean API +- Right-to-left support +- Custom templates + +--- + ### Interested in writing your own ES6 JavaScript plugins? Check out [ES6.io](https://ES6.io/friend/JOHNSON) for great tutorials! 💪🏼 ----- + +--- ## Installation + With [NPM](https://www.npmjs.com/package/choices.js): + ```zsh npm install choices.js ``` With [Yarn](https://yarnpkg.com/): + ```zsh yarn add choices.js ``` @@ -34,9 +41,15 @@ From a [CDN](https://www.jsdelivr.com/package/npm/choices.js): ```html - + - + ``` @@ -45,12 +58,13 @@ Or include Choices directly: ```html - + - + ``` + ## Setup If you pass a selector which targets multiple elements, an array of Choices instances @@ -78,7 +92,7 @@ will be returned. If you target one element, that instance will be returned. renderChoiceLimit: -1, maxItemCount: -1, addItems: true, - addItemFilterFn: null, + addItemFilter: null, removeItems: true, removeItemButton: false, editItems: false, @@ -152,24 +166,26 @@ will be returned. If you target one element, that instance will be returned. ``` ## Terminology + | Word | Definition | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Choice | A choice is a value a user can select. A choice would be equivalent to the `` element within a select input. | | Group | A group is a collection of choices. A group should be seen as equivalent to a `` element within a select input. | | Item | An item is an inputted value (text input) or a selected choice (select element). In the context of a select element, an item is equivalent to a selected option element: `` whereas in the context of a text input an item is equivalent to `` | - ## Configuration options + ### silent -**Type:** `Boolean` **Default:** `false` + +**Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-single`, `select-multiple` **Usage:** Optionally suppress console errors and warnings. - ### items -**Type:** `Array` **Default:** `[]` + +**Type:** `Array` **Default:** `[]` **Input types affected:** `text` @@ -198,7 +214,8 @@ Pass an array of objects: ``` ### choices -**Type:** `Array` **Default:** `[]` + +**Type:** `Array` **Default:** `[]` **Input types affected:** `select-one`, `select-multiple` @@ -226,6 +243,7 @@ Pass an array of objects: ``` ### renderChoiceLimit + **Type:** `Number` **Default:** `-1` **Input types affected:** `select-one`, `select-multiple` @@ -233,6 +251,7 @@ Pass an array of objects: **Usage:** The amount of choices to be rendered within the dropdown list ("-1" indicates no limit). This is useful if you have a lot of choices where it is easier for a user to use the search area to find a choice. ### maxItemCount + **Type:** `Number` **Default:** `-1` **Input types affected:** `text`, `select-multiple` @@ -240,6 +259,7 @@ Pass an array of objects: **Usage:** The amount of items a user can input/select ("-1" indicates no limit). ### addItems + **Type:** `Boolean` **Default:** `true` **Input types affected:** `text` @@ -247,6 +267,7 @@ Pass an array of objects: **Usage:** Whether a user can add items. ### removeItems + **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple` @@ -254,6 +275,7 @@ Pass an array of objects: **Usage:** Whether a user can remove items. ### removeItemButton + **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -261,6 +283,7 @@ Pass an array of objects: **Usage:** Whether each item should have a remove button. ### editItems + **Type:** `Boolean` **Default:** `false` **Input types affected:** `text` @@ -268,6 +291,7 @@ Pass an array of objects: **Usage:** Whether a user can edit items. An item's value can be edited by pressing the backspace. ### duplicateItemsAllowed + **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple` @@ -275,6 +299,7 @@ Pass an array of objects: **Usage:** Whether duplicate inputted/chosen items are allowed ### delimiter + **Type:** `String` **Default:** `,` **Input types affected:** `text` @@ -282,6 +307,7 @@ Pass an array of objects: **Usage:** What divides each value. The default delimiter seperates each value with a comma: `"Value 1, Value 2, Value 3"`. ### paste + **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple` @@ -289,21 +315,23 @@ Pass an array of objects: **Usage:** Whether a user can paste into the input. ### searchEnabled + **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-one` -**Usage:** Whether a search area should be shown. **Note:** Multiple select boxes will *always* show search areas. +**Usage:** Whether a search area should be shown. **Note:** Multiple select boxes will _always_ show search areas. ### searchChoices + **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-one` **Usage:** Whether choices should be filtered by input or not. If `false`, the search event will still emit, but choices will not be filtered. - ### searchFields + **Type:** `Array/String` **Default:** `['label', 'value']` **Input types affected:**`select-one`, `select-multiple` @@ -311,6 +339,7 @@ Pass an array of objects: **Usage:** Specify which fields should be used when a user is searching. If you have added custom properties to your choices, you can add these values thus: `['label', 'value', 'customProperties.example']`. ### searchFloor + **Type:** `Number` **Default:** `1` **Input types affected:** `select-one`, `select-multiple` @@ -318,6 +347,7 @@ Pass an array of objects: **Usage:** The minimum length a search value should be before choices are searched. ### searchResultLimit: 4, + **Type:** `Number` **Default:** `4` **Input types affected:** `select-one`, `select-multiple` @@ -325,6 +355,7 @@ Pass an array of objects: **Usage:** The maximum amount of search results to show. ### position + **Type:** `String` **Default:** `auto` **Input types affected:** `select-one`, `select-multiple` @@ -332,31 +363,40 @@ Pass an array of objects: **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` **Input types affected:** `select-multiple` **Usage:** Whether the scroll position should reset after adding an item. -### addItemFilterFn -**Type:** `Function` **Default:** `null` +### addItemFilter + +**Type:** `string | RegExp | Function` **Default:** `null` **Input types affected:** `text` -**Usage:** A filter function that will need to return `true` for a user to successfully add an item. +**Usage:** A RegExp or string (will be passed to RegExp constructor internally) or filter function that will need to return `true` for a user to successfully add an item. **Example:** ```js // Only adds items matching the text test new Choices(element, { - addItemFilterFn: (value) => { - return (value !== 'test'); + addItemFilter: (value) => { + return ['orange', 'apple', 'banana'].includes(value); }; }); + +// only items ending to `-red` +new Choices(element, { + addItemFilter: '-red$'; +}); + ``` ### shouldSort + **Type:** `Boolean` **Default:** `true` **Input types affected:** `select-one`, `select-multiple` @@ -364,6 +404,7 @@ new Choices(element, { **Usage:** Whether choices and groups should be sorted. If false, choices/groups will appear in the order they were given. ### shouldSortItems + **Type:** `Boolean` **Default:** `false` **Input types affected:** `text`, `select-multiple` @@ -371,6 +412,7 @@ new Choices(element, { **Usage:** Whether items should be sorted. If false, items will appear in the order they were selected. ### sortFn + **Type:** `Function` **Default:** sortByAlpha **Input types affected:** `select-one`, `select-multiple` @@ -389,11 +431,12 @@ const example = new Choices(element, { ``` ### placeholder + **Type:** `Boolean` **Default:** `true` **Input types affected:** `text`, `select-multiple` -**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. +**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. **Note:** For single select boxes, the recommended way of adding a placeholder is as follows: @@ -409,6 +452,7 @@ const example = new Choices(element, { For backward compatibility, `` is also supported. ### placeholderValue + **Type:** `String` **Default:** `null` **Input types affected:** `text`, `select-multiple` @@ -416,6 +460,7 @@ For backward compatibility, `` **Usage:** The value of the inputs placeholder. ### searchPlaceholderValue + **Type:** `String` **Default:** `null` **Input types affected:** `select-one` @@ -423,6 +468,7 @@ For backward compatibility, `` **Usage:** The value of the search inputs placeholder. ### prependValue + **Type:** `String` **Default:** `null` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -430,6 +476,7 @@ For backward compatibility, `` **Usage:** Prepend a value to each item added/selected. ### appendValue + **Type:** `String` **Default:** `null` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -437,6 +484,7 @@ For backward compatibility, `` **Usage:** Append a value to each item added/selected. ### renderSelectedChoices + **Type:** `String` **Default:** `auto` **Input types affected:** `select-multiple` @@ -444,6 +492,7 @@ For backward compatibility, `` **Usage:** Whether selected choices should be removed from the list. By default choices are removed when they are selected in multiple select box. To always render choices pass `always`. ### loadingText + **Type:** `String` **Default:** `Loading...` **Input types affected:** `select-one`, `select-multiple` @@ -451,6 +500,7 @@ For backward compatibility, `` **Usage:** The text that is shown whilst choices are being populated via AJAX. ### noResultsText + **Type:** `String/Function` **Default:** `No results found` **Input types affected:** `select-one`, `select-multiple` @@ -458,6 +508,7 @@ For backward compatibility, `` **Usage:** The text that is shown when a user's search has returned no results. Optionally pass a function returning a string. ### noChoicesText + **Type:** `String/Function` **Default:** `No choices to choose from` **Input types affected:** `select-multiple` @@ -465,6 +516,7 @@ For backward compatibility, `` **Usage:** The text that is shown when a user has selected all possible choices. Optionally pass a function returning a string. ### itemSelectText + **Type:** `String` **Default:** `Press to select` **Input types affected:** `select-multiple`, `select-one` @@ -472,6 +524,7 @@ For backward compatibility, `` **Usage:** The text that is shown when a user hovers over a selectable choice. ### addItemText + **Type:** `String/Function` **Default:** `Press Enter to add "${value}"` **Input types affected:** `text` @@ -479,6 +532,7 @@ For backward compatibility, `` **Usage:** The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example), otherwise pass a string. ### maxItemText + **Type:** `String/Function` **Default:** `Only ${maxItemCount} values can be added` **Input types affected:** `text` @@ -486,6 +540,7 @@ For backward compatibility, `` **Usage:** The text that is shown when a user has focus on the input but has already reached the [max item count](https://github.com/jshjohnson/Choices#maxitemcount). To access the max item count, pass a function with a `maxItemCount` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example), otherwise pass a string. ### itemComparer + **Type:** `Function` **Default:** `strict equality` **Input types affected:** `select-one`, `select-multiple` @@ -493,6 +548,7 @@ For backward compatibility, `` **Usage:** Compare choice and value in appropriate way (e.g. deep equality for objects). To compare choice and value, pass a function with a `itemComparer` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example). ### classNames + **Type:** `Object` **Default:** ``` @@ -527,9 +583,11 @@ classNames: { **Usage:** Classes added to HTML generated by Choices. By default classnames follow the [BEM](http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/) notation. ## Callbacks + **Note:** For each callback, `this` refers to the current instance of Choices. This can be useful if you need access to methods (`this.disable()`) or the config object (`this.config`). ### callbackOnInit + **Type:** `Function` **Default:** `null` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -537,6 +595,7 @@ classNames: { **Usage:** Function to run once Choices initialises. ### callbackOnCreateTemplates + **Type:** `Function` **Default:** `null` **Arguments:** `template` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -547,28 +606,45 @@ classNames: { ```js const example = new Choices(element, { - callbackOnCreateTemplates: function (template) { + callbackOnCreateTemplates: function(template) { return { item: (classNames, data) => { return template(` -
+
${data.label}
`); }, choice: (classNames, data) => { return template(` -
0 ? 'role="treeitem"' : 'role="option"'}> +
0 ? 'role="treeitem"' : 'role="option"' + }> ${data.label}
`); }, }; - } + }, }); ``` ## 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:** @@ -577,29 +653,38 @@ const example = new Choices(element, { 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.label); - console.log(event.detail.customProperties); - console.log(event.detail.groupValue); -}, false); +element.addEventListener( + 'addItem', + function(event) { + // do something creative here... + console.log(event.detail.id); + console.log(event.detail.value); + console.log(event.detail.label); + console.log(event.detail.customProperties); + console.log(event.detail.groupValue); + }, + false, +); // or const example = new Choices(document.getElementById('example')); -example.passedElement.element.addEventListener('addItem', function(event) { - // do something creative here... - console.log(event.detail.id); - console.log(event.detail.value); - console.log(event.detail.label); - console.log(event.detail.customProperties); - console.log(event.detail.groupValue); -}, false); +example.passedElement.element.addEventListener( + 'addItem', + function(event) { + // do something creative here... + console.log(event.detail.id); + console.log(event.detail.value); + console.log(event.detail.label); + console.log(event.detail.customProperties); + console.log(event.detail.groupValue); + }, + false, +); ``` ### addItem + **Arguments:** `id, value, label, groupValue, keyCode` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -607,6 +692,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered each time an item is added (programmatically or by the user). ### removeItem + **Arguments:** `id, value, label, groupValue` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -614,6 +700,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered each time an item is removed (programmatically or by the user). ### highlightItem + **Arguments:** `id, value, label, groupValue` **Input types affected:** `text`, `select-multiple` @@ -621,6 +708,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered each time an item is highlighted. ### unhighlightItem + **Arguments:** `id, value, label, groupValue` **Input types affected:** `text`, `select-multiple` @@ -628,6 +716,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered each time an item is unhighlighted. ### choice + **Arguments:** `value, keyCode` **Input types affected:** `select-one`, `select-multiple` @@ -635,6 +724,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input. ### change + **Arguments:** `value` **Input types affected:** `text`, `select-one`, `select-multiple` @@ -642,6 +732,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered each time an item is added/removed **by a user**. ### search + **Arguments:** `value`, `resultCount` **Input types affected:** `select-one`, `select-multiple` @@ -649,6 +740,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered when a user types into an input to search choices. ### showDropdown + **Arguments:** - **Input types affected:** `select-one`, `select-multiple` @@ -656,6 +748,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered when the dropdown is shown. ### hideDropdown + **Arguments:** - **Input types affected:** `select-one`, `select-multiple` @@ -663,6 +756,7 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered when the dropdown is hidden. ### highlightChoice + **Arguments:** `el` **Input types affected:** `select-one`, `select-multiple` @@ -670,14 +764,15 @@ example.passedElement.element.addEventListener('addItem', function(event) { **Usage:** Triggered when a choice from the dropdown is highlighted. The `el` argument is the HTML element node object that was affected. ## Methods + Methods can be called either directly or by chaining: ```js // Calling a method by chaining const choices = new Choices(element, { - addItems: false, - removeItems: false, - }) + addItems: false, + removeItems: false, +}) .setValue(['Set value 1', 'Set value 2']) .disable(); @@ -687,16 +782,18 @@ const choices = new Choices(element, { removeItems: false, }); -choices.setValue(['Set value 1', 'Set value 2']) +choices.setValue(['Set value 1', 'Set value 2']); choices.disable(); ``` ### destroy(); + **Input types affected:** `text`, `select-multiple`, `select-one` **Usage:** Kills the instance of Choices, removes all event listeners and returns passed input to its initial state. ### init(); + **Input types affected:** `text`, `select-multiple`, `select-one` **Usage:** Creates a new instance of Choices, adds event listeners, creates templates and renders a Choices element to the DOM. @@ -704,47 +801,49 @@ choices.disable(); **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` **Usage:** Highlight each chosen item (selected items can be removed). - ### unhighlightAll(); + **Input types affected:** `text`, `select-multiple` **Usage:** Un-highlight each chosen item. - ### removeActiveItemsByValue(value); + **Input types affected:** `text`, `select-multiple` **Usage:** Remove each item by a given value. - ### removeActiveItems(excludedId); + **Input types affected:** `text`, `select-multiple` **Usage:** Remove each selectable item. - ### removeHighlightedItems(); + **Input types affected:** `text`, `select-multiple` **Usage:** Remove each item the user has selected. - ### showDropdown(); + **Input types affected:** `select-one`, `select-multiple` **Usage:** Show option list dropdown (only affects select inputs). - ### hideDropdown(); + **Input types affected:** `text`, `select-multiple` **Usage:** Hide option list dropdown (only affects select inputs). ### setChoices(choices, value, label, replaceChoices); + **Input types affected:** `select-one`, `select-multiple` **Usage:** Set choices of select input via an array of objects, a value name and a label name. This behaves the same as passing items via the `choices` option but can be called after initialising Choices. This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc). Passing an empty array as the first parameter, and a true `replaceChoices` is the same as calling `clearChoices` (see below). @@ -754,11 +853,16 @@ choices.disable(); ```js const example = new Choices(element); -example.setChoices([ - {value: 'One', label: 'Label One', disabled: true}, - {value: 'Two', label: 'Label Two', selected: true}, - {value: 'Three', label: 'Label Three'}, -], 'value', 'label', false); +example.setChoices( + [ + { value: 'One', label: 'Label One', disabled: true }, + { value: 'Two', label: 'Label Two', selected: true }, + { value: 'Three', label: 'Label Three' }, + ], + 'value', + 'label', + false, +); ``` **Example 2:** @@ -766,37 +870,50 @@ example.setChoices([ ```js const example = new Choices(element); -example.setChoices([{ - label: 'Group one', - id: 1, - disabled: false, - choices: [ - {value: 'Child One', label: 'Child One', selected: true}, - {value: 'Child Two', label: 'Child Two', disabled: true}, - {value: 'Child Three', label: 'Child Three'}, - ] -}, -{ - label: 'Group two', - id: 2, - disabled: false, - choices: [ - {value: 'Child Four', label: 'Child Four', disabled: true}, - {value: 'Child Five', label: 'Child Five'}, - {value: 'Child Six', label: 'Child Six', customProperties: { - description: 'Custom description about child six', - random: 'Another random custom property' - }}, - ] -}], 'value', 'label', false); +example.setChoices( + [ + { + label: 'Group one', + id: 1, + disabled: false, + choices: [ + { value: 'Child One', label: 'Child One', selected: true }, + { value: 'Child Two', label: 'Child Two', disabled: true }, + { value: 'Child Three', label: 'Child Three' }, + ], + }, + { + label: 'Group two', + id: 2, + disabled: false, + choices: [ + { value: 'Child Four', label: 'Child Four', disabled: true }, + { value: 'Child Five', label: 'Child Five' }, + { + value: 'Child Six', + label: 'Child Six', + customProperties: { + description: 'Custom description about child six', + random: 'Another random custom property', + }, + }, + ], + }, + ], + 'value', + 'label', + false, +); ``` ### clearChoices(); + **Input types affected:** `select-one`, `select-multiple` **Usage:** Clear all choices from select ### getValue(valueOnly) + **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Get value(s) of input (i.e. inputted items (text) or selected choices (select)). Optionally pass an argument of `true` to only return values rather than value objects. @@ -810,6 +927,7 @@ const valueArray = example.getValue(); // returns [{ active: true, choiceId: 1, ``` ### setValue(items); + **Input types affected:** `text` **Usage:** Set value of input based on an array of objects or strings. This behaves exactly the same as passing items via the `items` option but can be called after initialising Choices. @@ -821,16 +939,17 @@ const example = new Choices(element); // via an array of objects example.setValue([ - {value: 'One', label: 'Label One'}, - {value: 'Two', label: 'Label Two'}, - {value: 'Three', label: 'Label Three'}, + { value: 'One', label: 'Label One' }, + { value: 'Two', label: 'Label Two' }, + { value: 'Three', label: 'Label Three' }, ]); // or via an array of strings -example.setValue(['Four','Five','Six']); +example.setValue(['Four', 'Five', 'Six']); ``` ### setChoiceByValue(value); + **Input types affected:** `select-one`, `select-multiple` **Usage:** Set value of input based on existing Choice. `value` can be either a single string or an array of strings @@ -840,9 +959,9 @@ example.setValue(['Four','Five','Six']); ```js const example = new Choices(element, { choices: [ - {value: 'One', label: 'Label One'}, - {value: 'Two', label: 'Label Two', disabled: true}, - {value: 'Three', label: 'Label Three'}, + { value: 'One', label: 'Label One' }, + { value: 'Two', label: 'Label Two', disabled: true }, + { value: 'Three', label: 'Label Three' }, ], }); @@ -850,29 +969,31 @@ example.setChoiceByValue('Two'); // Choice with value of 'Two' has now been sele ``` ### clearStore(); + **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Removes all items, choices and groups. Use with caution. - ### clearInput(); + **Input types affected:** `text` **Usage:** Clear input of any user inputted text. - ### disable(); + **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Disables input from accepting new value/selecting further choices. ### enable(); + **Input types affected:** `text`, `select-one`, `select-multiple` **Usage:** Enables input to accept new values/select further choices. - ### ajax(fn); + **Input types affected:** `select-one`, `select-multiple` **Usage:** Populate choices/groups via a callback. @@ -899,7 +1020,7 @@ example.ajax(function(callback) { If your structure differs from `data.value` and `data.key` structure you can write your own `key` and `value` into the `callback` function. This could be useful when you don't want to transform the given response. ```js -const example = new Choices(element) +const example = new Choices(element); example.ajax(function(callback) { fetch(url) @@ -915,6 +1036,7 @@ example.ajax(function(callback) { ``` ## Browser compatibility + Choices is compiled using [Babel](https://babeljs.io/) to enable support for [ES5 browsers](http://caniuse.com/#feat=es5). If you need to support a browser that does not support one of the features listed below, I suggest including a polyfill from the very good [polyfill.io](https://cdn.polyfill.io/v2/docs/): **Polyfill example used for the demo:** @@ -925,25 +1047,27 @@ Choices is compiled using [Babel](https://babeljs.io/) to enable support for [ES **Features used in Choices:** -* Array.prototype.forEach -* Array.prototype.map -* Array.prototype.find -* Array.prototype.some -* Array.prototype.includes -* Array.from -* Array.prototype.reduce -* Array.prototype.indexOf -* Object.assign -* Element.prototype.classList -* window.requestAnimationFrame -* CustomEvent +- Array.prototype.forEach +- Array.prototype.map +- Array.prototype.find +- Array.prototype.some +- Array.prototype.includes +- Array.from +- Array.prototype.reduce +- Array.prototype.indexOf +- Object.assign +- Element.prototype.classList +- window.requestAnimationFrame +- CustomEvent ## Development + To setup a local environment: clone this repo, navigate into it's directory in a terminal window and run the following command: -```npm install``` +`npm install` ### NPM tasks + | Task | Usage | | ------------------------- | ------------------------------------------------------------ | | `npm run start` | Fire up local server for development | @@ -958,10 +1082,13 @@ To setup a local environment: clone this repo, navigate into it's directory in a | `npm run css:build` | Compile, minify and prefix SCSS files to CSS | ## License + MIT License ## Web component + Want to use Choices as a web component? You're in luck. Adidas have built one for their design system which can be found [here](https://github.com/adidas/choicesjs-stencil). ## 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! diff --git a/public/index.html b/public/index.html index 579ebfe2f..c943526b5 100644 --- a/public/index.html +++ b/public/index.html @@ -1,590 +1,935 @@ - - - - - Choices - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - -
-
- -

Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without - the jQuery dependency.

-

For all config options, visit the GitHub repo.

- -
+ + + +
+
+ +

+ Choices.js is a lightweight, configurable select box/text input + plugin. Similar to Select2 and Selectize but without the jQuery + dependency. +

+

+ For all config options, visit the + GitHub repo. +

+ +

- Interested in writing your own ES6 JavaScript plugins? Check out ES6.io for great tutorials! 💪🏼 + Interested in writing your own ES6 JavaScript plugins? Check out + ES6.io for great + tutorials! 💪🏼

-
- -

Text inputs

- - - - - - - - - - - - - - - - - - - - - - - - -
- -

Multiple select input

- - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
+ +

Multiple select input

+ + + + + + + + + +

+ If the following example do not load, the Discogs rate limit has + probably been reached. Try again later! +

+ + + + + + + + +

+ +
+ +

Single select input

+ + + +

+ If the following two examples do not load, the Discogs rate limit + has probably been reached. Try again later! +

+ + + + + + + + + + + + + + + + + + + + +

+ Try searching for 'fantastic', "Label 3" should display +

+ + + +

+ Try searching for 'fantastic', "Label 3" should display +

+ + + + - -

If the following example do not load, the Discogs rate limit has probably been reached. Try again later!

- - - - - - - - -

- -
- -

Single select input

- - - -

If the following two examples do not load, the Discogs rate limit has probably been reached. Try again later!

- - - - - - - - - - - - - - - - - - - - -

Try searching for 'fantastic', "Label 3" should display

- - - -

Try searching for 'fantastic', "Label 3" should display

- - - - - - - - -

Below is an example of how you could have two select inputs depend on eachother. 'Tube stations' will only be enabled if - the value of 'Cities' is 'London'

- - - - - - -
-

Form interaction

-

Change the values and press reset to restore to initial state.

-
- - - - + + + + - -
-
-
- - - - - - - - + + + + + + + diff --git a/public/test/text.html b/public/test/text.html index f2e80034d..10404035d 100644 --- a/public/test/text.html +++ b/public/test/text.html @@ -1,28 +1,58 @@ - - - - - Choices - - - - - - - - - - - - - - - - - - + + + + Choices + + + + + + + + + + + + + + + + + + @@ -31,63 +61,76 @@

Text inputs

- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
@@ -114,8 +157,8 @@

Text inputs

new Choices('#choices-add-item-filter', { addItems: true, - addItemFilterFn: (value) => { - const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + addItemFilter: value => { + const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const expression = new RegExp(regex.source, 'i'); return expression.test(value); }, @@ -133,13 +176,16 @@

Text inputs

}); new Choices('#choices-prepopulated', { - items: ['Josh Johnson', { - value: 'joe@bloggs.co.uk', - label: 'Joe Bloggs', - customProperties: { - description: 'Joe Blogg is such a generic name', - } - }], + items: [ + 'Josh Johnson', + { + value: 'joe@bloggs.co.uk', + label: 'Joe Bloggs', + customProperties: { + description: 'Joe Blogg is such a generic name', + }, + }, + ], }); new Choices('#choices-placeholder', { @@ -151,4 +197,4 @@

Text inputs

}); - + diff --git a/src/scripts/choices.js b/src/scripts/choices.js index ee8e0001e..f86a43fc0 100644 --- a/src/scripts/choices.js +++ b/src/scripts/choices.js @@ -62,6 +62,18 @@ class Choices { { arrayMerge: (destinationArray, sourceArray) => [...sourceArray] }, ); + // Convert addItemFilter to function + if ( + userConfig.addItemFilter && + typeof userConfig.addItemFilter !== 'function' + ) { + const re = + userConfig.addItemFilter instanceof RegExp + ? userConfig.addItemFilter + : new RegExp(userConfig.addItemFilter); + this.config.addItemFilter = re.test.bind(re); + } + const invalidConfigOptions = diff(this.config, DEFAULT_CONFIG); if (invalidConfigOptions.length) { console.warn( @@ -993,13 +1005,14 @@ class Choices { this._isTextElement && this.config.addItems && canAddItem && - isType('Function', this.config.addItemFilterFn) && - !this.config.addItemFilterFn(value) + typeof this.config.addItemFilter === 'function' && + !this.config.addItemFilter(value) ) { canAddItem = false; - notice = isType('Function', this.config.customAddItemText) - ? this.config.customAddItemText(value) - : this.config.customAddItemText; + notice = + typeof this.config.customAddItemText === 'function' + ? this.config.customAddItemText(value) + : this.config.customAddItemText; } } diff --git a/src/scripts/constants.js b/src/scripts/constants.js index dd8192cc1..4699361d4 100644 --- a/src/scripts/constants.js +++ b/src/scripts/constants.js @@ -35,7 +35,7 @@ export const DEFAULT_CONFIG = { renderChoiceLimit: -1, maxItemCount: -1, addItems: true, - addItemFilterFn: null, + addItemFilter: null, removeItems: true, removeItemButton: false, editItems: false, diff --git a/src/scripts/constants.test.js b/src/scripts/constants.test.js index 9061f2074..1accbcde9 100644 --- a/src/scripts/constants.test.js +++ b/src/scripts/constants.test.js @@ -55,7 +55,7 @@ describe('constants', () => { expect(DEFAULT_CONFIG.renderChoiceLimit).to.be.a('number'); expect(DEFAULT_CONFIG.maxItemCount).to.be.a('number'); expect(DEFAULT_CONFIG.addItems).to.be.a('boolean'); - expect(DEFAULT_CONFIG.addItemFilterFn).to.equal(null); + expect(DEFAULT_CONFIG.addItemFilter).to.equal(null); expect(DEFAULT_CONFIG.removeItems).to.be.a('boolean'); expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean'); expect(DEFAULT_CONFIG.editItems).to.be.a('boolean'); diff --git a/types/index.d.ts b/types/index.d.ts index 49c6964d3..4324651e5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -11,11 +11,12 @@ declare namespace Choices { type renderSelected = 'auto' | 'always'; type dropdownPosition = 'auto' | 'top'; type strToEl = ( - str: string + str: string, ) => HTMLElement | HTMLInputElement | HTMLOptionElement; type stringFunction = () => string; type noticeStringFunction = (value: string) => string; type noticeLimitFunction = (maxItemCount: number) => string; + type filterFunction = (value: string) => boolean; type callbackOnCreateTemplates = (template: strToEl) => Choices.Templates; } @@ -135,17 +136,17 @@ declare namespace Choices { containerInner?: (classNames: ClassNames) => HTMLElement; itemList?: ( classNames: ClassNames, - isSelectOneElement: boolean + isSelectOneElement: boolean, ) => HTMLElement; placeholder?: (classNames: ClassNames, value: string) => HTMLElement; item?: ( classNames: ClassNames, data: Choice, - removeItemButton: boolean + removeItemButton: boolean, ) => HTMLElement; choiceList?: ( classNames: ClassNames, - isSelectOneElement: boolean + isSelectOneElement: boolean, ) => HTMLElement; choiceGroup?: (classNames: ClassNames, data: Choice) => HTMLElement; choice?: (classNames: ClassNames, data: Choice) => HTMLElement; @@ -217,9 +218,9 @@ declare namespace Choices { type: K, listener: ( this: HTMLInputElement | HTMLSelectElement, - ev: Choices.EventMap[K] + ev: Choices.EventMap[K], ) => void, - options?: boolean | AddEventListenerOptions + options?: boolean | AddEventListenerOptions, ): void; }; isDisabled: boolean; @@ -339,7 +340,7 @@ declare namespace Choices { * * @default null */ - addItemFilterFn?: (value: string) => boolean; + addItemFilter?: string | RegExp | Choices.Types.filterFunction; /** * The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the **default config** [https://github.com/jshjohnson/Choices#setup] for an example), otherwise pass a string. @@ -722,7 +723,7 @@ export default class Choices { constructor( selectorOrElement: string | HTMLInputElement | HTMLSelectElement, - userConfig?: Choices.Options + userConfig?: Choices.Options, ); /** @@ -895,7 +896,7 @@ export default class Choices { choices: Choices.Choice[], value: string, label: string, - replaceChoices?: boolean + replaceChoices?: boolean, ): this; /** @@ -954,20 +955,20 @@ export default class Choices { private createGroupsFragment( groups: Choices.Group[], choices: Choices.Choice[], - fragment: DocumentFragment + fragment: DocumentFragment, ): DocumentFragment; /** Render choices into a DOM fragment and append to choice list */ private createChoicesFragment( choices: Choices.Choice[], fragment: DocumentFragment, - withinGroup?: boolean + withinGroup?: boolean, ): DocumentFragment; /** Render items into a DOM fragment and append to items list */ private _createItemsFragment( items: Choices.Item[], - fragment?: DocumentFragment + fragment?: DocumentFragment, ): void; /** Render DOM with values */