mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-10 09:36:35 +02:00
Merge remote-tracking branch 'refs/remotes/jshjohnson/master'
This commit is contained in:
commit
fdced6276a
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -2,7 +2,8 @@ node_modules
|
|||
npm-debug.log
|
||||
.DS_Store
|
||||
.vscode
|
||||
package-lock.json
|
||||
|
||||
# Test
|
||||
tests/reports
|
||||
tests/results
|
||||
tests/results
|
||||
|
|
|
@ -6,4 +6,4 @@ before_install:
|
|||
- npm install -g npm@latest
|
||||
install:
|
||||
- npm install
|
||||
script: npm run js:test
|
||||
script: npm run test
|
||||
|
|
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at josh@joshuajohnson.co.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
23
CONTRIBUTING.md
Normal file
23
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Contributions
|
||||
In lieu of a formal styleguide, take care to maintain the existing coding style ensuring there are no linting errors. Add unit tests for any new or changed functionality. Lint and test your code using the npm scripts below:
|
||||
|
||||
### NPM tasks
|
||||
| Task | Usage |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| `npm run start` | Fire up local server for development |
|
||||
| `npm run test` | Run sequence of tests once |
|
||||
| `npm run test:watch` | Fire up test server and re-test on file change |
|
||||
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
|
||||
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
|
||||
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
|
||||
|
||||
## Pull requests
|
||||
When submitting a pull request that resolves a bug, feel free to use the following template:
|
||||
|
||||
```md
|
||||
## This is the problem:
|
||||
|
||||
## Steps to reproduce:
|
||||
|
||||
## This is my solution:
|
||||
```
|
232
README.md
232
README.md
|
@ -70,6 +70,7 @@ Or include Choices directly:
|
|||
searchEnabled: true,
|
||||
searchChoices: true,
|
||||
searchFloor: 1,
|
||||
searchResultLimit: 4,
|
||||
searchFields: ['label', 'value'],
|
||||
position: 'auto',
|
||||
resetScrollPosition: true,
|
||||
|
@ -91,29 +92,29 @@ Or include Choices directly:
|
|||
return `Only ${maxItemCount} values can be added.`;
|
||||
},
|
||||
classNames: {
|
||||
containerOuter: 'choices',
|
||||
containerInner: 'choices__inner',
|
||||
input: 'choices__input',
|
||||
inputCloned: 'choices__input--cloned',
|
||||
list: 'choices__list',
|
||||
listItems: 'choices__list--multiple',
|
||||
listSingle: 'choices__list--single',
|
||||
listDropdown: 'choices__list--dropdown',
|
||||
item: 'choices__item',
|
||||
itemSelectable: 'choices__item--selectable',
|
||||
itemDisabled: 'choices__item--disabled',
|
||||
itemChoice: 'choices__item--choice',
|
||||
group: 'choices__group',
|
||||
groupHeading : 'choices__heading',
|
||||
button: 'choices__button',
|
||||
activeState: 'is-active',
|
||||
focusState: 'is-focused',
|
||||
openState: 'is-open',
|
||||
disabledState: 'is-disabled',
|
||||
highlightedState: 'is-highlighted',
|
||||
hiddenState: 'is-hidden',
|
||||
flippedState: 'is-flipped',
|
||||
loadingState: 'is-loading',
|
||||
containerOuter: 'choices',
|
||||
containerInner: 'choices__inner',
|
||||
input: 'choices__input',
|
||||
inputCloned: 'choices__input--cloned',
|
||||
list: 'choices__list',
|
||||
listItems: 'choices__list--multiple',
|
||||
listSingle: 'choices__list--single',
|
||||
listDropdown: 'choices__list--dropdown',
|
||||
item: 'choices__item',
|
||||
itemSelectable: 'choices__item--selectable',
|
||||
itemDisabled: 'choices__item--disabled',
|
||||
itemChoice: 'choices__item--choice',
|
||||
group: 'choices__group',
|
||||
groupHeading : 'choices__heading',
|
||||
button: 'choices__button',
|
||||
activeState: 'is-active',
|
||||
focusState: 'is-focused',
|
||||
openState: 'is-open',
|
||||
disabledState: 'is-disabled',
|
||||
highlightedState: 'is-highlighted',
|
||||
hiddenState: 'is-hidden',
|
||||
flippedState: 'is-flipped',
|
||||
loadingState: 'is-loading',
|
||||
},
|
||||
// Choices uses the great Fuse library for searching. You
|
||||
// can find more options here: https://github.com/krisk/Fuse#options
|
||||
|
@ -164,7 +165,10 @@ Pass an array of objects:
|
|||
{
|
||||
value: 'Value 2',
|
||||
label: 'Label 2',
|
||||
id: 2
|
||||
id: 2,
|
||||
customProperties: {
|
||||
random: 'I am a custom property'
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
|
@ -189,6 +193,10 @@ Pass an array of objects:
|
|||
label: 'Option 2',
|
||||
selected: false,
|
||||
disabled: true,
|
||||
customProperties: {
|
||||
description: 'Custom description about Option 2',
|
||||
random: 'Another random custom property'
|
||||
},
|
||||
}]
|
||||
```
|
||||
|
||||
|
@ -268,7 +276,7 @@ Pass an array of objects:
|
|||
|
||||
**Input types affected:**`select-one`, `select-multiple`
|
||||
|
||||
**Usage:** Specify which fields should be used when a user is searching.
|
||||
**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`
|
||||
|
@ -277,6 +285,13 @@ 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`
|
||||
|
||||
**Usage:** The maximum amount of search results to show.
|
||||
|
||||
### position
|
||||
**Type:** `String` **Default:** `auto`
|
||||
|
||||
|
@ -317,9 +332,9 @@ Pass an array of objects:
|
|||
```js
|
||||
// Sorting via length of label from largest to smallest
|
||||
const example = new Choices(element, {
|
||||
sortFilter: function(a, b) {
|
||||
return b.label.length - a.label.length;
|
||||
},
|
||||
sortFilter: function(a, b) {
|
||||
return b.label.length - a.label.length;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -398,29 +413,29 @@ const example = new Choices(element, {
|
|||
|
||||
```
|
||||
classNames: {
|
||||
containerOuter: 'choices',
|
||||
containerInner: 'choices__inner',
|
||||
input: 'choices__input',
|
||||
inputCloned: 'choices__input--cloned',
|
||||
list: 'choices__list',
|
||||
listItems: 'choices__list--multiple',
|
||||
listSingle: 'choices__list--single',
|
||||
listDropdown: 'choices__list--dropdown',
|
||||
item: 'choices__item',
|
||||
itemSelectable: 'choices__item--selectable',
|
||||
itemDisabled: 'choices__item--disabled',
|
||||
itemOption: 'choices__item--choice',
|
||||
group: 'choices__group',
|
||||
groupHeading : 'choices__heading',
|
||||
button: 'choices__button',
|
||||
activeState: 'is-active',
|
||||
focusState: 'is-focused',
|
||||
openState: 'is-open',
|
||||
disabledState: 'is-disabled',
|
||||
highlightedState: 'is-highlighted',
|
||||
hiddenState: 'is-hidden',
|
||||
flippedState: 'is-flipped',
|
||||
selectedState: 'is-highlighted',
|
||||
containerOuter: 'choices',
|
||||
containerInner: 'choices__inner',
|
||||
input: 'choices__input',
|
||||
inputCloned: 'choices__input--cloned',
|
||||
list: 'choices__list',
|
||||
listItems: 'choices__list--multiple',
|
||||
listSingle: 'choices__list--single',
|
||||
listDropdown: 'choices__list--dropdown',
|
||||
item: 'choices__item',
|
||||
itemSelectable: 'choices__item--selectable',
|
||||
itemDisabled: 'choices__item--disabled',
|
||||
itemOption: 'choices__item--choice',
|
||||
group: 'choices__group',
|
||||
groupHeading : 'choices__heading',
|
||||
button: 'choices__button',
|
||||
activeState: 'is-active',
|
||||
focusState: 'is-focused',
|
||||
openState: 'is-open',
|
||||
disabledState: 'is-disabled',
|
||||
highlightedState: 'is-highlighted',
|
||||
hiddenState: 'is-hidden',
|
||||
flippedState: 'is-flipped',
|
||||
selectedState: 'is-highlighted',
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -457,14 +472,14 @@ const example = new Choices(element, {
|
|||
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''}>
|
||||
<span>★</span> ${data.label}
|
||||
</div>
|
||||
`);
|
||||
`);
|
||||
},
|
||||
choice: (data) => {
|
||||
return template(`
|
||||
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="${this.config.itemSelectText}" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}" ${data.groupId > 0 ? 'role="treeitem"' : 'role="option"'}>
|
||||
<span>★</span> ${data.label}
|
||||
</div>
|
||||
`);
|
||||
<span>★</span> ${data.label}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -498,7 +513,6 @@ example.passedElement.addEventListener('addItem', function(event) {
|
|||
console.log(event.detail.label);
|
||||
console.log(event.detail.groupValue);
|
||||
}, false);
|
||||
|
||||
```
|
||||
|
||||
### addItem
|
||||
|
@ -566,12 +580,14 @@ Methods can be called either directly or by chaining:
|
|||
const choices = new Choices(element, {
|
||||
addItems: false,
|
||||
removeItems: false,
|
||||
}).setValue(['Set value 1', 'Set value 2']).disable();
|
||||
})
|
||||
.setValue(['Set value 1', 'Set value 2'])
|
||||
.disable();
|
||||
|
||||
// Calling a method directly
|
||||
const choices = new Choices(element, {
|
||||
addItems: false,
|
||||
removeItems: false,
|
||||
addItems: false,
|
||||
removeItems: false,
|
||||
});
|
||||
|
||||
choices.setValue(['Set value 1', 'Set value 2'])
|
||||
|
@ -640,7 +656,7 @@ choices.disable();
|
|||
### 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.
|
||||
**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).
|
||||
|
||||
**Example 1:**
|
||||
|
||||
|
@ -648,9 +664,9 @@ choices.disable();
|
|||
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: 'One', label: 'Label One', disabled: true},
|
||||
{value: 'Two', label: 'Label Two', selected: true},
|
||||
{value: 'Three', label: 'Label Three'},
|
||||
], 'value', 'label', false);
|
||||
```
|
||||
|
||||
|
@ -660,24 +676,27 @@ example.setChoices([
|
|||
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 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'},
|
||||
]
|
||||
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);
|
||||
```
|
||||
|
||||
|
@ -706,9 +725,9 @@ 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
|
||||
|
@ -718,17 +737,17 @@ example.setValue(['Four','Five','Six']);
|
|||
### setValueByChoice(value);
|
||||
**Input types affected:** `select-one`, `select-multiple`
|
||||
|
||||
**Usage:** Set value of input based on existing Choice.
|
||||
**Usage:** Set value of input based on existing Choice. `value` can be either a single string or an array of strings
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
const example = new Choices(element, {
|
||||
choices: [
|
||||
{value: 'One', label: 'Label One'},
|
||||
{value: 'Two', label: 'Label Two', disabled: true},
|
||||
{value: 'Three', label: 'Label Three'},
|
||||
],
|
||||
choices: [
|
||||
{value: 'One', label: 'Label One'},
|
||||
{value: 'Two', label: 'Label Two', disabled: true},
|
||||
{value: 'Three', label: 'Label Three'},
|
||||
],
|
||||
});
|
||||
|
||||
example.setValueByChoice('Two'); // Choice with value of 'Two' has now been selected.
|
||||
|
@ -749,7 +768,7 @@ example.setValueByChoice('Two'); // Choice with value of 'Two' has now been sele
|
|||
### disable();
|
||||
**Input types affected:** `text`, `select-one`, `select-multiple`
|
||||
|
||||
**Usage:** Disables input from accepting new value/sselecting further choices.
|
||||
**Usage:** Disables input from accepting new value/selecting further choices.
|
||||
|
||||
### enable();
|
||||
**Input types affected:** `text`, `select-one`, `select-multiple`
|
||||
|
@ -768,15 +787,15 @@ example.setValueByChoice('Two'); // Choice with value of 'Two' has now been sele
|
|||
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);
|
||||
});
|
||||
fetch(url)
|
||||
.then(function(response) {
|
||||
response.json().then(function(data) {
|
||||
callback(data, 'value', 'label');
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -807,15 +826,14 @@ To setup a local environment: clone this repo, navigate into it's directory in a
|
|||
```npm install```
|
||||
|
||||
### NPM tasks
|
||||
| Task | Usage |
|
||||
| ------------------- | ------------------------------------------------------------ |
|
||||
| `npm start` | Fire up local server for development |
|
||||
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
|
||||
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
|
||||
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
|
||||
|
||||
## Contributions
|
||||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using npm scripts...bla bla bla
|
||||
| Task | Usage |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| `npm run start` | Fire up local server for development |
|
||||
| `npm run test` | Run sequence of tests once |
|
||||
| `npm run test:watch` | Fire up test server and re-test on file change |
|
||||
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
|
||||
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
|
||||
| `npm run css:build` | Compile, minify and prefix SCSS files to CSS |
|
||||
|
||||
## License
|
||||
MIT License
|
||||
|
|
1004
assets/scripts/dist/choices.js
vendored
1004
assets/scripts/dist/choices.js
vendored
File diff suppressed because it is too large
Load diff
2
assets/scripts/dist/choices.js.map
vendored
2
assets/scripts/dist/choices.js.map
vendored
File diff suppressed because one or more lines are too long
8
assets/scripts/dist/choices.min.js
vendored
8
assets/scripts/dist/choices.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
export const addItem = (value, label, id, choiceId, groupId) => {
|
||||
export const addItem = (value, label, id, choiceId, groupId, customProperties) => {
|
||||
return {
|
||||
type: 'ADD_ITEM',
|
||||
value,
|
||||
|
@ -6,6 +6,7 @@ export const addItem = (value, label, id, choiceId, groupId) => {
|
|||
id,
|
||||
choiceId,
|
||||
groupId,
|
||||
customProperties
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -25,7 +26,7 @@ export const highlightItem = (id, highlighted) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const addChoice = (value, label, id, groupId, disabled, elementId) => {
|
||||
export const addChoice = (value, label, id, groupId, disabled, elementId, customProperties) => {
|
||||
return {
|
||||
type: 'ADD_CHOICE',
|
||||
value,
|
||||
|
@ -34,6 +35,7 @@ export const addChoice = (value, label, id, groupId, disabled, elementId) => {
|
|||
groupId,
|
||||
disabled,
|
||||
elementId: elementId,
|
||||
customProperties
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Fuse from 'fuse.js';
|
||||
import classNames from 'classnames';
|
||||
import Store from './store/index.js';
|
||||
import {
|
||||
addItem,
|
||||
|
@ -64,6 +65,7 @@ class Choices {
|
|||
searchEnabled: true,
|
||||
searchChoices: true,
|
||||
searchFloor: 1,
|
||||
searchResultLimit: 4,
|
||||
searchFields: ['label', 'value'],
|
||||
position: 'auto',
|
||||
resetScrollPosition: true,
|
||||
|
@ -334,19 +336,29 @@ class Choices {
|
|||
// Create a fragment to store our list items (so we don't have to update the DOM for each item)
|
||||
const choicesFragment = fragment || document.createDocumentFragment();
|
||||
const filter = this.isSearching ? sortByScore : this.config.sortFilter;
|
||||
const appendChoice = (choice) => {
|
||||
const dropdownItem = this._getTemplate('choice', choice);
|
||||
const shouldRender = this.passedElement.type === 'select-one' || !choice.selected;
|
||||
if (shouldRender) {
|
||||
choicesFragment.appendChild(dropdownItem);
|
||||
}
|
||||
};
|
||||
|
||||
// If sorting is enabled or the user is searching, filter choices
|
||||
if (this.config.shouldSort || this.isSearching) {
|
||||
choices.sort(filter);
|
||||
}
|
||||
|
||||
choices.forEach((choice) => {
|
||||
const dropdownItem = this._getTemplate('choice', choice);
|
||||
const shouldRender = this.passedElement.type === 'select-one' || !choice.selected;
|
||||
if (shouldRender) {
|
||||
choicesFragment.appendChild(dropdownItem);
|
||||
if (this.isSearching) {
|
||||
for (let i = 0; i < this.config.searchResultLimit; i++) {
|
||||
const choice = choices[i];
|
||||
if (choice) {
|
||||
appendChoice(choice);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
choices.forEach(choice => appendChoice(choice));
|
||||
}
|
||||
|
||||
return choicesFragment;
|
||||
}
|
||||
|
@ -361,10 +373,10 @@ class Choices {
|
|||
renderItems(items, fragment) {
|
||||
// Create fragment to add elements to
|
||||
const itemListFragment = fragment || document.createDocumentFragment();
|
||||
// Simplify store data to just values
|
||||
const itemsFiltered = this.store.getItemsReducedToValues(items);
|
||||
|
||||
if (this.isTextElement) {
|
||||
// Simplify store data to just values
|
||||
const itemsFiltered = this.store.getItemsReducedToValues(items);
|
||||
// Assign hidden input array of values
|
||||
this.passedElement.setAttribute('value', itemsFiltered.join(this.config.delimiter));
|
||||
} else {
|
||||
|
@ -419,7 +431,7 @@ class Choices {
|
|||
this.choiceList.innerHTML = '';
|
||||
|
||||
// Scroll back to top of choices list
|
||||
if(this.config.resetScrollPosition){
|
||||
if (this.config.resetScrollPosition) {
|
||||
this.choiceList.scrollTop = 0;
|
||||
}
|
||||
|
||||
|
@ -509,7 +521,7 @@ class Choices {
|
|||
this.store.dispatch(highlightItem(id, true));
|
||||
|
||||
if (runEvent) {
|
||||
if(group && group.value) {
|
||||
if (group && group.value) {
|
||||
triggerEvent(this.passedElement, 'highlightItem', {
|
||||
id,
|
||||
value: item.value,
|
||||
|
@ -545,7 +557,7 @@ class Choices {
|
|||
|
||||
this.store.dispatch(highlightItem(id, false));
|
||||
|
||||
if(group && group.value) {
|
||||
if (group && group.value) {
|
||||
triggerEvent(this.passedElement, 'unhighlightItem', {
|
||||
id,
|
||||
value: item.value,
|
||||
|
@ -795,13 +807,32 @@ class Choices {
|
|||
// If we are dealing with a select input, we need to create an option first
|
||||
// that is then selected. For text inputs we can just add items normally.
|
||||
if (passedElementType !== 'text') {
|
||||
this._addChoice(true, false, item.value, item.label, -1);
|
||||
this._addChoice(
|
||||
item.value,
|
||||
item.label,
|
||||
true,
|
||||
false,
|
||||
-1,
|
||||
item.customProperties
|
||||
);
|
||||
} else {
|
||||
this._addItem(item.value, item.label, item.id);
|
||||
this._addItem(
|
||||
item.value,
|
||||
item.label,
|
||||
item.id,
|
||||
undefined,
|
||||
item.customProperties
|
||||
);
|
||||
}
|
||||
} else if (itemType === 'String') {
|
||||
if (passedElementType !== 'text') {
|
||||
this._addChoice(true, false, item, item, -1);
|
||||
this._addChoice(
|
||||
item,
|
||||
item,
|
||||
true,
|
||||
false,
|
||||
-1
|
||||
);
|
||||
} else {
|
||||
this._addItem(item);
|
||||
}
|
||||
|
@ -840,7 +871,13 @@ class Choices {
|
|||
|
||||
if (foundChoice) {
|
||||
if (!foundChoice.selected) {
|
||||
this._addItem(foundChoice.value, foundChoice.label, foundChoice.id, foundChoice.groupId);
|
||||
this._addItem(
|
||||
foundChoice.value,
|
||||
foundChoice.label,
|
||||
foundChoice.id,
|
||||
foundChoice.groupId,
|
||||
foundChoice.customProperties
|
||||
);
|
||||
} else if (!this.config.silent) {
|
||||
console.warn('Attempting to select choice already selected');
|
||||
}
|
||||
|
@ -868,19 +905,29 @@ class Choices {
|
|||
return;
|
||||
}
|
||||
// Clear choices if needed
|
||||
if(replaceChoices) {
|
||||
if (replaceChoices) {
|
||||
this._clearChoices();
|
||||
}
|
||||
// Add choices if passed
|
||||
if (choices && choices.length) {
|
||||
this.containerOuter.classList.remove(this.config.classNames.loadingState);
|
||||
choices.forEach((result, index) => {
|
||||
const isSelected = result.selected ? result.selected : false;
|
||||
const isDisabled = result.disabled ? result.disabled : false;
|
||||
if (result.choices) {
|
||||
this._addGroup(result, (result.id || null), value, label);
|
||||
this._addGroup(
|
||||
result,
|
||||
(result.id || null),
|
||||
value,
|
||||
label
|
||||
);
|
||||
} else {
|
||||
this._addChoice(isSelected, isDisabled, result[value], result[label]);
|
||||
this._addChoice(
|
||||
result[value],
|
||||
result[label],
|
||||
result.selected,
|
||||
result.disabled,
|
||||
undefined,
|
||||
result['customProperties']
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -968,7 +1015,9 @@ class Choices {
|
|||
if (this.initialised === true) {
|
||||
if (this.isSelectElement) {
|
||||
// Show loading text
|
||||
this._handleLoadingState(true);
|
||||
requestAnimationFrame(() => {
|
||||
this._handleLoadingState(true)
|
||||
});
|
||||
// Run callback
|
||||
fn(this._ajaxCallback());
|
||||
}
|
||||
|
@ -1092,7 +1141,13 @@ class Choices {
|
|||
const canAddItem = this._canAddItem(activeItems, choice.value);
|
||||
|
||||
if (canAddItem.response) {
|
||||
this._addItem(choice.value, choice.label, choice.id, choice.groupId);
|
||||
this._addItem(
|
||||
choice.value,
|
||||
choice.label,
|
||||
choice.id,
|
||||
choice.groupId,
|
||||
choice.customProperties
|
||||
);
|
||||
this._triggerChange(choice.value);
|
||||
}
|
||||
}
|
||||
|
@ -1115,7 +1170,7 @@ class Choices {
|
|||
_handleBackspace(activeItems) {
|
||||
if (this.config.removeItems && activeItems) {
|
||||
const lastItem = activeItems[activeItems.length - 1];
|
||||
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
|
||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
||||
|
||||
// If editing the last item is allowed and there are not other selected items,
|
||||
// we can edit the item value. Otherwise if we can remove items, remove all selected items
|
||||
|
@ -1166,10 +1221,15 @@ class Choices {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// If no duplicates are allowed, and the value already exists
|
||||
// in the array
|
||||
const isUnique = !activeItems.some((item) => item.value === isType('String', value) ? value.trim() : value);
|
||||
const isUnique = !activeItems.some((item) => {
|
||||
if (isType('String', value)) {
|
||||
return item.value === value.trim();
|
||||
}
|
||||
|
||||
return item.value === value;
|
||||
});
|
||||
|
||||
if (
|
||||
!isUnique &&
|
||||
|
@ -1178,7 +1238,9 @@ class Choices {
|
|||
canAddItem
|
||||
) {
|
||||
canAddItem = false;
|
||||
notice = isType('Function', this.config.uniqueItemText) ? this.config.uniqueItemText(value) : this.config.uniqueItemText;
|
||||
notice = isType('Function', this.config.uniqueItemText) ?
|
||||
this.config.uniqueItemText(value) :
|
||||
this.config.uniqueItemText;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1195,7 +1257,7 @@ class Choices {
|
|||
*/
|
||||
_handleLoadingState(isLoading = true) {
|
||||
let placeholderItem = this.itemList.querySelector(`.${this.config.classNames.placeholder}`);
|
||||
if(isLoading) {
|
||||
if (isLoading) {
|
||||
this.containerOuter.classList.add(this.config.classNames.loadingState);
|
||||
this.containerOuter.setAttribute('aria-busy', 'true');
|
||||
if (this.passedElement.type === 'select-one') {
|
||||
|
@ -1211,7 +1273,11 @@ class Choices {
|
|||
} else {
|
||||
// Remove loading states/text
|
||||
this.containerOuter.classList.remove(this.config.classNames.loadingState);
|
||||
const placeholder = this.config.placeholder ? this.config.placeholderValue || this.passedElement.getAttribute('placeholder') : false;
|
||||
const placeholder = this.config.placeholder ?
|
||||
this.config.placeholderValue ||
|
||||
this.passedElement.getAttribute('placeholder') :
|
||||
false;
|
||||
|
||||
if (this.passedElement.type === 'select-one') {
|
||||
placeholderItem.innerHTML = placeholder || '';
|
||||
} else {
|
||||
|
@ -1238,12 +1304,23 @@ class Choices {
|
|||
this._handleLoadingState(false);
|
||||
// Add each result as a choice
|
||||
parsedResults.forEach((result, index) => {
|
||||
const isSelected = result.selected ? result.selected : false;
|
||||
const isDisabled = result.disabled ? result.disabled : false;
|
||||
if (result.choices) {
|
||||
this._addGroup(result, (result.id || null), value, label);
|
||||
const groupId = (result.id || null);
|
||||
this._addGroup(
|
||||
result,
|
||||
groupId,
|
||||
value,
|
||||
label
|
||||
);
|
||||
} else {
|
||||
this._addChoice(isSelected, isDisabled, result[value], result[label]);
|
||||
this._addChoice(
|
||||
result[value],
|
||||
result[label],
|
||||
result.selected,
|
||||
result.disabled,
|
||||
undefined,
|
||||
result['customProperties']
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -1293,7 +1370,7 @@ class Choices {
|
|||
}
|
||||
|
||||
const choices = this.store.getChoices();
|
||||
const hasUnactiveChoices = choices.some((option) => option.active !== true);
|
||||
const hasUnactiveChoices = choices.some(option => !option.active);
|
||||
|
||||
// Run callback if it is a function
|
||||
if (this.input === document.activeElement) {
|
||||
|
@ -1677,7 +1754,7 @@ class Choices {
|
|||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
const hasShiftKey = e.shiftKey;
|
||||
|
||||
if(foundTarget = findAncestorByAttrName(target, 'data-button')) {
|
||||
if (foundTarget = findAncestorByAttrName(target, 'data-button')) {
|
||||
this._handleButtonAction(activeItems, foundTarget);
|
||||
} else if (foundTarget = findAncestorByAttrName(target, 'data-item')) {
|
||||
this._handleItemAction(activeItems, foundTarget, hasShiftKey);
|
||||
|
@ -1725,7 +1802,7 @@ class Choices {
|
|||
this.hideDropdown(true);
|
||||
}
|
||||
} else {
|
||||
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
|
||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
||||
|
||||
// De-select any highlighted items
|
||||
if (hasHighlightedItems) {
|
||||
|
@ -1823,7 +1900,7 @@ class Choices {
|
|||
if (this.containerOuter.contains(target)) {
|
||||
const activeItems = this.store.getItemsFilteredByActive();
|
||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
|
||||
const hasHighlightedItems = activeItems.some(item => item.highlighted);
|
||||
const blurActions = {
|
||||
text: () => {
|
||||
if (target === this.input) {
|
||||
|
@ -1995,10 +2072,13 @@ class Choices {
|
|||
* Add item to store with correct value
|
||||
* @param {String} value Value to add to store
|
||||
* @param {String} label Label to add to store
|
||||
* @param {Number} choiceId ID of the associated choice that was selected
|
||||
* @param {Number} groupId ID of group choice is within. Negative number indicates no group
|
||||
* @param {Object} customProperties Object containing user defined properties
|
||||
* @return {Object} Class instance
|
||||
* @public
|
||||
*/
|
||||
_addItem(value, label, choiceId = -1, groupId = -1) {
|
||||
_addItem(value, label, choiceId = -1, groupId = -1, customProperties = null) {
|
||||
let passedValue = isType('String', value) ? value.trim() : value;
|
||||
const items = this.store.getItems();
|
||||
const passedLabel = label || passedValue;
|
||||
|
@ -2020,14 +2100,14 @@ class Choices {
|
|||
passedValue += this.config.appendValue.toString();
|
||||
}
|
||||
|
||||
this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId));
|
||||
this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId, customProperties));
|
||||
|
||||
if (this.passedElement.type === 'select-one') {
|
||||
this.removeActiveItems(id);
|
||||
}
|
||||
|
||||
// Trigger change event
|
||||
if(group && group.value) {
|
||||
if (group && group.value) {
|
||||
triggerEvent(this.passedElement, 'addItem', {
|
||||
id,
|
||||
value: passedValue,
|
||||
|
@ -2069,7 +2149,7 @@ class Choices {
|
|||
|
||||
this.store.dispatch(removeItem(id, choiceId));
|
||||
|
||||
if(group && group.value) {
|
||||
if (group && group.value) {
|
||||
triggerEvent(this.passedElement, 'removeItem', {
|
||||
id,
|
||||
value,
|
||||
|
@ -2089,15 +2169,16 @@ class Choices {
|
|||
|
||||
/**
|
||||
* Add choice to dropdown
|
||||
* @param {Boolean} isSelected Whether choice is selected
|
||||
* @param {Boolean} isDisabled Whether choice is disabled
|
||||
* @param {String} value Value of choice
|
||||
* @param {String} Label Label of choice
|
||||
* @param {Boolean} isSelected Whether choice is selected
|
||||
* @param {Boolean} isDisabled Whether choice is disabled
|
||||
* @param {Number} groupId ID of group choice is within. Negative number indicates no group
|
||||
* @param {Object} customProperties Object containing user defined properties
|
||||
* @return
|
||||
* @private
|
||||
*/
|
||||
_addChoice(isSelected, isDisabled, value, label, groupId = -1) {
|
||||
_addChoice(value, label, isSelected = false, isDisabled = false, groupId = -1, customProperties = null) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
return;
|
||||
}
|
||||
|
@ -2108,10 +2189,24 @@ class Choices {
|
|||
const choiceId = choices ? choices.length + 1 : 1;
|
||||
const choiceElementId = `${this.baseId}-${this.idNames.itemChoice}-${choiceId}`;
|
||||
|
||||
this.store.dispatch(addChoice(value, choiceLabel, choiceId, groupId, isDisabled, choiceElementId));
|
||||
this.store.dispatch(addChoice(
|
||||
value,
|
||||
choiceLabel,
|
||||
choiceId,
|
||||
groupId,
|
||||
isDisabled,
|
||||
choiceElementId,
|
||||
customProperties
|
||||
));
|
||||
|
||||
if (isSelected) {
|
||||
this._addItem(value, choiceLabel, choiceId);
|
||||
this._addItem(
|
||||
value,
|
||||
choiceLabel,
|
||||
choiceId,
|
||||
undefined,
|
||||
customProperties
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2139,23 +2234,36 @@ class Choices {
|
|||
const isDisabled = group.disabled ? group.disabled : false;
|
||||
|
||||
if (groupChoices) {
|
||||
this.store.dispatch(addGroup(group.label, groupId, true, isDisabled));
|
||||
this.store.dispatch(addGroup(
|
||||
group.label,
|
||||
groupId,
|
||||
true,
|
||||
isDisabled
|
||||
));
|
||||
|
||||
groupChoices.forEach((option) => {
|
||||
const isOptDisabled = (option.disabled || (option.parentNode && option.parentNode.disabled)) || false;
|
||||
const isOptSelected = option.selected ? option.selected : false;
|
||||
let label;
|
||||
const isOptDisabled = option.disabled ||
|
||||
(option.parentNode && option.parentNode.disabled);
|
||||
let label = isType('Object', option) ?
|
||||
option[labelKey] :
|
||||
option.innerHTML;
|
||||
|
||||
if (isType('Object', option)) {
|
||||
label = option[labelKey] || option[valueKey];
|
||||
} else {
|
||||
label = option.innerHTML;
|
||||
}
|
||||
|
||||
this._addChoice(isOptSelected, isOptDisabled, option[valueKey], label, groupId);
|
||||
this._addChoice(
|
||||
option[valueKey],
|
||||
label,
|
||||
option.selected,
|
||||
isOptDisabled,
|
||||
groupId,
|
||||
option.customProperties
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.store.dispatch(addGroup(group.label, group.id, false, group.disabled));
|
||||
this.store.dispatch(addGroup(
|
||||
group.label,
|
||||
group.id,
|
||||
false,
|
||||
group.disabled
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2180,74 +2288,231 @@ class Choices {
|
|||
* @private
|
||||
*/
|
||||
_createTemplates() {
|
||||
const classNames = this.config.classNames;
|
||||
const globalClasses = this.config.classNames;
|
||||
const templates = {
|
||||
containerOuter: (direction) => {
|
||||
return strToEl(`
|
||||
<div class="${classNames.containerOuter}" ${this.isSelectElement ? ( this.config.searchEnabled ? 'role="combobox" aria-autocomplete="list"' : 'role="listbox"') : ''} data-type="${this.passedElement.type}" ${this.passedElement.type === 'select-one' ? 'tabindex="0"' : ''} aria-haspopup="true" aria-expanded="false" dir="${direction}"></div>
|
||||
<div
|
||||
class="${globalClasses.containerOuter}"
|
||||
${this.isSelectElement ? (this.config.searchEnabled ?
|
||||
'role="combobox" aria-autocomplete="list"' :
|
||||
'role="listbox"') :
|
||||
''
|
||||
}
|
||||
data-type="${this.passedElement.type}"
|
||||
${this.passedElement.type === 'select-one' ?
|
||||
'tabindex="0"' :
|
||||
''
|
||||
}
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
dir="${direction}"
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
containerInner: () => {
|
||||
return strToEl(`
|
||||
<div class="${classNames.containerInner}"></div>
|
||||
<div class="${globalClasses.containerInner}"></div>
|
||||
`);
|
||||
},
|
||||
itemList: () => {
|
||||
const localClasses = classNames(
|
||||
globalClasses.list,
|
||||
{
|
||||
[globalClasses.listSingle]: (this.passedElement.type === 'select-one'),
|
||||
[globalClasses.listItems]: (this.passedElement.type !== 'select-one')
|
||||
}
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.list} ${this.passedElement.type === 'select-one' ? classNames.listSingle : classNames.listItems}"></div>
|
||||
<div class="${localClasses}"></div>
|
||||
`);
|
||||
},
|
||||
placeholder: (value) => {
|
||||
return strToEl(`
|
||||
<div class="${classNames.placeholder}">${value}</div>
|
||||
<div class="${globalClasses.placeholder}">
|
||||
${value}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
item: (data) => {
|
||||
let localClasses = classNames(
|
||||
globalClasses.item,
|
||||
{
|
||||
[globalClasses.highlightedState]: data.highlighted,
|
||||
[globalClasses.itemSelectable]: !data.highlighted
|
||||
}
|
||||
);
|
||||
|
||||
if (this.config.removeItemButton) {
|
||||
localClasses = classNames(
|
||||
globalClasses.item,
|
||||
{
|
||||
[globalClasses.highlightedState]: data.highlighted,
|
||||
[globalClasses.itemSelectable]: !data.disabled
|
||||
}
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : ''} ${!data.disabled ? classNames.itemSelectable : ''}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''} data-deletable>
|
||||
${data.label}<button type="button" class="${classNames.button}" data-button>Remove item</button>
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-item
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
data-deletable
|
||||
${data.active ?
|
||||
'aria-selected="true"' :
|
||||
''
|
||||
}
|
||||
${data.disabled ?
|
||||
'aria-disabled="true"' :
|
||||
''
|
||||
}
|
||||
>
|
||||
${data.label}<!--
|
||||
--><button
|
||||
type="button"
|
||||
class="${globalClasses.button}"
|
||||
data-button
|
||||
aria-label="Remove item: '${data.value}'"
|
||||
>
|
||||
Remove item
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}" ${data.active ? 'aria-selected="true"' : ''} ${data.disabled ? 'aria-disabled="true"' : ''}>
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-item
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
${data.active ?
|
||||
'aria-selected="true"' :
|
||||
''
|
||||
}
|
||||
${data.disabled ?
|
||||
'aria-disabled="true"' :
|
||||
''
|
||||
}
|
||||
>
|
||||
${data.label}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
choiceList: () => {
|
||||
return strToEl(`
|
||||
<div class="${classNames.list}" dir="ltr" role="listbox" ${this.passedElement.type !== 'select-one' ? 'aria-multiselectable="true"' : ''}></div>
|
||||
<div
|
||||
class="${globalClasses.list}"
|
||||
dir="ltr"
|
||||
role="listbox"
|
||||
${this.passedElement.type !== 'select-one' ?
|
||||
'aria-multiselectable="true"' :
|
||||
''
|
||||
}
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
choiceGroup: (data) => {
|
||||
let localClasses = classNames(
|
||||
globalClasses.group,
|
||||
{
|
||||
[globalClasses.itemDisabled]: data.disabled
|
||||
}
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.group} ${data.disabled ? classNames.itemDisabled : ''}" data-group data-id="${data.id}" data-value="${data.value}" role="group" ${data.disabled ? 'aria-disabled="true"' : ''}>
|
||||
<div class="${classNames.groupHeading}">${data.value}</div>
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-group
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
role="group"
|
||||
${data.disabled ?
|
||||
'aria-disabled="true"' :
|
||||
''
|
||||
}
|
||||
>
|
||||
<div class="${globalClasses.groupHeading}">${data.value}</div>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
choice: (data) => {
|
||||
let localClasses = classNames(
|
||||
globalClasses.item,
|
||||
globalClasses.itemChoice,
|
||||
{
|
||||
[globalClasses.itemDisabled]: data.disabled,
|
||||
[globalClasses.itemSelectable]: !data.disabled
|
||||
}
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="${this.config.itemSelectText}" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} id="${data.elementId}" data-id="${data.id}" data-value="${data.value}" ${data.groupId > 0 ? 'role="treeitem"' : 'role="option"'}>
|
||||
<div
|
||||
class="${localClasses}"
|
||||
data-select-text="${this.config.itemSelectText}"
|
||||
data-choice
|
||||
data-id="${data.id}"
|
||||
data-value="${data.value}"
|
||||
${data.disabled ?
|
||||
'data-choice-disabled aria-disabled="true"' :
|
||||
'data-choice-selectable'
|
||||
}
|
||||
id="${data.elementId}"
|
||||
${data.groupId > 0 ?
|
||||
'role="treeitem"' :
|
||||
'role="option"'
|
||||
}
|
||||
>
|
||||
${data.label}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
input: () => {
|
||||
let localClasses = classNames(
|
||||
globalClasses.input,
|
||||
globalClasses.inputCloned
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<input type="text" class="${classNames.input} ${classNames.inputCloned}" autocomplete="off" autocapitalize="off" spellcheck="false" role="textbox" aria-autocomplete="list">
|
||||
<input
|
||||
type="text"
|
||||
class="${localClasses}"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
role="textbox"
|
||||
aria-autocomplete="list"
|
||||
>
|
||||
`);
|
||||
},
|
||||
dropdown: () => {
|
||||
let localClasses = classNames(
|
||||
globalClasses.list,
|
||||
globalClasses.listDropdown
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.list} ${classNames.listDropdown}" aria-expanded="false"></div>
|
||||
<div
|
||||
class="${localClasses}"
|
||||
aria-expanded="false"
|
||||
>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
notice: (label) => {
|
||||
let localClasses = classNames(
|
||||
globalClasses.item,
|
||||
globalClasses.itemChoice
|
||||
);
|
||||
|
||||
return strToEl(`
|
||||
<div class="${classNames.item} ${classNames.itemChoice}">${label}</div>
|
||||
<div class="${localClasses}">
|
||||
${label}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
option: (data) => {
|
||||
|
@ -2366,26 +2631,43 @@ class Choices {
|
|||
}
|
||||
|
||||
// Determine whether there is a selected choice
|
||||
const hasSelectedChoice = allChoices.some((choice) => {
|
||||
return choice.selected === true;
|
||||
});
|
||||
const hasSelectedChoice = allChoices.some(choice => choice.selected);
|
||||
|
||||
// Add each choice
|
||||
allChoices.forEach((choice, index) => {
|
||||
const isDisabled = choice.disabled ? choice.disabled : false;
|
||||
const isSelected = choice.selected ? choice.selected : false;
|
||||
// Pre-select first choice if it's a single select
|
||||
if (this.passedElement.type === 'select-one') {
|
||||
if (hasSelectedChoice || (!hasSelectedChoice && index > 0)) {
|
||||
// If there is a selected choice already or the choice is not
|
||||
// the first in the array, add each choice normally
|
||||
this._addChoice(isSelected, isDisabled, choice.value, choice.label);
|
||||
this._addChoice(
|
||||
choice.value,
|
||||
choice.label,
|
||||
choice.selected,
|
||||
choice.disabled,
|
||||
undefined,
|
||||
choice.customProperties
|
||||
);
|
||||
} else {
|
||||
// Otherwise pre-select the first choice in the array
|
||||
this._addChoice(true, false, choice.value, choice.label);
|
||||
this._addChoice(
|
||||
choice.value,
|
||||
choice.label,
|
||||
true,
|
||||
false,
|
||||
undefined,
|
||||
choice.customProperties
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._addChoice(isSelected, isDisabled, choice.value, choice.label);
|
||||
this._addChoice(
|
||||
choice.value,
|
||||
choice.label,
|
||||
choice.selected,
|
||||
choice.disabled,
|
||||
undefined,
|
||||
choice.customProperties
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2397,7 +2679,13 @@ class Choices {
|
|||
if (!item.value) {
|
||||
return;
|
||||
}
|
||||
this._addItem(item.value, item.label, item.id);
|
||||
this._addItem(
|
||||
item.value,
|
||||
item.label,
|
||||
item.id,
|
||||
undefined,
|
||||
item.customProperties
|
||||
);
|
||||
} else if (itemType === 'String') {
|
||||
this._addItem(item);
|
||||
}
|
||||
|
|
|
@ -477,10 +477,11 @@ export const getRandomNumber = function(min, max) {
|
|||
* @return {HTMLElement} Converted node element
|
||||
*/
|
||||
export const strToEl = (function() {
|
||||
var tmpEl = document.createElement('div');
|
||||
let tmpEl = document.createElement('div');
|
||||
return function(str) {
|
||||
var r;
|
||||
tmpEl.innerHTML = str;
|
||||
let cleanedInput = str.trim();
|
||||
let r;
|
||||
tmpEl.innerHTML = cleanedInput;
|
||||
r = tmpEl.children[0];
|
||||
|
||||
while (tmpEl.firstChild) {
|
||||
|
|
|
@ -11,11 +11,12 @@ const choices = (state = [], action) => {
|
|||
elementId: action.elementId,
|
||||
groupId: action.groupId,
|
||||
value: action.value,
|
||||
label: action.label,
|
||||
disabled: action.disabled,
|
||||
label: (action.label || action.value),
|
||||
disabled: (action.disabled || false),
|
||||
selected: false,
|
||||
active: true,
|
||||
score: 9999,
|
||||
customProperties: action.customProperties
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ const items = (state = [], action) => {
|
|||
label: action.label,
|
||||
active: true,
|
||||
highlighted: false,
|
||||
customProperties: action.customProperties
|
||||
}];
|
||||
|
||||
return newState.map((item) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "choices.js",
|
||||
"version": "2.8.4",
|
||||
"version": "2.8.7",
|
||||
"description": "A vanilla JS customisable text input/select box plugin",
|
||||
"main": [
|
||||
"./assets/scripts/dist/choices.js",
|
||||
|
|
54
index.html
54
index.html
|
@ -15,7 +15,7 @@
|
|||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<!-- Ignore these -->
|
||||
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.8.4">
|
||||
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.8.7">
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Optional includes -->
|
||||
|
@ -23,8 +23,8 @@
|
|||
<!-- End optional includes -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.8.4">
|
||||
<script src="assets/scripts/dist/choices.min.js?version=2.8.4"></script>
|
||||
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.8.7">
|
||||
<script src="assets/scripts/dist/choices.min.js?version=2.8.7"></script>
|
||||
<!-- End Choices includes -->
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
|
@ -63,7 +63,7 @@
|
|||
<input class="form-control" id="choices-text-prepend-append-value" type="text" value="preset-1, preset-2" placeholder="This is a placeholder">
|
||||
|
||||
<label for="choices-text-preset-values">Preset values passed through options</label>
|
||||
<input class="form-control" id="choices-text-preset-values" type="text" value="olivia@benson.com" placeholder="This is a placeholder">
|
||||
<input class="form-control" id="choices-text-preset-values" type="text" value="Michael Smith" placeholder="This is a placeholder">
|
||||
|
||||
<label for="choices-text-i18n">I18N labels</label>
|
||||
<input class="form-control" data-trigger id="choices-text-i18n" type="text">
|
||||
|
@ -208,7 +208,8 @@
|
|||
<label for="choices-single-preset-options">Option and option groups added via config</label>
|
||||
<select class="form-control" name="choices-single-preset-options" id="choices-single-preset-options" placeholder="This is a placeholder"></select>
|
||||
|
||||
<label for="choices-single-selected-option">Option selected via config</label>
|
||||
<label for="choices-single-selected-option">Option selected via config with custom properties</label>
|
||||
<p><small>Try searching for 'fantastic'</small></p>
|
||||
<select class="form-control" name="choices-single-selected-option" id="choices-single-selected-option" placeholder="This is a placeholder"></select>
|
||||
|
||||
<label for="choices-single-no-sorting">Options without sorting</label>
|
||||
|
@ -306,7 +307,13 @@
|
|||
}).removeActiveItems();
|
||||
|
||||
var textPresetVal = new Choices('#choices-text-preset-values', {
|
||||
items: ['josh@joshuajohnson.co.uk', { value: 'joe@bloggs.co.uk', label: 'Joe Bloggs' } ],
|
||||
items: ['Josh Johnson', {
|
||||
value: 'joe@bloggs.co.uk',
|
||||
label: 'Joe Bloggs',
|
||||
customProperties: {
|
||||
description: 'Joe Blogg is such a generic name'
|
||||
}
|
||||
}],
|
||||
});
|
||||
|
||||
var multipleDefault = new Choices(document.getElementById('choices-multiple-groups'));
|
||||
|
@ -317,14 +324,14 @@
|
|||
maxItemCount: 5,
|
||||
}).ajax(function(callback) {
|
||||
fetch('https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
|
||||
.then(function(response) {
|
||||
response.json().then(function(data) {
|
||||
callback(data.releases, 'title', 'title');
|
||||
.then(function(response) {
|
||||
response.json().then(function(data) {
|
||||
callback(data.releases, 'title', 'title');
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
var multipleCancelButton = new Choices('#choices-multiple-remove-button', {
|
||||
|
@ -358,15 +365,15 @@
|
|||
placeholderValue: 'Pick an Arctic Monkeys record'
|
||||
}).ajax(function(callback) {
|
||||
fetch('https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
|
||||
.then(function(response) {
|
||||
response.json().then(function(data) {
|
||||
callback(data.releases, 'title', 'title');
|
||||
singleFetch.setValueByChoice('Fake Tales Of San Francisco');
|
||||
.then(function(response) {
|
||||
response.json().then(function(data) {
|
||||
callback(data.releases, 'title', 'title');
|
||||
singleFetch.setValueByChoice('Fake Tales Of San Francisco');
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
var singleXhrRemove = new Choices('#choices-single-remove-xhr', {
|
||||
|
@ -433,10 +440,13 @@
|
|||
}], 'value', 'label');
|
||||
|
||||
var singleSelectedOpt = new Choices('#choices-single-selected-option', {
|
||||
searchFields: ['label', 'value', 'customProperties.description'],
|
||||
choices: [
|
||||
{value: 'One', label: 'Label One', selected: true},
|
||||
{value: 'Two', label: 'Label Two', disabled: true},
|
||||
{value: 'Three', label: 'Label Three'},
|
||||
{value: 'Three', label: 'Label Three', customProperties: {
|
||||
description: 'This option is fantastic'
|
||||
}},
|
||||
],
|
||||
}).setValueByChoice('Two');
|
||||
|
||||
|
|
12
package.json
12
package.json
|
@ -1,18 +1,18 @@
|
|||
{
|
||||
"name": "choices.js",
|
||||
"version": "2.8.4",
|
||||
"version": "2.8.7",
|
||||
"description": "A vanilla JS customisable text input/select box plugin",
|
||||
"main": "./assets/scripts/dist/choices.min.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"test": "./node_modules/karma/bin/karma start --single-run --no-auto-watch tests/karma.config.js",
|
||||
"test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js",
|
||||
"css:watch": "nodemon -e scss -x \"npm run css:build\"",
|
||||
"css:build": "npm run css:sass -s && npm run css:prefix -s && npm run css:min -s",
|
||||
"css:sass": "node-sass --output-style expanded --include-path scss assets/styles/scss/base.scss assets/styles/css/base.css && node-sass --output-style expanded --include-path scss assets/styles/scss/choices.scss assets/styles/css/choices.css",
|
||||
"css:prefix": "postcss --use autoprefixer -b 'last 2 versions' assets/styles/css/*.css -d assets/styles/css/",
|
||||
"css:min": "csso assets/styles/css/base.css assets/styles/css/base.min.css && csso assets/styles/css/choices.css assets/styles/css/choices.min.css",
|
||||
"js:build": "concurrently --prefix-colors yellow,green \"webpack --minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"",
|
||||
"js:test": "./node_modules/karma/bin/karma start --single-run --no-auto-watch tests/karma.config.js",
|
||||
"js:test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js",
|
||||
"version": "node version.js --current $npm_package_version --new $npm_config_newVersion",
|
||||
"postversion": "npm run js:build"
|
||||
},
|
||||
|
@ -33,6 +33,7 @@
|
|||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"concurrently": "^3.1.0",
|
||||
"core-js": "^2.4.1",
|
||||
"csso": "^1.8.2",
|
||||
"es6-promise": "^3.2.1",
|
||||
"eslint": "^3.3.0",
|
||||
|
@ -61,8 +62,9 @@
|
|||
"wrapper-webpack-plugin": "^0.1.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"redux": "^3.3.1",
|
||||
"fuse.js": "^2.2.2"
|
||||
"classnames": "^2.2.5",
|
||||
"fuse.js": "^2.2.2",
|
||||
"redux": "^3.3.1"
|
||||
},
|
||||
"npmName": "choices.js",
|
||||
"npmFileMap": [
|
||||
|
|
|
@ -1,39 +1,16 @@
|
|||
import 'whatwg-fetch';
|
||||
import 'es6-promise';
|
||||
import 'core-js/fn/object/assign';
|
||||
import Choices from '../../assets/scripts/src/choices.js';
|
||||
|
||||
if (typeof Object.assign != 'function') {
|
||||
Object.assign = function (target, varArgs) { // .length of function is 2
|
||||
if (target == null) { // TypeError if undefined or null
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
}
|
||||
|
||||
var to = Object(target);
|
||||
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
var nextSource = arguments[index];
|
||||
|
||||
if (nextSource != null) { // Skip over if undefined or null
|
||||
for (var nextKey in nextSource) {
|
||||
// Avoid bugs when hasOwnProperty is shadowed
|
||||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
};
|
||||
}
|
||||
import itemReducer from '../../assets/scripts/src/reducers/items.js';
|
||||
import choiceReducer from '../../assets/scripts/src/reducers/choices.js';
|
||||
import {
|
||||
addItem as addItemAction,
|
||||
addChoice as addChoiceAction
|
||||
} from '../../assets/scripts/src/actions/index.js';
|
||||
|
||||
describe('Choices', () => {
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
describe('should initialize Choices', () => {
|
||||
|
||||
beforeEach(function() {
|
||||
this.input = document.createElement('input');
|
||||
this.input.type = 'text';
|
||||
|
@ -43,6 +20,10 @@ describe('Choices', () => {
|
|||
this.choices = new Choices(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should be defined', function() {
|
||||
expect(this.choices).toBeDefined();
|
||||
});
|
||||
|
@ -78,6 +59,7 @@ describe('Choices', () => {
|
|||
expect(this.choices.config.searchEnabled).toEqual(jasmine.any(Boolean));
|
||||
expect(this.choices.config.searchChoices).toEqual(jasmine.any(Boolean));
|
||||
expect(this.choices.config.searchFloor).toEqual(jasmine.any(Number));
|
||||
expect(this.choices.config.searchResultLimit).toEqual(jasmine.any(Number));
|
||||
expect(this.choices.config.searchFields).toEqual(jasmine.any(Array) || jasmine.any(String));
|
||||
expect(this.choices.config.position).toEqual(jasmine.any(String));
|
||||
expect(this.choices.config.regexFilter).toEqual(null);
|
||||
|
@ -163,6 +145,10 @@ describe('Choices', () => {
|
|||
document.body.appendChild(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should accept a user inputted value', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
|
||||
|
@ -271,6 +257,10 @@ describe('Choices', () => {
|
|||
document.body.appendChild(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should open the choice list on focussing', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
this.choices.input.focus();
|
||||
|
@ -566,6 +556,10 @@ describe('Choices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should add any pre-defined values', function() {
|
||||
expect(this.choices.currentState.items.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
@ -603,6 +597,10 @@ describe('Choices', () => {
|
|||
this.choices = new Choices(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should handle highlightItem()', function() {
|
||||
const items = this.choices.currentState.items;
|
||||
const randomItem = items[Math.floor(Math.random() * items.length)];
|
||||
|
@ -838,6 +836,10 @@ describe('Choices', () => {
|
|||
this.choices = new Choices(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should handle disable()', function() {
|
||||
this.choices.disable();
|
||||
|
||||
|
@ -862,6 +864,10 @@ describe('Choices', () => {
|
|||
this.choices = new Choices(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should handle clearInput()', function() {
|
||||
this.choices.clearInput();
|
||||
expect(this.choices.input.value).toBe('');
|
||||
|
@ -898,6 +904,10 @@ describe('Choices', () => {
|
|||
document.body.appendChild(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should flip the dropdown', function() {
|
||||
this.choices = new Choices(this.input, {
|
||||
position: 'top'
|
||||
|
@ -918,4 +928,133 @@ describe('Choices', () => {
|
|||
expect(container.classList.contains(this.choices.config.classNames.flippedState)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should allow custom properties provided by the user on items or choices', function() {
|
||||
it('should allow the user to supply custom properties for an item', function() {
|
||||
const randomItem = {
|
||||
id: 8999,
|
||||
choiceId: 9000,
|
||||
groupId: 9001,
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
customProperties: {
|
||||
foo: 'bar'
|
||||
}
|
||||
}
|
||||
|
||||
const expectedState = [{
|
||||
id: randomItem.id,
|
||||
choiceId: randomItem.choiceId,
|
||||
groupId: randomItem.groupId,
|
||||
value: randomItem.value,
|
||||
label: randomItem.label,
|
||||
active: true,
|
||||
highlighted: false,
|
||||
customProperties: randomItem.customProperties
|
||||
}];
|
||||
|
||||
const action = addItemAction(
|
||||
randomItem.value,
|
||||
randomItem.label,
|
||||
randomItem.id,
|
||||
randomItem.choiceId,
|
||||
randomItem.groupId,
|
||||
randomItem.customProperties
|
||||
);
|
||||
|
||||
expect(itemReducer([], action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should allow the user to supply custom properties for a choice', function() {
|
||||
const randomChoice = {
|
||||
id: 123,
|
||||
elementId: 321,
|
||||
groupId: 213,
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
disabled: false,
|
||||
customProperties: {
|
||||
foo: 'bar'
|
||||
}
|
||||
}
|
||||
|
||||
const expectedState = [{
|
||||
id: randomChoice.id,
|
||||
elementId: randomChoice.elementId,
|
||||
groupId: randomChoice.groupId,
|
||||
value: randomChoice.value,
|
||||
label: randomChoice.label,
|
||||
disabled: randomChoice.disabled,
|
||||
selected: false,
|
||||
active: true,
|
||||
score: 9999,
|
||||
customProperties: randomChoice.customProperties
|
||||
}];
|
||||
|
||||
const action = addChoiceAction(
|
||||
randomChoice.value,
|
||||
randomChoice.label,
|
||||
randomChoice.id,
|
||||
randomChoice.groupId,
|
||||
randomChoice.disabled,
|
||||
randomChoice.elementId,
|
||||
randomChoice.customProperties
|
||||
);
|
||||
|
||||
expect(choiceReducer([], action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should allow custom properties provided by the user on items or choices', function() {
|
||||
beforeEach(function() {
|
||||
this.input = document.createElement('select');
|
||||
this.input.className = 'js-choices';
|
||||
this.input.setAttribute('multiple', '');
|
||||
|
||||
document.body.appendChild(this.input);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.choices.destroy();
|
||||
});
|
||||
|
||||
it('should allow the user to supply custom properties for a choice that will be inherited by the item when the user selects the choice', function() {
|
||||
const expectedCustomProperties = {
|
||||
isBestOptionEver: true
|
||||
};
|
||||
|
||||
this.choices = new Choices(this.input);
|
||||
this.choices.setChoices([{
|
||||
value: '42',
|
||||
label: 'My awesome choice',
|
||||
selected: false,
|
||||
disabled: false,
|
||||
customProperties: expectedCustomProperties
|
||||
}], 'value', 'label', true);
|
||||
|
||||
this.choices.setValueByChoice('42');
|
||||
const selectedItems = this.choices.getValue();
|
||||
|
||||
expect(selectedItems.length).toBe(1);
|
||||
expect(selectedItems[0].customProperties).toBe(expectedCustomProperties);
|
||||
});
|
||||
|
||||
it('should allow the user to supply custom properties when directly creating a selected item', function() {
|
||||
const expectedCustomProperties = {
|
||||
isBestOptionEver: true
|
||||
};
|
||||
|
||||
this.choices = new Choices(this.input);
|
||||
|
||||
this.choices.setValue([{
|
||||
value: 'bar',
|
||||
label: 'foo',
|
||||
customProperties: expectedCustomProperties
|
||||
}]);
|
||||
const selectedItems = this.choices.getValue();
|
||||
|
||||
expect(selectedItems.length).toBe(1);
|
||||
expect(selectedItems[0].customProperties).toBe(expectedCustomProperties);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
18
version.js
18
version.js
|
@ -1,10 +1,10 @@
|
|||
// Example usage: npm --newVersion=2.7.2 run version
|
||||
// Example usage: npm --newVersion=2.8.7 run version
|
||||
|
||||
const fs = require('fs'),
|
||||
path = require('path'),
|
||||
config = {
|
||||
files: ['bower.json', 'package.json', 'index.html']
|
||||
};
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const config = {
|
||||
files: ['bower.json', 'package.json', 'index.html', 'version.js']
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert node arguments into an object
|
||||
|
@ -30,11 +30,17 @@ const argvToObject = () => {
|
|||
return args;
|
||||
};
|
||||
|
||||
/**
|
||||
* Loop through files updating the current version
|
||||
* @param {Object} config
|
||||
*/
|
||||
const updateVersion = (config) => {
|
||||
const args = argvToObject();
|
||||
const currentVersion = args.current;
|
||||
const newVersion = args.new;
|
||||
|
||||
console.log(`Updating version from ${currentVersion} to ${newVersion}`);
|
||||
|
||||
config.files.forEach((file) => {
|
||||
const filePath = path.join(__dirname, file);
|
||||
const regex = new RegExp(currentVersion, 'g');
|
||||
|
|
Loading…
Reference in a new issue