[MAJOR] Remove .ajax and allow async function for .setChoices (#701)

* WIP: remove ajax

* copy #700

* remove ajax, add fetchChoices

* extend setChoices

* update README
This commit is contained in:
Konstantin Vyatkin 2019-10-29 14:12:32 -04:00 committed by Josh Johnson
parent 1f5192b4ad
commit e3cc6eaf1b
9 changed files with 631 additions and 425 deletions

View file

@ -863,7 +863,9 @@ choices.disable();
**Input types affected:** `select-one`, `select-multiple` **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). **Usage:** Set choices of select input via an array of objects (or function that returns array of object or promise of it), a value field name and a label field name.
This behaves the similar 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 3); 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).
**Example 1:** **Example 1:**
@ -887,6 +889,22 @@ example.setChoices(
```js ```js
const example = new Choices(element); const example = new Choices(element);
// Passing a function that returns Promise of choices
example.setChoices(async () => {
try {
const items = await fetch('/items');
return items.json();
} catch (err) {
console.error(err);
}
});
```
**Example 3:**
```js
const example = new Choices(element);
example.setChoices( example.setChoices(
[ [
{ {
@ -1009,49 +1027,6 @@ example.setChoiceByValue('Two'); // Choice with value of 'Two' has now been sele
**Usage:** Enables input to accept new values/select further choices. **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.
**Example:**
```js
var example = new Choices(element);
example.ajax(function(callback) {
fetch(url)
.then(function(response) {
response.json().then(function(data) {
callback(data, 'value', 'label');
});
})
.catch(function(error) {
console.log(error);
});
});
```
**Example 2:**
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);
example.ajax(function(callback) {
fetch(url)
.then(function(response) {
response.json().then(function(data) {
callback(data, 'data.key', 'data.value');
});
})
.catch(function(error) {
console.log(error);
});
});
```
## Browser compatibility ## 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/): 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/):

View file

@ -320,7 +320,7 @@
</select> </select>
<label for="choices-single-remove-xhr" <label for="choices-single-remove-xhr"
>Options from remote source (XHR) &amp; remove button</label >Options from remote source (Fetch API) &amp; remove button</label
> >
<select <select
class="form-control" class="form-control"
@ -627,17 +627,17 @@
placeholder: true, placeholder: true,
placeholderValue: 'Pick an Strokes record', placeholderValue: 'Pick an Strokes record',
maxItemCount: 5, maxItemCount: 5,
}).ajax(function(callback) { }).setChoices(function() {
fetch( return fetch(
'https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW', 'https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW',
) )
.then(function(response) { .then(function(response) {
response.json().then(function(data) { return response.json();
callback(data.releases, 'title', 'title');
});
}) })
.catch(function(error) { .then(function(data) {
console.error(error); return data.releases.map(function(release) {
return { value: release.title, label: release.title };
});
}); });
}); });
@ -685,46 +685,39 @@
var singleFetch = new Choices('#choices-single-remote-fetch', { var singleFetch = new Choices('#choices-single-remote-fetch', {
searchPlaceholderValue: 'Search for an Arctic Monkeys record', searchPlaceholderValue: 'Search for an Arctic Monkeys record',
}).ajax(function(callback) { })
fetch( .setChoices(function() {
'https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW', return fetch(
) 'https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW',
.then(function(response) { )
response.json().then(function(data) { .then(function(response) {
callback(data.releases, 'title', 'title'); return response.json();
singleFetch.setChoiceByValue('Fake Tales Of San Francisco'); })
.then(function(data) {
return data.releases.map(function(release) {
return { label: release.title, value: release.title };
});
}); });
}) })
.catch(function(error) { .then(function(instance) {
console.error(error); instance.setChoiceByValue('Fake Tales Of San Francisco');
}); });
});
var singleXhrRemove = new Choices('#choices-single-remove-xhr', { var singleXhrRemove = new Choices('#choices-single-remove-xhr', {
removeItemButton: true, removeItemButton: true,
searchPlaceholderValue: "Search for a Smiths' record", searchPlaceholderValue: "Search for a Smiths' record",
}).ajax(function(callback) { }).setChoices(function(callback) {
var request = new XMLHttpRequest(); return fetch(
request.open(
'get',
'https://api.discogs.com/artists/83080/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW', 'https://api.discogs.com/artists/83080/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW',
true, )
); .then(function(res) {
request.onreadystatechange = function() { return res.json();
var status; })
var data; .then(function(data) {
if (request.readyState === 4) { return data.releases.map(function(release) {
status = request.status; return { label: release.title, value: release.title };
if (status === 200) { });
data = JSON.parse(request.responseText); });
callback(data.releases, 'title', 'title');
singleXhrRemove.setChoiceByValue('How Soon Is Now?');
} else {
console.error(status);
}
}
};
request.send();
}); });
var genericExamples = new Choices('[data-trigger]', { var genericExamples = new Choices('[data-trigger]', {

View file

@ -1,28 +1,58 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,user-scalable=no"
/>
<title>Choices</title>
<meta
name="description"
itemprop="description"
content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency."
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="../assets/images/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
href="../assets/images/favicon-32x32.png"
sizes="32x32"
/>
<link
rel="icon"
type="image/png"
href="../assets/images/favicon-16x16.png"
sizes="16x16"
/>
<link rel="manifest" href="../assets/images/manifest.json" />
<link
rel="mask-icon"
href="../assets/images/safari-pinned-tab.svg"
color="#00bcd4"
/>
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
<meta
name="msapplication-config"
content="../assets/images/browserconfig.xml"
/>
<meta name="theme-color" content="#ffffff" />
<head> <!-- Ignore these -->
<meta charset="UTF-8"> <link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> <!-- End ignore these -->
<title>Choices</title>
<meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.">
<link rel="apple-touch-icon" sizes="180x180" href="../assets/images/apple-touch-icon.png">
<link rel="icon" type="image/png" href="../assets/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="../assets/images/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="../assets/images/manifest.json">
<link rel="mask-icon" href="../assets/images/safari-pinned-tab.svg" color="#00bcd4">
<link rel="shortcut icon" href="../assets/images/favicon.ico">
<meta name="msapplication-config" content="../assets/images/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- Ignore these --> <!-- Choices includes -->
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3"> <link
<!-- End ignore these --> rel="stylesheet"
href="../assets/styles/choices.min.css?version=6.0.3"
<!-- Choices includes --> />
<link rel="stylesheet" href="../assets/styles/choices.min.css?version=6.0.3"> <script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script> <!-- End Choices includes -->
<!-- End Choices includes -->
</head> </head>
<body> <body>
@ -33,7 +63,12 @@
<label for="choices-basic">Basic</label> <label for="choices-basic">Basic</label>
<button class="disable push-bottom">Disable</button> <button class="disable push-bottom">Disable</button>
<button class="enable push-bottom">Enable</button> <button class="enable push-bottom">Enable</button>
<select class="form-control" name="choices-basic" id="choices-basic" multiple> <select
class="form-control"
name="choices-basic"
id="choices-basic"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Find me">Choice 3</option> <option value="Find me">Choice 3</option>
@ -43,7 +78,12 @@
<div data-test-hook="remove-button"> <div data-test-hook="remove-button">
<label for="choices-remove-button">Remove button</label> <label for="choices-remove-button">Remove button</label>
<select class="form-control" name="choices-remove-button" id="choices-remove-button" multiple> <select
class="form-control"
name="choices-remove-button"
id="choices-remove-button"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -53,7 +93,12 @@
<div data-test-hook="disabled-choice"> <div data-test-hook="disabled-choice">
<label for="choices-disabled-choice">Disabled choice</label> <label for="choices-disabled-choice">Disabled choice</label>
<select class="form-control" name="choices-disabled-choice" id="choices-disabled-choice" multiple> <select
class="form-control"
name="choices-disabled-choice"
id="choices-disabled-choice"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -63,7 +108,12 @@
<div data-test-hook="add-items-disabled"> <div data-test-hook="add-items-disabled">
<label for="choices-add-items-disabled">Add items disabled</label> <label for="choices-add-items-disabled">Add items disabled</label>
<select class="form-control" name="choices-add-items-disabled" id="choices-add-items-disabled" multiple> <select
class="form-control"
name="choices-add-items-disabled"
id="choices-add-items-disabled"
multiple
>
<option value="Choice 1" selected>Choice 1</option> <option value="Choice 1" selected>Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -72,7 +122,13 @@
<div data-test-hook="disabled-via-attr"> <div data-test-hook="disabled-via-attr">
<label for="choices-disabled-via-attr">Disabled via attribute</label> <label for="choices-disabled-via-attr">Disabled via attribute</label>
<select class="form-control" name="choices-disabled-via-attr" id="choices-disabled-via-attr" multiple disabled> <select
class="form-control"
name="choices-disabled-via-attr"
id="choices-disabled-via-attr"
multiple
disabled
>
<option value="Choice 1" selected>Choice 1</option> <option value="Choice 1" selected>Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -81,7 +137,12 @@
<div data-test-hook="selection-limit"> <div data-test-hook="selection-limit">
<label for="choices-selection-limit">Input limit</label> <label for="choices-selection-limit">Input limit</label>
<select class="form-control" name="choices-selection-limit" id="choices-selection-limit" multiple> <select
class="form-control"
name="choices-selection-limit"
id="choices-selection-limit"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -93,7 +154,12 @@
<div data-test-hook="prepend-append"> <div data-test-hook="prepend-append">
<label for="choices-prepend-append">Prepend/append</label> <label for="choices-prepend-append">Prepend/append</label>
<select class="form-control" name="choices-prepend-append" id="choices-prepend-append" multiple> <select
class="form-control"
name="choices-prepend-append"
id="choices-prepend-append"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -102,7 +168,12 @@
<div data-test-hook="render-choice-limit"> <div data-test-hook="render-choice-limit">
<label for="choices-render-choice-limit">Render choice limit</label> <label for="choices-render-choice-limit">Render choice limit</label>
<select class="form-control" name="choices-render-choice-limit" id="choices-render-choice-limit" multiple> <select
class="form-control"
name="choices-render-choice-limit"
id="choices-render-choice-limit"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -111,7 +182,12 @@
<div data-test-hook="search-floor"> <div data-test-hook="search-floor">
<label for="choices-search-floor">Search floor</label> <label for="choices-search-floor">Search floor</label>
<select class="form-control" name="choices-search-floor" id="choices-search-floor" multiple> <select
class="form-control"
name="choices-search-floor"
id="choices-search-floor"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -120,7 +196,12 @@
<div data-test-hook="placeholder"> <div data-test-hook="placeholder">
<label for="choices-placeholder">Placeholder</label> <label for="choices-placeholder">Placeholder</label>
<select class="form-control" name="choices-placeholder" id="choices-placeholder" multiple> <select
class="form-control"
name="choices-placeholder"
id="choices-placeholder"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -129,12 +210,22 @@
<div data-test-hook="remote-data"> <div data-test-hook="remote-data">
<label for="choices-remote-data">Remote data</label> <label for="choices-remote-data">Remote data</label>
<select class="form-control" name="choices-remote-data" id="choices-remote-data" multiple></select> <select
class="form-control"
name="choices-remote-data"
id="choices-remote-data"
multiple
></select>
</div> </div>
<div data-test-hook="scrolling-dropdown"> <div data-test-hook="scrolling-dropdown">
<label for="choices-scrolling-dropdown">Scrolling dropdown</label> <label for="choices-scrolling-dropdown">Scrolling dropdown</label>
<select class="form-control" name="choices-scrolling-dropdown" id="choices-scrolling-dropdown" multiple> <select
class="form-control"
name="choices-scrolling-dropdown"
id="choices-scrolling-dropdown"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -155,7 +246,12 @@
<div data-test-hook="groups"> <div data-test-hook="groups">
<label for="choices-groups">Choice groups</label> <label for="choices-groups">Choice groups</label>
<select class="form-control" name="choices-groups" id="choices-groups" multiple> <select
class="form-control"
name="choices-groups"
id="choices-groups"
multiple
>
<optgroup label="UK"> <optgroup label="UK">
<option value="London">London</option> <option value="London">London</option>
<option value="Manchester">Manchester</option> <option value="Manchester">Manchester</option>
@ -171,26 +267,47 @@
<div data-test-hook="custom-properties"> <div data-test-hook="custom-properties">
<label for="choices-custom-properties">Custom properties</label> <label for="choices-custom-properties">Custom properties</label>
<select class="form-control" name="choices-custom-properties" id="choices-custom-properties" multiple></select> <select
class="form-control"
name="choices-custom-properties"
id="choices-custom-properties"
multiple
></select>
</div> </div>
<div data-test-hook="non-string-values"> <div data-test-hook="non-string-values">
<label for="choices-non-string-values">Non-string values</label> <label for="choices-non-string-values">Non-string values</label>
<select class="form-control" name="choices-non-string-values" id="choices-non-string-values"></select> <select
class="form-control"
name="choices-non-string-values"
id="choices-non-string-values"
></select>
</div> </div>
<div data-test-hook="within-form"> <div data-test-hook="within-form">
<form> <form>
<label for="choices-within-form">Within form</label> <label for="choices-within-form">Within form</label>
<select class="form-control" name="choices-within-form" id="choices-within-form" multiple> <select
class="form-control"
name="choices-within-form"
id="choices-within-form"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
</select> </select>
</form> </form>
</div> </div>
<div data-test-hook="set-choice-by-value"> <div data-test-hook="set-choice-by-value">
<label for="choices-set-choice-by-value">Dynamically set choice by value</label> <label for="choices-set-choice-by-value"
<select class="form-control" name="choices-set-choice-by-value" id="choices-set-choice-by-value" multiple> >Dynamically set choice by value</label
>
<select
class="form-control"
name="choices-set-choice-by-value"
id="choices-set-choice-by-value"
multiple
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -199,7 +316,12 @@
<div data-test-hook="search-by-label"> <div data-test-hook="search-by-label">
<label for="choices-search-by-label">Search by label</label> <label for="choices-search-by-label">Search by label</label>
<select class="form-control" name="choices-search-by-label" id="choices-search-by-label" multiple> <select
class="form-control"
name="choices-search-by-label"
id="choices-search-by-label"
multiple
>
<option value="value1">label1</option> <option value="value1">label1</option>
<option value="value2">label2</option> <option value="value2">label2</option>
</select> </select>
@ -210,13 +332,17 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const choicesBasic = new Choices('#choices-basic'); const choicesBasic = new Choices('#choices-basic');
document.querySelector('button.disable').addEventListener('click', () => { document
choicesBasic.disable(); .querySelector('button.disable')
}); .addEventListener('click', () => {
choicesBasic.disable();
});
document.querySelector('button.enable').addEventListener('click', () => { document
choicesBasic.enable(); .querySelector('button.enable')
}); .addEventListener('click', () => {
choicesBasic.enable();
});
new Choices('#choices-remove-button', { new Choices('#choices-remove-button', {
removeItemButton: true, removeItemButton: true,
@ -254,16 +380,9 @@
new Choices('#choices-remote-data', { new Choices('#choices-remote-data', {
shouldSort: false, shouldSort: false,
}).ajax((callback) => { }).setChoices(async () => {
fetch('/data') const data = await fetch('/data');
.then((response) => { return data.json();
response.json().then((data) => {
callback(data, 'value', 'label');
});
})
.catch((error) => {
console.error(error);
});
}); });
new Choices('#choices-scrolling-dropdown', { new Choices('#choices-scrolling-dropdown', {
@ -331,10 +450,12 @@
new Choices('#choices-within-form'); new Choices('#choices-within-form');
new Choices('#choices-set-choice-by-value').setChoiceByValue('Choice 2'); new Choices('#choices-set-choice-by-value').setChoiceByValue(
'Choice 2',
);
new Choices('#choices-search-by-label', { searchFields: ['label'] }); new Choices('#choices-search-by-label', { searchFields: ['label'] });
}); });
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,28 +1,58 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,user-scalable=no"
/>
<title>Choices</title>
<meta
name="description"
itemprop="description"
content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency."
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="../assets/images/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
href="../assets/images/favicon-32x32.png"
sizes="32x32"
/>
<link
rel="icon"
type="image/png"
href="../assets/images/favicon-16x16.png"
sizes="16x16"
/>
<link rel="manifest" href="../assets/images/manifest.json" />
<link
rel="mask-icon"
href="../assets/images/safari-pinned-tab.svg"
color="#00bcd4"
/>
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
<meta
name="msapplication-config"
content="../assets/images/browserconfig.xml"
/>
<meta name="theme-color" content="#ffffff" />
<head> <!-- Ignore these -->
<meta charset="UTF-8"> <link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> <!-- End ignore these -->
<title>Choices</title>
<meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.">
<link rel="apple-touch-icon" sizes="180x180" href="../assets/images/apple-touch-icon.png">
<link rel="icon" type="image/png" href="../assets/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="../assets/images/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="../assets/images/manifest.json">
<link rel="mask-icon" href="../assets/images/safari-pinned-tab.svg" color="#00bcd4">
<link rel="shortcut icon" href="../assets/images/favicon.ico">
<meta name="msapplication-config" content="../assets/images/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- Ignore these --> <!-- Choices includes -->
<link rel="stylesheet" href="../assets/styles/base.min.css?version=6.0.3"> <link
<!-- End ignore these --> rel="stylesheet"
href="../assets/styles/choices.min.css?version=6.0.3"
<!-- Choices includes --> />
<link rel="stylesheet" href="../assets/styles/choices.min.css?version=6.0.3"> <script src="../assets/scripts/choices.min.js?version=6.0.3"></script>
<script src="../assets/scripts/choices.min.js?version=6.0.3"></script> <!-- End Choices includes -->
<!-- End Choices includes -->
</head> </head>
<body> <body>
@ -43,7 +73,11 @@
<div data-test-hook="remove-button"> <div data-test-hook="remove-button">
<label for="choices-remove-button">Remove button</label> <label for="choices-remove-button">Remove button</label>
<select class="form-control" name="choices-remove-button" id="choices-remove-button"> <select
class="form-control"
name="choices-remove-button"
id="choices-remove-button"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -53,7 +87,11 @@
<div data-test-hook="disabled-choice"> <div data-test-hook="disabled-choice">
<label for="choices-disabled-choice">Disabled choice</label> <label for="choices-disabled-choice">Disabled choice</label>
<select class="form-control" name="choices-disabled-choice" id="choices-disabled-choice"> <select
class="form-control"
name="choices-disabled-choice"
id="choices-disabled-choice"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -63,7 +101,11 @@
<div data-test-hook="add-items-disabled"> <div data-test-hook="add-items-disabled">
<label for="choices-add-items-disabled">Add items disabled</label> <label for="choices-add-items-disabled">Add items disabled</label>
<select class="form-control" name="choices-add-items-disabled" id="choices-add-items-disabled"> <select
class="form-control"
name="choices-add-items-disabled"
id="choices-add-items-disabled"
>
<option value="Choice 1" selected>Choice 1</option> <option value="Choice 1" selected>Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -72,7 +114,12 @@
<div data-test-hook="disabled-via-attr"> <div data-test-hook="disabled-via-attr">
<label for="choices-disabled-via-attr">Disabled via attribute</label> <label for="choices-disabled-via-attr">Disabled via attribute</label>
<select class="form-control" name="choices-disabled-via-attr" id="choices-disabled-via-attr" disabled> <select
class="form-control"
name="choices-disabled-via-attr"
id="choices-disabled-via-attr"
disabled
>
<option value="Choice 1" selected>Choice 1</option> <option value="Choice 1" selected>Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -81,7 +128,11 @@
<div data-test-hook="prepend-append"> <div data-test-hook="prepend-append">
<label for="choices-prepend-append">Prepend/append</label> <label for="choices-prepend-append">Prepend/append</label>
<select class="form-control" name="choices-prepend-append" id="choices-prepend-append"> <select
class="form-control"
name="choices-prepend-append"
id="choices-prepend-append"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -90,7 +141,11 @@
<div data-test-hook="render-choice-limit"> <div data-test-hook="render-choice-limit">
<label for="choices-render-choice-limit">Render choice limit</label> <label for="choices-render-choice-limit">Render choice limit</label>
<select class="form-control" name="choices-render-choice-limit" id="choices-render-choice-limit"> <select
class="form-control"
name="choices-render-choice-limit"
id="choices-render-choice-limit"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -99,7 +154,11 @@
<div data-test-hook="search-disabled"> <div data-test-hook="search-disabled">
<label for="choices-search-disabled">Search disabled</label> <label for="choices-search-disabled">Search disabled</label>
<select class="form-control" name="choices-search-disabled" id="choices-search-disabled"> <select
class="form-control"
name="choices-search-disabled"
id="choices-search-disabled"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -108,7 +167,11 @@
<div data-test-hook="search-floor"> <div data-test-hook="search-floor">
<label for="choices-search-floor">Search floor</label> <label for="choices-search-floor">Search floor</label>
<select class="form-control" name="choices-search-floor" id="choices-search-floor"> <select
class="form-control"
name="choices-search-floor"
id="choices-search-floor"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -117,12 +180,20 @@
<div data-test-hook="remote-data"> <div data-test-hook="remote-data">
<label for="choices-remote-data">Remote data</label> <label for="choices-remote-data">Remote data</label>
<select class="form-control" name="choices-remote-data" id="choices-remote-data"></select> <select
class="form-control"
name="choices-remote-data"
id="choices-remote-data"
></select>
</div> </div>
<div data-test-hook="scrolling-dropdown"> <div data-test-hook="scrolling-dropdown">
<label for="choices-scrolling-dropdown">Scrolling dropdown</label> <label for="choices-scrolling-dropdown">Scrolling dropdown</label>
<select class="form-control" name="choices-scrolling-dropdown" id="choices-scrolling-dropdown"> <select
class="form-control"
name="choices-scrolling-dropdown"
id="choices-scrolling-dropdown"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -143,7 +214,12 @@
<div data-test-hook="groups"> <div data-test-hook="groups">
<label for="choices-groups">Choice groups</label> <label for="choices-groups">Choice groups</label>
<select class="form-control" name="choices-groups" id="choices-groups" multiple> <select
class="form-control"
name="choices-groups"
id="choices-groups"
multiple
>
<optgroup label="UK"> <optgroup label="UK">
<option value="London">London</option> <option value="London">London</option>
<option value="Manchester">Manchester</option> <option value="Manchester">Manchester</option>
@ -159,7 +235,11 @@
<div data-test-hook="parent-child"> <div data-test-hook="parent-child">
<label for="choices-parent">Parent</label> <label for="choices-parent">Parent</label>
<select class="form-control" name="choices-parent" id="choices-parent"> <select
class="form-control"
name="choices-parent"
id="choices-parent"
>
<option value="Parent choice 1">Parent choice 1</option> <option value="Parent choice 1">Parent choice 1</option>
<option value="Parent choice 2">Parent choice 2</option> <option value="Parent choice 2">Parent choice 2</option>
<option value="Parent choice 3">Parent choice 3</option> <option value="Parent choice 3">Parent choice 3</option>
@ -175,26 +255,44 @@
<div data-test-hook="custom-properties"> <div data-test-hook="custom-properties">
<label for="choices-custom-properties">Custom properties</label> <label for="choices-custom-properties">Custom properties</label>
<select class="form-control" name="choices-custom-properties" id="choices-custom-properties"></select> <select
class="form-control"
name="choices-custom-properties"
id="choices-custom-properties"
></select>
</div> </div>
<div data-test-hook="non-string-values"> <div data-test-hook="non-string-values">
<label for="choices-non-string-values">Non-string values</label> <label for="choices-non-string-values">Non-string values</label>
<select class="form-control" name="choices-non-string-values" id="choices-non-string-values"></select> <select
class="form-control"
name="choices-non-string-values"
id="choices-non-string-values"
></select>
</div> </div>
<div data-test-hook="within-form"> <div data-test-hook="within-form">
<form> <form>
<label for="choices-within-form">Within form</label> <label for="choices-within-form">Within form</label>
<select class="form-control" name="choices-within-form" id="choices-within-form"> <select
class="form-control"
name="choices-within-form"
id="choices-within-form"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
</select> </select>
</form> </form>
</div> </div>
<div data-test-hook="set-choice-by-value"> <div data-test-hook="set-choice-by-value">
<label for="choices-set-choice-by-value">Dynamically set choice by value</label> <label for="choices-set-choice-by-value"
<select class="form-control" name="choices-set-choice-by-value" id="choices-set-choice-by-value"> >Dynamically set choice by value</label
>
<select
class="form-control"
name="choices-set-choice-by-value"
id="choices-set-choice-by-value"
>
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option> <option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option> <option value="Choice 3">Choice 3</option>
@ -203,7 +301,11 @@
<div data-test-hook="search-by-label"> <div data-test-hook="search-by-label">
<label for="choices-search-by-label">Search by label</label> <label for="choices-search-by-label">Search by label</label>
<select class="form-control" name="choices-search-by-label" id="choices-search-by-label"> <select
class="form-control"
name="choices-search-by-label"
id="choices-search-by-label"
>
<option value="value1">label1</option> <option value="value1">label1</option>
<option value="value2">label2</option> <option value="value2">label2</option>
</select> </select>
@ -214,13 +316,17 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const choicesBasic = new Choices('#choices-basic'); const choicesBasic = new Choices('#choices-basic');
document.querySelector('button.disable').addEventListener('click', () => { document
choicesBasic.disable(); .querySelector('button.disable')
}); .addEventListener('click', () => {
choicesBasic.disable();
});
document.querySelector('button.enable').addEventListener('click', () => { document
choicesBasic.enable(); .querySelector('button.enable')
}); .addEventListener('click', () => {
choicesBasic.enable();
});
new Choices('#choices-remove-button', { new Choices('#choices-remove-button', {
removeItemButton: true, removeItemButton: true,
@ -242,12 +348,12 @@
}); });
new Choices('#choices-render-choice-limit', { new Choices('#choices-render-choice-limit', {
renderChoiceLimit: 1 renderChoiceLimit: 1,
}); });
new Choices('#choices-search-disabled', { new Choices('#choices-search-disabled', {
searchEnabled: false searchEnabled: false,
}) });
new Choices('#choices-search-floor', { new Choices('#choices-search-floor', {
searchFloor: 5, searchFloor: 5,
@ -255,16 +361,9 @@
new Choices('#choices-remote-data', { new Choices('#choices-remote-data', {
shouldSort: false, shouldSort: false,
}).ajax((callback) => { }).setChoices(async () => {
fetch('/data') const res = await fetch('/data');
.then((response) => { return res.json();
response.json().then((data) => {
callback(data, 'value', 'label');
});
})
.catch((error) => {
console.error(error);
});
}); });
new Choices('#choices-scrolling-dropdown', { new Choices('#choices-scrolling-dropdown', {
@ -276,7 +375,7 @@
const parent = new Choices('#choices-parent'); const parent = new Choices('#choices-parent');
const child = new Choices('#choices-child').disable(); const child = new Choices('#choices-child').disable();
parent.passedElement.element.addEventListener('change', (event) => { parent.passedElement.element.addEventListener('change', event => {
if (event.detail.value === 'Parent choice 2') { if (event.detail.value === 'Parent choice 2') {
child.enable(); child.enable();
} else { } else {
@ -310,8 +409,8 @@
customProperties: { customProperties: {
country: 'Portugal', country: 'Portugal',
}, },
} },
] ],
}); });
new Choices('#choices-non-string-values', { new Choices('#choices-non-string-values', {
@ -343,10 +442,12 @@
new Choices('#choices-within-form'); new Choices('#choices-within-form');
new Choices('#choices-set-choice-by-value').setChoiceByValue('Choice 2'); new Choices('#choices-set-choice-by-value').setChoiceByValue(
'Choice 2',
);
new Choices('#choices-search-by-label', { searchFields: ['label'] }); new Choices('#choices-search-by-label', { searchFields: ['label'] });
}); });
</script> </script>
</body> </body>
</html> </html>

View file

@ -31,7 +31,6 @@ import {
sortByScore, sortByScore,
generateId, generateId,
findAncestorByAttrName, findAncestorByAttrName,
fetchFromObject,
isIE11, isIE11,
existsInArray, existsInArray,
cloneObject, cloneObject,
@ -44,6 +43,10 @@ const USER_DEFAULTS = /** @type {Partial<import('../../types/index').Choices.Opt
* Choices * Choices
* @author Josh Johnson<josh@joshuajohnson.co.uk> * @author Josh Johnson<josh@joshuajohnson.co.uk>
*/ */
/**
* @typedef {import('../../types/index').Choices.Choice} Choice
*/
class Choices { class Choices {
/* ======================================== /* ========================================
= Static properties = = Static properties =
@ -222,7 +225,7 @@ class Choices {
} }
/* ======================================== /* ========================================
= Public functions = = Public methods =
======================================== */ ======================================== */
init() { init() {
@ -460,9 +463,93 @@ class Choices {
return this; return this;
} }
setChoices(choices = [], value = '', label = '', replaceChoices = false) { /**
if (!this._isSelectElement || !value) { * Set choices of select input via an array of objects (or function that returns array of object or promise of it),
return this; * a value field name and a label field 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).
*
* **Input types affected:** select-one, select-multiple
*
* @template {object[] | ((instance: Choices) => object[] | Promise<object[]>)} T
* @param {T} [choicesArrayOrFetcher]
* @param {string} [value = 'value'] - name of `value` field
* @param {string} [label = 'label'] - name of 'label' field
* @param {boolean} [replaceChoices = false] - whether to replace of add choices
* @returns {this | Promise<this>}
*
* @example
* ```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
* ```js
* const example = new Choices(element);
*
* example.setChoices(async () => {
* try {
* const items = await fetch('/items');
* return items.json()
* } catch(err) {
* console.error(err)
* }
* });
* ```
*
* @example
* ```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);
* ```
*/
setChoices(
choicesArrayOrFetcher = [],
value = 'value',
label = 'label',
replaceChoices = false,
) {
if (!this.initialised)
throw new ReferenceError(
`setChoices was called on a non-initialized instance of Choices`,
);
if (!this._isSelectElement)
throw new TypeError(`setChoices can't be used with INPUT based Choices`);
if (typeof value !== 'string' || !value) {
throw new TypeError(
`value parameter must be a name of 'value' field in passed objects`,
);
} }
// Clear choices if needed // Clear choices if needed
@ -470,6 +557,34 @@ class Choices {
this.clearChoices(); this.clearChoices();
} }
if (!Array.isArray(choicesArrayOrFetcher)) {
if (typeof choicesArrayOrFetcher !== 'function')
throw new TypeError(
`.setChoices must be called either with array of choices with a function resulting into Promise of array of choices`,
);
// it's a choices fetcher
requestAnimationFrame(() => this._handleLoadingState(true));
const fetcher = choicesArrayOrFetcher(this);
if (typeof fetcher === 'object' && typeof fetcher.then === 'function') {
// that's a promise
return fetcher
.then(data => this.setChoices(data, value, label, replaceChoices))
.catch(err => {
if (!this.config.silent) console.error(err);
})
.then(() => this._handleLoadingState(false))
.then(() => this);
}
// function returned something else than promise, let's check if it's an array of choices
if (!Array.isArray(fetcher))
throw new TypeError(
`.setChoices first argument function must return either array of choices or Promise, got: ${typeof fetcher}`,
);
// recursion with results, it's sync and choices were cleared already
return this.setChoices(fetcher, value, label, false);
}
this.containerOuter.removeLoadingState(); this.containerOuter.removeLoadingState();
const addGroupsAndChoices = groupOrChoice => { const addGroupsAndChoices = groupOrChoice => {
if (groupOrChoice.choices) { if (groupOrChoice.choices) {
@ -492,7 +607,7 @@ class Choices {
}; };
this._setLoading(true); this._setLoading(true);
choices.forEach(addGroupsAndChoices); choicesArrayOrFetcher.forEach(addGroupsAndChoices);
this._setLoading(false); this._setLoading(false);
return this; return this;
@ -519,18 +634,7 @@ class Choices {
return this; return this;
} }
ajax(fn) { /* ===== End of Public methods ====== */
if (!this.initialised || !this._isSelectElement || !fn) {
return this;
}
requestAnimationFrame(() => this._handleLoadingState(true));
fn(this._ajaxCallback());
return this;
}
/* ===== End of Public functions ====== */
/* ============================================= /* =============================================
= Private functions = = Private functions =
@ -1054,55 +1158,6 @@ class Choices {
}; };
} }
_ajaxCallback() {
return (results, value, label) => {
if (!results || !value) {
return;
}
const parsedResults = isType('Object', results) ? [results] : results;
if (
parsedResults &&
isType('Array', parsedResults) &&
parsedResults.length
) {
// Remove loading states/text
this._handleLoadingState(false);
this._setLoading(true);
// Add each result as a choice
parsedResults.forEach(result => {
if (result.choices) {
this._addGroup({
group: result,
id: result.id || null,
valueKey: value,
labelKey: label,
});
} else {
this._addChoice({
value: fetchFromObject(result, value),
label: fetchFromObject(result, label),
isSelected: result.selected,
isDisabled: result.disabled,
customProperties: result.customProperties,
placeholder: result.placeholder,
});
}
});
this._setLoading(false);
if (this._isSelectOneElement) {
this._selectPlaceholderChoice();
}
} else {
// No results, remove loading state
this._handleLoadingState(false);
}
};
}
_searchChoices(value) { _searchChoices(value) {
const newValue = isType('String', value) ? value.trim() : value; const newValue = isType('String', value) ? value.trim() : value;
const currentValue = isType('String', this._currentValue) const currentValue = isType('String', this._currentValue)

View file

@ -881,84 +881,74 @@ describe('choices', () => {
}); });
}); });
describe('ajax', () => { describe('setChoices with callback/Promise', () => {
const callbackoutput = 'worked';
let handleLoadingStateStub;
let ajaxCallbackStub;
const returnsEarly = () => {
it('returns early', () => {
expect(handleLoadingStateStub.called).to.equal(false);
expect(ajaxCallbackStub.called).to.equal(false);
});
};
beforeEach(() => {
handleLoadingStateStub = stub();
ajaxCallbackStub = stub().returns(callbackoutput);
instance._ajaxCallback = ajaxCallbackStub;
instance._handleLoadingState = handleLoadingStateStub;
});
afterEach(() => {
instance._ajaxCallback.reset();
instance._handleLoadingState.reset();
});
describe('not initialised', () => { describe('not initialised', () => {
beforeEach(() => { beforeEach(() => {
instance.initialised = false; instance.initialised = false;
output = instance.ajax(() => {});
}); });
returnsInstance(output); it('should throw', () => {
returnsEarly(); expect(() => instance.setChoices(null)).Throw(ReferenceError);
});
}); });
describe('text element', () => { describe('text element', () => {
beforeEach(() => { beforeEach(() => {
instance._isSelectElement = false; instance._isSelectElement = false;
output = instance.ajax(() => {});
}); });
returnsInstance(output); it('should throw', () => {
returnsEarly(); expect(() => instance.setChoices(null)).Throw(TypeError);
});
}); });
describe('passing invalid function', () => { describe('passing invalid function', () => {
beforeEach(() => { beforeEach(() => {
output = instance.ajax(null); instance._isSelectElement = true;
}); });
returnsInstance(output); it('should throw on non function', () => {
returnsEarly(); expect(() => instance.setChoices(null)).Throw(TypeError, /Promise/i);
});
it(`should throw on function that doesn't return promise`, () => {
expect(() => instance.setChoices(() => 'boo')).to.throw(
TypeError,
/promise/i,
);
});
}); });
describe('select element', () => { describe('select element', () => {
let callback; it('fetches and sets choices', async () => {
document.body.innerHTML = '<select id="test" />';
const choice = new Choices('#test');
const handleLoadingStateSpy = spy(choice, '_handleLoadingState');
beforeEach(() => { let fetcherCalled = false;
instance.initialised = true; const fetcher = async inst => {
instance._isSelectElement = true; expect(inst).to.eq(choice);
ajaxCallbackStub = stub(); fetcherCalled = true;
callback = stub(); await new Promise(resolve => setTimeout(resolve, 1000));
output = instance.ajax(callback); return [
}); { label: 'l1', value: 'v1', customProperties: 'prop1' },
{ label: 'l2', value: 'v2', customProperties: 'prop2' },
returnsInstance(output); ];
};
it('sets loading state', done => { expect(choice._store.choices.length).to.equal(0);
requestAnimationFrame(() => { const promise = choice.setChoices(fetcher);
expect(handleLoadingStateStub.called).to.equal(true); await new Promise(resolve =>
done(); requestAnimationFrame(() => {
}); expect(handleLoadingStateSpy.callCount).to.equal(1);
}); resolve();
}),
it('calls passed function with ajax callback', () => { );
expect(callback.called).to.equal(true); expect(fetcherCalled).to.be.true;
expect(callback.lastCall.args[0]).to.eql(callbackoutput); const res = await promise;
expect(res).to.equal(choice);
expect(choice._store.choices[1].value).to.equal('v2');
expect(choice._store.choices[1].label).to.equal('l2');
expect(choice._store.choices[1].customProperties).to.equal('prop2');
}); });
}); });
}); });
@ -1353,31 +1343,29 @@ describe('choices', () => {
instance.containerOuter.removeLoadingState.reset(); instance.containerOuter.removeLoadingState.reset();
}); });
const returnsEarly = () => {
it('returns early', () => {
expect(addGroupStub.called).to.equal(false);
expect(addChoiceStub.called).to.equal(false);
expect(clearChoicesStub.called).to.equal(false);
});
};
describe('when element is not select element', () => { describe('when element is not select element', () => {
beforeEach(() => { beforeEach(() => {
instance._isSelectElement = false; instance._isSelectElement = false;
instance.setChoices(choices, value, label, false);
}); });
returnsEarly(); it('throws', () => {
expect(() =>
instance.setChoices(choices, value, label, false),
).to.throw(TypeError, /input/i);
});
}); });
describe('passing invalid arguments', () => { describe('passing invalid arguments', () => {
describe('passing no value', () => { describe('passing no value', () => {
beforeEach(() => { beforeEach(() => {
instance._isSelectElement = true; instance._isSelectElement = true;
instance.setChoices(choices, undefined, 'label', false);
}); });
returnsEarly(); it('throws', () => {
expect(() =>
instance.setChoices(choices, null, 'label', false),
).to.throw(TypeError, /value/i);
});
}); });
}); });

View file

@ -142,19 +142,6 @@ export const getWindowHeight = () => {
); );
}; };
export const fetchFromObject = (object, path) => {
const index = path.indexOf('.');
if (index > -1) {
return fetchFromObject(
object[path.substring(0, index)],
path.substr(index + 1),
);
}
return object[path];
};
export const isIE11 = () => export const isIE11 = () =>
!!( !!(
navigator.userAgent.match(/Trident/) && navigator.userAgent.match(/Trident/) &&

View file

@ -10,7 +10,6 @@ import {
sanitise, sanitise,
sortByAlpha, sortByAlpha,
sortByScore, sortByScore,
fetchFromObject,
existsInArray, existsInArray,
cloneObject, cloneObject,
dispatchEvent, dispatchEvent,
@ -198,19 +197,6 @@ describe('utils', () => {
}); });
}); });
describe('fetchFromObject', () => {
it('fetches value from object using given path', () => {
const object = {
band: {
name: 'The Strokes',
},
};
const output = fetchFromObject(object, 'band.name');
expect(output).to.equal(object.band.name);
});
});
describe('existsInArray', () => { describe('existsInArray', () => {
it('determines whether a value exists within given array', () => { it('determines whether a value exists within given array', () => {
const values = [ const values = [

112
types/index.d.ts vendored
View file

@ -872,16 +872,47 @@ export default class Choices {
*/ */
getValue(valueOnly?: boolean): string | string[]; getValue(valueOnly?: boolean): string | string[];
/** Direct populate choices
*
* @param {string[] | Choices.Item[]} items
*/
setValue(items: string[] | Choices.Item[]): this;
/** /**
* Set choices of select input via an array of objects, a value name and a label name. * Set value of input based on existing Choice. `value` can be either a single string or an array of strings
*
* **Input types affected:** select-one, select-multiple
*
* @example
* ```
* const example = new Choices(element, {
* choices: [
* {value: 'One', label: 'Label One'},
* {value: 'Two', label: 'Label Two', disabled: true},
* {value: 'Three', label: 'Label Three'},
* ],
* });
*
* example.setChoiceByValue('Two'); // Choice with value of 'Two' has now been selected.
* ```
*/
setChoiceByValue(value: string | string[]): this;
/**
* Set choices of select input via an array of objects (or function that returns array of object or promise of it),
* a value field name and a label field name.
* This behaves the same as passing items via the choices option but can be called after initialising Choices. * 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. * 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). * Optionally pass a `customProperties` object to add additional data to your choices (useful when searching/filtering etc).
* *
* **Input types affected:** select-one, select-multiple * **Input types affected:** select-one, select-multiple
* *
* @example Example 1: * @param {string} [value = 'value'] - name of `value` field
* ``` * @param {string} [label = 'label'] - name of 'label' field
* @param {boolean} [replaceChoices = false] - whether to replace of add choices
*
* @example
* ```js
* const example = new Choices(element); * const example = new Choices(element);
* *
* example.setChoices([ * example.setChoices([
@ -891,8 +922,22 @@ export default class Choices {
* ], 'value', 'label', false); * ], 'value', 'label', false);
* ``` * ```
* *
* @example Example 2: * @example
* ```js
* const example = new Choices(element);
*
* example.setChoices(async () => {
* try {
* const items = await fetch('/items');
* return items.json()
* } catch(err) {
* console.error(err)
* }
* });
* ``` * ```
*
* @example
* ```js
* const example = new Choices(element); * const example = new Choices(element);
* *
* example.setChoices([{ * example.setChoices([{
@ -920,35 +965,14 @@ export default class Choices {
* }], 'value', 'label', false); * }], 'value', 'label', false);
* ``` * ```
*/ */
setValue(args: string[]): this; setChoices<
T extends object[] | ((instance: Choices) => object[] | Promise<object[]>)
/** >(
* Set value of input based on existing Choice. `value` can be either a single string or an array of strings choices: T,
* value?: string,
* **Input types affected:** select-one, select-multiple label?: string,
*
* @example
* ```
* const example = new Choices(element, {
* choices: [
* {value: 'One', label: 'Label One'},
* {value: 'Two', label: 'Label Two', disabled: true},
* {value: 'Three', label: 'Label Three'},
* ],
* });
*
* example.setChoiceByValue('Two'); // Choice with value of 'Two' has now been selected.
* ```
*/
setChoiceByValue(value: string | string[]): this;
/** Direct populate choices */
setChoices(
choices: Choices.Choice[],
value: string,
label: string,
replaceChoices?: boolean, replaceChoices?: boolean,
): this; ): T extends object[] ? this : Promise<this>;
/** /**
* Clear all choices from select. * Clear all choices from select.
@ -984,28 +1008,4 @@ export default class Choices {
* **Input types affected:** text, select-one, select-multiple * **Input types affected:** text, select-one, select-multiple
*/ */
disable(): this; disable(): this;
/**
* Populate choices/groups via a callback.
*
* **Input types affected:** select-one, select-multiple
*
* @example
* ```
* var example = new Choices(element);
*
* example.ajax(function(callback) {
* fetch(url)
* .then(function(response) {
* response.json().then(function(data) {
* callback(data, 'value', 'label');
* });
* })
* .catch(function(error) {
* console.log(error);
* });
* });
* ```
*/
ajax(fn: (values: any) => any): this;
} }