Merge branch 'master' into feature/select-placeholders

* master: (48 commits)
  select-one input is now not focusable in disabled state (fixes #137)
  Don't use template literals on demo
  Use nornmal functions on demo
  Update README.md
  Version 2.7.7
  Resolve broken cancel buttons
  Resolve ARIA role bug
  Update README.md
  naming change
  lint fixes
  Fix item custom template select bug
  Version 2.7.6
  Update documentation Added showDropdown/hideDropdown to list of events
  Added showDropdown/hideDropdown events
  Version 2.7.5
  Add label to highlighting events too
  Add example in index.html + refactoring
  Adding generated sources
  Add feature of passing the label through the event of add/remove item
  Fix choices crash with wicked_pdf
  ...

# Conflicts:
#	assets/scripts/src/choices.js
#	index.html
#	tests/spec/choices_spec.js
This commit is contained in:
Adam Mockor 2017-04-04 16:59:12 +02:00
commit 4a8cdecbfd
17 changed files with 1111 additions and 493 deletions

View file

@ -1,4 +1,4 @@
# Choices.js [![Build Status](https://travis-ci.org/jshjohnson/Choices.svg?branch=master)](https://travis-ci.org/jshjohnson/Choices) # Choices.js ![Build Status](https://travis-ci.org/jshjohnson/Choices.svg?branch=master)
A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency. A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
[Demo](https://joshuajohnson.co.uk/Choices/) [Demo](https://joshuajohnson.co.uk/Choices/)
@ -13,6 +13,10 @@ A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input
* Right-to-left support * Right-to-left support
* Custom templates * Custom templates
----
### Interested in writing your own ES6 JavaScript plugins? Check out [ES6.io](https://ES6.io/friend/JOHNSON) for great tutorials! 💪🏼
----
## Installation ## Installation
With [NPM](https://www.npmjs.com/package/choices.js): With [NPM](https://www.npmjs.com/package/choices.js):
```zsh ```zsh
@ -64,7 +68,8 @@ Or include Choices directly:
paste: true, paste: true,
search: true, search: true,
searchFloor: 1, searchFloor: 1,
flip: true, position: 'auto',
resetScrollPosition: true,
regexFilter: null, regexFilter: null,
shouldSort: true, shouldSort: true,
sortFilter: () => {...}, sortFilter: () => {...},
@ -246,12 +251,19 @@ Pass an array of objects:
**Usage:** The minimum length a search value should be before choices are searched. **Usage:** The minimum length a search value should be before choices are searched.
### flip ### position
**Type:** `Boolean` **Default:** `true` **Type:** `String` **Default:** `auto`
**Input types affected:** `select-one`, `select-multiple` **Input types affected:** `select-one`, `select-multiple`
**Usage:** Whether the dropdown should appear above the input (rather than beneath) if there is not enough space within the window. **Usage:** Whether the dropdown should appear above (`top`) or below (`bottom`) the input. By default, if there is not enough space within the window the dropdown will appear above the input, otherwise below it.
### resetScrollPosition
**Type:** `Boolean` **Default:** `true`
**Input types affected:** `select-multiple`
**Usage:** Whether the scroll position should reset after adding an item.
### regexFilter ### regexFilter
**Type:** `Regex` **Default:** `null` **Type:** `Regex` **Default:** `null`
@ -332,18 +344,18 @@ const example = new Choices(element, {
**Usage:** The text that is shown whilst choices are being populated via AJAX. **Usage:** The text that is shown whilst choices are being populated via AJAX.
### noResultsText ### noResultsText
**Type:** `String` **Default:** `No results found` **Type:** `String/Function` **Default:** `No results found`
**Input types affected:** `select-one`, `select-multiple` **Input types affected:** `select-one`, `select-multiple`
**Usage:** The text that is shown when a user's search has returned no results. **Usage:** The text that is shown when a user's search has returned no results. Optionally pass a function returning a string.
### noChoicesText ### noChoicesText
**Type:** `String` **Default:** `No choices to choose from` **Type:** `String/Function` **Default:** `No choices to choose from`
**Input types affected:** `select-multiple` **Input types affected:** `select-multiple`
**Usage:** The text that is shown when a user has selected all possible choices. **Usage:** The text that is shown when a user has selected all possible choices. Optionally pass a function returning a string.
### itemSelectText ### itemSelectText
**Type:** `String` **Default:** `Press to select` **Type:** `String` **Default:** `Press to select`
@ -457,6 +469,7 @@ element.addEventListener('addItem', function(event) {
// do something creative here... // do something creative here...
console.log(event.detail.id); console.log(event.detail.id);
console.log(event.detail.value); console.log(event.detail.value);
console.log(event.detail.label);
console.log(event.detail.groupValue); console.log(event.detail.groupValue);
}, false); }, false);
@ -467,39 +480,47 @@ example.passedElement.addEventListener('addItem', function(event) {
// do something creative here... // do something creative here...
console.log(event.detail.id); console.log(event.detail.id);
console.log(event.detail.value); console.log(event.detail.value);
console.log(event.detail.label);
console.log(event.detail.groupValue); console.log(event.detail.groupValue);
}, false); }, false);
``` ```
### addItem ### addItem
**Arguments:** `id, value, groupValue` **Arguments:** `id, value, label, groupValue`
**Input types affected:** `text`, `select-one`, `select-multiple` **Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Triggered each time an item is added (programmatically or by the user). **Usage:** Triggered each time an item is added (programmatically or by the user).
### removeItem ### removeItem
**Arguments:** `id, value, groupValue` **Arguments:** `id, value, label, groupValue`
**Input types affected:** `text`, `select-one`, `select-multiple` **Input types affected:** `text`, `select-one`, `select-multiple`
**Usage:** Triggered each time an item is removed (programmatically or by the user). **Usage:** Triggered each time an item is removed (programmatically or by the user).
### highlightItem ### highlightItem
**Arguments:** `id, value, groupValue` **Arguments:** `id, value, label, groupValue`
**Input types affected:** `text`, `select-multiple` **Input types affected:** `text`, `select-multiple`
**Usage:** Triggered each time an item is highlighted. **Usage:** Triggered each time an item is highlighted.
### unhighlightItem ### unhighlightItem
**Arguments:** `id, value, groupValue` **Arguments:** `id, value, label, groupValue`
**Input types affected:** `text`, `select-multiple` **Input types affected:** `text`, `select-multiple`
**Usage:** Triggered each time an item is unhighlighted. **Usage:** Triggered each time an item is unhighlighted.
### choice
**Arguments:** `value`
**Input types affected:** `select-one`, `select-multiple`
**Usage:** Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input.
### change ### change
**Arguments:** `value` **Arguments:** `value`
@ -512,6 +533,16 @@ example.passedElement.addEventListener('addItem', function(event) {
**Usage:** Triggered when a user types into an input to search choices. **Usage:** Triggered when a user types into an input to search choices.
### showDropdown
**Arguments:** - **Input types affected:** `select-one`, `select-multiple`
**Usage:** Triggered when the dropdown is shown.
### hideDropdown
**Arguments:** - **Input types affected:** `select-one`, `select-multiple`
**Usage:** Triggered when the dropdown is hidden.
## Methods ## Methods
Methods can be called either directly or by chaining: Methods can be called either directly or by chaining:

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -16,6 +16,7 @@ import {
isScrolledIntoView, isScrolledIntoView,
getAdjacentEl, getAdjacentEl,
wrap, wrap,
getType,
isType, isType,
isElement, isElement,
strToEl, strToEl,
@ -24,6 +25,7 @@ import {
sortByAlpha, sortByAlpha,
sortByScore, sortByScore,
triggerEvent, triggerEvent,
findAncestorByAttrName
} }
from './lib/utils.js'; from './lib/utils.js';
import './lib/polyfills.js'; import './lib/polyfills.js';
@ -61,6 +63,8 @@ class Choices {
searchFloor: 1, searchFloor: 1,
searchPlaceholderValue: null, searchPlaceholderValue: null,
flip: true, flip: true,
position: 'auto',
resetScrollPosition: true,
regexFilter: null, regexFilter: null,
shouldSort: true, shouldSort: true,
sortFilter: sortByAlpha, sortFilter: sortByAlpha,
@ -389,8 +393,11 @@ class Choices {
// Clear choices // Clear choices
this.choiceList.innerHTML = ''; this.choiceList.innerHTML = '';
// Scroll back to top of choices list // Scroll back to top of choices list
this.choiceList.scrollTop = 0; if(this.config.resetScrollPosition){
this.choiceList.scrollTop = 0;
}
// If we have grouped options // If we have grouped options
if (activeGroups.length >= 1 && this.isSearching !== true) { if (activeGroups.length >= 1 && this.isSearching !== true) {
@ -406,9 +413,17 @@ class Choices {
this._highlightChoice(); this._highlightChoice();
} else { } else {
// Otherwise show a notice // Otherwise show a notice
const dropdownItem = this.isSearching ? let dropdownItem;
this._getTemplate('notice', this.config.noResultsText) : let notice;
this._getTemplate('notice', this.config.noChoicesText);
if (this.isSearching) {
notice = isType('Function', this.config.noResultsText) ? this.config.noResultsText() : this.config.noResultsText;
dropdownItem = this._getTemplate('notice', notice);
} else {
notice = isType('Function', this.config.noChoicesText) ? this.config.noChoicesText() : this.config.noChoicesText;
dropdownItem = this._getTemplate('notice', notice);
}
this.choiceList.appendChild(dropdownItem); this.choiceList.appendChild(dropdownItem);
} }
} }
@ -461,12 +476,14 @@ class Choices {
triggerEvent(this.passedElement, 'highlightItem', { triggerEvent(this.passedElement, 'highlightItem', {
id, id,
value: item.value, value: item.value,
label: item.label,
groupValue: group.value groupValue: group.value
}); });
} else { } else {
triggerEvent(this.passedElement, 'highlightItem', { triggerEvent(this.passedElement, 'highlightItem', {
id, id,
value: item.value, value: item.value,
label: item.label,
}); });
} }
} }
@ -492,12 +509,14 @@ class Choices {
triggerEvent(this.passedElement, 'unhighlightItem', { triggerEvent(this.passedElement, 'unhighlightItem', {
id, id,
value: item.value, value: item.value,
label: item.label,
groupValue: group.value groupValue: group.value
}); });
} else { } else {
triggerEvent(this.passedElement, 'unhighlightItem', { triggerEvent(this.passedElement, 'unhighlightItem', {
id, id,
value: item.value, value: item.value,
label: item.label,
}); });
} }
@ -605,17 +624,29 @@ class Choices {
showDropdown(focusInput = false) { showDropdown(focusInput = false) {
const body = document.body; const body = document.body;
const html = document.documentElement; const html = document.documentElement;
const winHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, const winHeight = Math.max(
html.scrollHeight, html.offsetHeight); body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
this.containerOuter.classList.add(this.config.classNames.openState); this.containerOuter.classList.add(this.config.classNames.openState);
this.containerOuter.setAttribute('aria-expanded', 'true'); this.containerOuter.setAttribute('aria-expanded', 'true');
this.dropdown.classList.add(this.config.classNames.activeState); this.dropdown.classList.add(this.config.classNames.activeState);
this.dropdown.setAttribute('aria-expanded', 'true');
const dimensions = this.dropdown.getBoundingClientRect(); const dimensions = this.dropdown.getBoundingClientRect();
const dropdownPos = Math.ceil(dimensions.top + window.scrollY + dimensions.height); const dropdownPos = Math.ceil(dimensions.top + window.scrollY + dimensions.height);
// If flip is enabled and the dropdown bottom position is greater than the window height flip the dropdown. // If flip is enabled and the dropdown bottom position is greater than the window height flip the dropdown.
const shouldFlip = this.config.flip ? dropdownPos >= winHeight : false; let shouldFlip = false;
if (this.config.position === 'auto') {
shouldFlip = dropdownPos >= winHeight;
} else if (this.config.position === 'top') {
shouldFlip = true;
}
if (shouldFlip) { if (shouldFlip) {
this.containerOuter.classList.add(this.config.classNames.flippedState); this.containerOuter.classList.add(this.config.classNames.flippedState);
@ -628,6 +659,8 @@ class Choices {
this.input.focus(); this.input.focus();
} }
triggerEvent(this.passedElement, 'showDropdown', {});
return this; return this;
} }
@ -643,6 +676,7 @@ class Choices {
this.containerOuter.classList.remove(this.config.classNames.openState); this.containerOuter.classList.remove(this.config.classNames.openState);
this.containerOuter.setAttribute('aria-expanded', 'false'); this.containerOuter.setAttribute('aria-expanded', 'false');
this.dropdown.classList.remove(this.config.classNames.activeState); this.dropdown.classList.remove(this.config.classNames.activeState);
this.dropdown.setAttribute('aria-expanded', 'false');
if (isFlipped) { if (isFlipped) {
this.containerOuter.classList.remove(this.config.classNames.flippedState); this.containerOuter.classList.remove(this.config.classNames.flippedState);
@ -653,6 +687,8 @@ class Choices {
this.input.blur(); this.input.blur();
} }
triggerEvent(this.passedElement, 'hideDropdown', {});
return this; return this;
} }
@ -700,34 +736,42 @@ class Choices {
/** /**
* Set value of input. If the input is a select box, a choice will be created and selected otherwise * Set value of input. If the input is a select box, a choice will be created and selected otherwise
* an item will created directly. * an item will created directly.
* @param {Array} args Array of value objects or value strings * @param {Array} args Array of value objects or value strings
* @return {Object} Class instance * @return {Object} Class instance
* @public * @public
*/ */
setValue(args) { setValue(args) {
if (this.initialised === true) { if (this.initialised === true) {
// Convert args to an itterable array // Convert args to an iterable array
const values = [...args], const values = [...args],
passedElementType = this.passedElement.type; passedElementType = this.passedElement.type,
handleValue = (item) => {
const itemType = getType(item);
if (itemType === 'Object') {
if (!item.value) return;
// 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);
} else {
this._addItem(item.value, item.label, item.id);
}
} else if (itemType === 'String') {
if (passedElementType !== 'text') {
this._addChoice(true, false, item, item, -1);
} else {
this._addItem(item);
}
}
};
values.forEach((item) => { if (values.length > 1) {
if (isType('Object', item)) { values.forEach((value) => {
if (!item.value) return; handleValue(value);
// 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. } else {
if (passedElementType !== 'text') { handleValue(values[0]);
this._addChoice(true, false, item.value, item.label, -1); }
} else {
this._addItem(item.value, item.label, item.id);
}
} else if (isType('String', item)) {
if (passedElementType !== 'text') {
this._addChoice(true, false, item, item, -1);
} else {
this._addItem(item);
}
}
});
} }
return this; return this;
} }
@ -791,7 +835,7 @@ class Choices {
const isPlaceholder = result.placeholder ? result.placeholder : false; const isPlaceholder = result.placeholder ? result.placeholder : false;
if (result.choices) { if (result.choices) {
this._addGroup(result, index, value, label); this._addGroup(result, (result.id || null), value, label);
} else { } else {
this._addChoice(isSelected, isDisabled, result[value], result[label], -1, isPlaceholder); this._addChoice(isSelected, isDisabled, result[value], result[label], -1, isPlaceholder);
} }
@ -843,6 +887,9 @@ class Choices {
this.input.removeAttribute('disabled'); this.input.removeAttribute('disabled');
this.containerOuter.classList.remove(this.config.classNames.disabledState); this.containerOuter.classList.remove(this.config.classNames.disabledState);
this.containerOuter.removeAttribute('aria-disabled'); this.containerOuter.removeAttribute('aria-disabled');
if (this.passedElement.type === 'select-one') {
this.containerOuter.setAttribute('tabindex', '0');
}
} }
return this; return this;
} }
@ -861,6 +908,9 @@ class Choices {
this.input.setAttribute('disabled', ''); this.input.setAttribute('disabled', '');
this.containerOuter.classList.add(this.config.classNames.disabledState); this.containerOuter.classList.add(this.config.classNames.disabledState);
this.containerOuter.setAttribute('aria-disabled', 'true'); this.containerOuter.setAttribute('aria-disabled', 'true');
if (this.passedElement.type === 'select-one') {
this.containerOuter.setAttribute('tabindex', '-1');
}
} }
return this; return this;
} }
@ -972,6 +1022,10 @@ class Choices {
const choice = this.store.getChoiceById(id); const choice = this.store.getChoiceById(id);
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState); const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
triggerEvent(this.passedElement, 'choice', {
choice,
});
if (choice && !choice.selected && !choice.disabled) { if (choice && !choice.selected && !choice.disabled) {
const canAddItem = this._canAddItem(activeItems, choice.value); const canAddItem = this._canAddItem(activeItems, choice.value);
@ -1101,6 +1155,7 @@ class Choices {
_ajaxCallback() { _ajaxCallback() {
return (results, value, label) => { return (results, value, label) => {
if (!results || !value) return; if (!results || !value) return;
const parsedResults = isType('Object', results) ? [results] : results; const parsedResults = isType('Object', results) ? [results] : results;
if (parsedResults && isType('Array', parsedResults) && parsedResults.length) { if (parsedResults && isType('Array', parsedResults) && parsedResults.length) {
@ -1112,12 +1167,16 @@ class Choices {
const isDisabled = result.disabled ? result.disabled : false; const isDisabled = result.disabled ? result.disabled : false;
const isPlaceholder = result.placeholder ? result.placeholder : false; const isPlaceholder = result.placeholder ? result.placeholder : false;
if (result.choices) { if (result.choices) {
this._addGroup(result, index, value, label); this._addGroup(result, (result.id || null), value, label);
} else { } else {
this._addChoice(isSelected, isDisabled, result[value], result[label], isPlaceholder); this._addChoice(isSelected, isDisabled, result[value], result[label], isPlaceholder);
} }
}); });
} else {
// No results, remove loading state
this._handleLoadingState(false);
} }
this.containerOuter.removeAttribute('aria-busy'); this.containerOuter.removeAttribute('aria-busy');
}; };
} }
@ -1268,6 +1327,8 @@ class Choices {
const escapeKey = 27; const escapeKey = 27;
const upKey = 38; const upKey = 38;
const downKey = 40; const downKey = 40;
const pageUpKey = 33;
const pageDownKey = 34;
const ctrlDownKey = e.ctrlKey || e.metaKey; const ctrlDownKey = e.ctrlKey || e.metaKey;
// If a user is typing and the dropdown is not active // If a user is typing and the dropdown is not active
@ -1342,16 +1403,25 @@ class Choices {
this.showDropdown(true); this.showDropdown(true);
} }
const currentEl = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
const directionInt = e.keyCode === downKey ? 1 : -1;
let nextEl;
this.canSearch = false; this.canSearch = false;
if (currentEl) { const directionInt = e.keyCode === downKey || e.keyCode === pageDownKey ? 1 : -1;
nextEl = getAdjacentEl(currentEl, '[data-choice-selectable]', directionInt); const skipKey = e.metaKey || e.keyCode === pageDownKey || e.keyCode === pageUpKey;
let nextEl;
if (skipKey) {
if (directionInt > 0) {
nextEl = Array.from(this.dropdown.querySelectorAll('[data-choice-selectable]')).pop();
} else {
nextEl = this.dropdown.querySelector('[data-choice-selectable]');
}
} else { } else {
nextEl = this.dropdown.querySelector('[data-choice-selectable]'); const currentEl = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
if (currentEl) {
nextEl = getAdjacentEl(currentEl, '[data-choice-selectable]', directionInt);
} else {
nextEl = this.dropdown.querySelector('[data-choice-selectable]');
}
} }
if (nextEl) { if (nextEl) {
@ -1383,7 +1453,9 @@ class Choices {
[enterKey]: onEnterKey, [enterKey]: onEnterKey,
[escapeKey]: onEscapeKey, [escapeKey]: onEscapeKey,
[upKey]: onDirectionKey, [upKey]: onDirectionKey,
[pageUpKey]: onDirectionKey,
[downKey]: onDirectionKey, [downKey]: onDirectionKey,
[pageDownKey]: onDirectionKey,
[deleteKey]: onDeleteKey, [deleteKey]: onDeleteKey,
[backKey]: onDeleteKey, [backKey]: onDeleteKey,
}; };
@ -1511,13 +1583,16 @@ class Choices {
_onMouseDown(e) { _onMouseDown(e) {
const target = e.target; const target = e.target;
if (this.containerOuter.contains(target) && target !== this.input) { if (this.containerOuter.contains(target) && target !== this.input) {
let foundTarget;
const activeItems = this.store.getItemsFilteredByActive(); const activeItems = this.store.getItemsFilteredByActive();
const hasShiftKey = e.shiftKey; const hasShiftKey = e.shiftKey;
if (target.hasAttribute('data-item')) { if(foundTarget = findAncestorByAttrName(target, 'data-button')) {
this._handleItemAction(activeItems, target, hasShiftKey); this._handleButtonAction(activeItems, foundTarget);
} else if (target.hasAttribute('data-choice')) { } else if (foundTarget = findAncestorByAttrName(target, 'data-item')) {
this._handleChoiceAction(activeItems, target); this._handleItemAction(activeItems, foundTarget, hasShiftKey);
} else if (foundTarget = findAncestorByAttrName(target, 'data-choice')) {
this._handleChoiceAction(activeItems, foundTarget);
} }
e.preventDefault(); e.preventDefault();
@ -1535,6 +1610,7 @@ class Choices {
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState); const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
const activeItems = this.store.getItemsFilteredByActive(); const activeItems = this.store.getItemsFilteredByActive();
// If target is something that concerns us // If target is something that concerns us
if (this.containerOuter.contains(target)) { if (this.containerOuter.contains(target)) {
// Handle button delete // Handle button delete
@ -1859,12 +1935,14 @@ class Choices {
triggerEvent(this.passedElement, 'addItem', { triggerEvent(this.passedElement, 'addItem', {
id, id,
value: passedValue, value: passedValue,
label: passedLabel,
groupValue: group.value, groupValue: group.value,
}); });
} else { } else {
triggerEvent(this.passedElement, 'addItem', { triggerEvent(this.passedElement, 'addItem', {
id, id,
value: passedValue, value: passedValue,
label: passedLabel,
}); });
} }
@ -1886,6 +1964,7 @@ class Choices {
const id = item.id; const id = item.id;
const value = item.value; const value = item.value;
const label = item.label;
const choiceId = item.choiceId; const choiceId = item.choiceId;
const groupId = item.groupId; const groupId = item.groupId;
const group = groupId >= 0 ? this.store.getGroupById(groupId) : null; const group = groupId >= 0 ? this.store.getGroupById(groupId) : null;
@ -1896,12 +1975,14 @@ class Choices {
triggerEvent(this.passedElement, 'removeItem', { triggerEvent(this.passedElement, 'removeItem', {
id, id,
value, value,
label,
groupValue: group.value, groupValue: group.value,
}); });
} else { } else {
triggerEvent(this.passedElement, 'removeItem', { triggerEvent(this.passedElement, 'removeItem', {
id, id,
value, value,
label,
}); });
} }
@ -1953,7 +2034,7 @@ class Choices {
*/ */
_addGroup(group, id, valueKey = 'value', labelKey = 'label') { _addGroup(group, id, valueKey = 'value', labelKey = 'label') {
const groupChoices = isType('Object', group) ? group.choices : Array.from(group.getElementsByTagName('OPTION')); const groupChoices = isType('Object', group) ? group.choices : Array.from(group.getElementsByTagName('OPTION'));
const groupId = id; const groupId = id ? id : Math.floor(new Date().valueOf() * Math.random());
const isDisabled = group.disabled ? group.disabled : false; const isDisabled = group.disabled ? group.disabled : false;
if (groupChoices) { if (groupChoices) {
@ -2241,8 +2322,8 @@ class Choices {
this.isSearching = false; this.isSearching = false;
if (passedGroups && passedGroups.length) { if (passedGroups && passedGroups.length) {
passedGroups.forEach((group, index) => { passedGroups.forEach((group) => {
this._addGroup(group, index); this._addGroup(group, (group.id || null));
}); });
} else { } else {
const passedOptions = Array.from(this.passedElement.options); const passedOptions = Array.from(this.passedElement.options);
@ -2292,10 +2373,11 @@ class Choices {
} else if (this.isTextElement) { } else if (this.isTextElement) {
// Add any preset values seperated by delimiter // Add any preset values seperated by delimiter
this.presetItems.forEach((item) => { this.presetItems.forEach((item) => {
if (isType('Object', item)) { const itemType = getType(item);
if (itemType === 'Object') {
if (!item.value) return; if (!item.value) return;
this._addItem(item.value, item.label, item.id); this._addItem(item.value, item.label, item.id);
} else if (isType('String', item)) { } else if (itemType === 'String') {
this._addItem(item); this._addItem(item);
} }
}); });

View file

@ -10,6 +10,16 @@ export const capitalise = function(str) {
}); });
}; };
/**
* Tests the type of an object
* @param {String} type Type to test object against
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const getType = function(obj) {
return Object.prototype.toString.call(obj).slice(8, -1);
};
/** /**
* Tests the type of an object * Tests the type of an object
* @param {String} type Type to test object against * @param {String} type Type to test object against
@ -17,7 +27,7 @@ export const capitalise = function(str) {
* @return {Boolean} * @return {Boolean}
*/ */
export const isType = function(type, obj) { export const isType = function(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1); var clas = getType(obj);
return obj !== undefined && obj !== null && clas === type; return obj !== undefined && obj !== null && clas === type;
}; };
@ -30,7 +40,7 @@ export const isNode = (o) => {
return ( return (
typeof Node === "object" ? o instanceof Node : typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string" o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string"
); );
}; };
/** /**
@ -42,7 +52,7 @@ export const isElement = (o) => {
return ( return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string" o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"
); );
}; };
/** /**
@ -248,6 +258,26 @@ export const findAncestor = function(el, cls) {
return el; return el;
}; };
/**
* Find ancestor in DOM tree by attribute name
* @param {NodeElement} el Element to start search from
* @param {string} attr Attribute name of parent
* @return {?NodeElement} Found parent element or null
*/
export const findAncestorByAttrName = function(el, attr) {
let target = el;
while (target) {
if (target.hasAttribute(attr)) {
return target;
}
target = target.parentElement;
}
return null;
};
/** /**
* Debounce an event handler. * Debounce an event handler.
* @param {Function} func Function to run after wait * @param {Function} func Function to run after wait

View file

@ -138,4 +138,12 @@ h6, .h6 {
display: none; display: none;
} }
.zero-bottom {
margin-bottom: 0;
}
.zero-top {
margin-top: 0;
}
/*===== End of Section comment block ======*/ /*===== End of Section comment block ======*/

View file

@ -1 +1 @@
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:"Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label{margin-bottom:8px;font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:36px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none} *{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:"Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label{margin-bottom:8px;font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:36px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none}.zero-bottom{margin-bottom:0}.zero-top{margin-top:0}

View file

@ -46,7 +46,7 @@
} }
.choices[data-type*="select-one"] .choices__button { .choices[data-type*="select-one"] .choices__button {
background-image: url("../../icons//cross-inverse.svg"); background-image: url("../../icons/cross-inverse.svg");
padding: 0; padding: 0;
background-size: 8px; background-size: 8px;
height: 100%; height: 100%;
@ -113,7 +113,7 @@
margin-left: 8px; margin-left: 8px;
padding-left: 16px; padding-left: 16px;
border-left: 1px solid #008fa1; border-left: 1px solid #008fa1;
background-image: url("../../icons//cross.svg"); background-image: url("../../icons/cross.svg");
background-size: 8px; background-size: 8px;
width: 8px; width: 8px;
line-height: 1; line-height: 1;

File diff suppressed because one or more lines are too long

View file

@ -118,5 +118,7 @@ h6, .h6 { font-size: $global-font-size-h6; }
} }
.visible-ie { display: none; } .visible-ie { display: none; }
.zero-bottom { margin-bottom: 0; }
.zero-top { margin-top: 0; }
/*===== End of Section comment block ======*/ /*===== End of Section comment block ======*/

View file

@ -17,7 +17,7 @@ $choices-keyline-color: #DDDDDD !default;
$choices-primary-color: #00BCD4 !default; $choices-primary-color: #00BCD4 !default;
$choices-disabled-color: #eaeaea !default; $choices-disabled-color: #eaeaea !default;
$choices-highlight-color: $choices-primary-color !default; $choices-highlight-color: $choices-primary-color !default;
$choices-button-icon-path: '../../icons/' !default; $choices-button-icon-path: '../../icons' !default;
$choices-button-dimension: 8px !default; $choices-button-dimension: 8px !default;
$choices-button-offset: 8px !default; $choices-button-offset: 8px !default;

View file

@ -1,6 +1,6 @@
{ {
"name": "choices.js", "name": "choices.js",
"version": "2.6.1", "version": "2.7.7",
"description": "A vanilla JS customisable text input/select box plugin", "description": "A vanilla JS customisable text input/select box plugin",
"main": [ "main": [
"./assets/scripts/dist/choices.js", "./assets/scripts/dist/choices.js",
@ -16,7 +16,7 @@
"node_modules", "node_modules",
"bower_components", "bower_components",
"test", "test",
"tests", "tests"
], ],
"keywords": [ "keywords": [
"customisable", "customisable",

View file

@ -15,7 +15,7 @@
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<!-- Ignore these --> <!-- Ignore these -->
<link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.6.1"> <link rel="stylesheet" href="assets/styles/css/base.min.css?version=2.7.7">
<!-- End ignore these --> <!-- End ignore these -->
<!-- Optional includes --> <!-- Optional includes -->
@ -23,8 +23,8 @@
<!-- End optional includes --> <!-- End optional includes -->
<!-- Choices includes --> <!-- Choices includes -->
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.6.1"> <link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.7.7">
<script src="assets/scripts/dist/choices.min.js?version=2.6.1"></script> <script src="assets/scripts/dist/choices.min.js?version=2.7.7"></script>
<!-- End Choices includes --> <!-- End Choices includes -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
@ -137,6 +137,10 @@
<option value="Dropdown item 4" disabled>Dropdown item 4</option> <option value="Dropdown item 4" disabled>Dropdown item 4</option>
</select> </select>
<label for="label-event">Use label in event (add/remove)</label>
<p id="message"></p>
<select id="choices-multiple-labels" multiple></select>
<hr> <hr>
<h2>Single select input</h2> <h2>Single select input</h2>
@ -238,12 +242,12 @@
<option value="three">Three</option> <option value="three">Three</option>
</select> </select>
<label for="choices-custom-templates">Custom templates</label> <label for="choices-single-custom-templates">Custom templates</label>
<select class="form-control" name="choices-custom-templates" id="choices-custom-templates"> <select class="form-control" name="choices-single-custom-templates" id="choices-single-custom-templates" placeholder="This is a placeholder">
<option value="React">React</option> <option value="React">React</option>
<option value="React">Angular</option> <option value="Angular">Angular</option>
<option value="React">Ember</option> <option value="Ember">Ember</option>
<option value="React">Vue</option> <option value="Vue">Vue</option>
</select> </select>
<p>Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if the value of 'States' is 'New York'</p> <p>Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if the value of 'States' is 'New York'</p>
@ -285,11 +289,12 @@
paste: false, paste: false,
duplicateItems: false, duplicateItems: false,
editItems: true, editItems: true,
addItemText: (value) => { maxItemCount: 5,
return `Appuyez sur Entrée pour ajouter <b>"${value}"</b>`; addItemText: function(value) {
return 'Appuyez sur Entrée pour ajouter <b>"' + String(value) + '"</b>';
}, },
maxItemText: (maxItemCount) => { maxItemText: function(maxItemCount) {
return `${maxItemCount} valeurs peuvent être ajoutées`; return String(maxItemCount) + 'valeurs peuvent être ajoutées';
}, },
uniqueItemText: 'Cette valeur est déjà présente', uniqueItemText: 'Cette valeur est déjà présente',
}); });
@ -297,7 +302,7 @@
var textEmailFilter = new Choices('#choices-text-email-filter', { var textEmailFilter = new Choices('#choices-text-email-filter', {
editItems: true, editItems: true,
regexFilter: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, regexFilter: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
}); }).setValue(['joe@bloggs.com']);
var textDisabled = new Choices('#choices-text-disabled', { var textDisabled = new Choices('#choices-text-disabled', {
addItems: false, addItems: false,
@ -332,7 +337,30 @@
var multipleCancelButton = new Choices('#choices-multiple-remove-button', { var multipleCancelButton = new Choices('#choices-multiple-remove-button', {
removeItemButton: true, removeItemButton: true,
}) });
/* Use label on event */
var choicesSelect = new Choices('#choices-multiple-labels', {
search: false,
removeItemButton: true,
choices: [
{value: 'One', label: 'Label One'},
{value: 'Two', label: 'Label Two', disabled: true},
{value: 'Three', label: 'Label Three'},
],
}).setChoices([
{value: 'Four', label: 'Label Four', disabled: true},
{value: 'Five', label: 'Label Five'},
{value: 'Six', label: 'Label Six', selected: true},
], 'value', 'label', false);
choicesSelect.passedElement.addEventListener('addItem', function(event) {
document.getElementById('message').innerHTML = 'You just added "' + event.detail.label + '"';
});
choicesSelect.passedElement.addEventListener('removeItem', function(event) {
document.getElementById('message').innerHTML = 'You just removed "' + event.detail.label + '"';
});
var singleFetch = new Choices('#choices-single-remote-fetch', { var singleFetch = new Choices('#choices-single-remote-fetch', {
searchPlaceholderValue: 'Pick an Arctic Monkeys record', searchPlaceholderValue: 'Pick an Arctic Monkeys record',
@ -439,28 +467,42 @@
} }
}); });
var singleCustomTemplates = new Choices( var customTemplates = new Choices(document.getElementById('choices-single-custom-templates'), {
document.getElementById('choices-custom-templates'), { searchPlaceholderValue: 'Choose your favourite framework...',
searchPlaceholderValue: 'Choose your favourite framework...', callbackOnCreateTemplates: function (strToEl) {
callbackOnCreateTemplates: function (strToEl) { var classNames = this.config.classNames;
var classNames = this.config.classNames; var itemSelectText = this.config.itemSelectText;
return { return {
item: (data) => { item: function(data) {
return strToEl(` 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\
<span style="margin-right:10px;">🎉</span> ${data.label} class="'+ String(classNames.item) + ' ' + String(data.highlighted ? classNames.highlightedState : classNames.itemSelectable) + '"\
</div> data-item\
`); data-id="'+ String(data.id) + '"\
}, data-value="'+ String(data.value) +'"\
choice: (data) => { '+ String(data.active ? 'aria-selected="true"' : '') + '\
return strToEl(` '+ String(data.disabled ? 'aria-disabled="true"' : '') + '\
<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 style="margin-right:10px;">👉🏽</span> ${data.label} <span style="margin-right:10px;">🎉</span> ' + String(data.label) + '\
</div> </div>\
`); ');
}, },
}; choice: function(data) {
} return strToEl('\
<div\
class="'+ String(classNames.item) + ' ' + String(classNames.itemChoice) + ' ' + String(data.disabled ? classNames.itemDisabled : classNames.itemSelectable) + '"\
data-select-text="'+ String(itemSelectText) +'"\
data-choice \
'+ String(data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable') + '\
data-id="'+ String(data.id) +'"\
data-value="'+ String(data.value) +'"\
'+ String(data.groupId > 0 ? 'role="treeitem"' : 'role="option"') + '\
>\
<span style="margin-right:10px;">👉🏽</span> ' + String(data.label) + '\
</div>\
');
},
};
} }
); );
}); });

View file

@ -1,6 +1,6 @@
{ {
"name": "choices.js", "name": "choices.js",
"version": "2.6.1", "version": "2.7.7",
"description": "A vanilla JS customisable text input/select box plugin", "description": "A vanilla JS customisable text input/select box plugin",
"main": "./assets/scripts/dist/choices.min.js", "main": "./assets/scripts/dist/choices.min.js",
"scripts": { "scripts": {
@ -13,7 +13,8 @@
"js:build": "concurrently --prefix-colors yellow,green \"webpack --minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"", "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": "./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", "js:test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js",
"preversion": "npm run js:build" "version": "node version.js --current $npm_package_version --new $npm_config_newVersion",
"postversion": "npm run js:build"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -78,6 +78,7 @@ describe('Choices', () => {
expect(this.choices.config.searchFloor).toEqual(jasmine.any(Number)); expect(this.choices.config.searchFloor).toEqual(jasmine.any(Number));
expect(this.choices.config.searchPlaceholderValue).toEqual(null); expect(this.choices.config.searchPlaceholderValue).toEqual(null);
expect(this.choices.config.flip).toEqual(jasmine.any(Boolean)); expect(this.choices.config.flip).toEqual(jasmine.any(Boolean));
expect(this.choices.config.position).toEqual(jasmine.any(String));
expect(this.choices.config.regexFilter).toEqual(null); expect(this.choices.config.regexFilter).toEqual(null);
expect(this.choices.config.sortFilter).toEqual(jasmine.any(Function)); expect(this.choices.config.sortFilter).toEqual(jasmine.any(Function));
expect(this.choices.config.sortFields).toEqual(jasmine.any(Array) || jasmine.any(String)); expect(this.choices.config.sortFields).toEqual(jasmine.any(Array) || jasmine.any(String));
@ -367,9 +368,56 @@ describe('Choices', () => {
}); });
it('should close the dropdown on double click', function() { it('should close the dropdown on double click', function() {
this.choices = new Choices(this.input);
const container = this.choices.containerOuter,
openState = this.choices.config.classNames.openState;
this.choices._onClick({
target: container,
ctrlKey: false,
preventDefault: () => {}
});
this.choices._onClick({
target: container,
ctrlKey: false,
preventDefault: () => {}
});
expect(document.activeElement === this.choices.input && container.classList.contains(openState)).toBe(false);
});
it('should trigger showDropdown on dropdown opening', function() {
this.choices = new Choices(this.input); this.choices = new Choices(this.input);
const container = this.choices.containerOuter; const container = this.choices.containerOuter;
const showDropdownSpy = jasmine.createSpy('showDropdownSpy');
const passedElement = this.choices.passedElement;
passedElement.addEventListener('showDropdown', showDropdownSpy);
this.choices.input.focus();
this.choices._onClick({
target: container,
ctrlKey: false,
preventDefault: () => {}
});
expect(showDropdownSpy).toHaveBeenCalled();
});
it('should trigger hideDropdown on dropdown closing', function() {
this.choices = new Choices(this.input);
const container = this.choices.containerOuter;
const hideDropdownSpy = jasmine.createSpy('hideDropdownSpy');
const passedElement = this.choices.passedElement;
passedElement.addEventListener('hideDropdown', hideDropdownSpy);
this.choices.input.focus();
this.choices._onClick({ this.choices._onClick({
target: container, target: container,
ctrlKey: false, ctrlKey: false,
@ -382,7 +430,7 @@ describe('Choices', () => {
preventDefault: () => {} preventDefault: () => {}
}); });
expect(document.activeElement === this.choices.input && container.classList.contains('is-open')).toBe(false); expect(hideDropdownSpy).toHaveBeenCalled();
}); });
it('should filter choices when searching', function() { it('should filter choices when searching', function() {
@ -735,6 +783,42 @@ describe('Choices', () => {
}); });
}); });
describe('should handle public methods on select-one input types', function() {
beforeEach(function() {
this.input = document.createElement('select');
this.input.className = 'js-choices';
this.input.placeholder = 'Placeholder text';
for (let i = 1; i < 10; i++) {
const option = document.createElement('option');
option.value = `Value ${i}`;
option.innerHTML = `Value ${i}`;
if (i % 2) {
option.selected = true;
}
this.input.appendChild(option);
}
document.body.appendChild(this.input);
this.choices = new Choices(this.input);
});
it('should handle disable()', function() {
this.choices.disable();
expect(this.choices.containerOuter.getAttribute('tabindex')).toBe('-1');
});
it('should handle enable()', function() {
this.choices.enable();
expect(this.choices.containerOuter.getAttribute('tabindex')).toBe('0');
});
});
describe('should handle public methods on text input types', function() { describe('should handle public methods on text input types', function() {
beforeEach(function() { beforeEach(function() {
this.input = document.createElement('input'); this.input = document.createElement('input');
@ -759,4 +843,47 @@ describe('Choices', () => {
expect(randomItem.active).toBe(false); expect(randomItem.active).toBe(false);
}); });
}); });
describe('should react to config options', function() {
beforeEach(function() {
this.input = document.createElement('select');
this.input.className = 'js-choices';
this.input.setAttribute('multiple', '');
for (let i = 1; i < 4; i++) {
const option = document.createElement('option');
option.value = `Value ${i}`;
option.innerHTML = `Value ${i}`;
if (i % 2) {
option.selected = true;
}
this.input.appendChild(option);
}
document.body.appendChild(this.input);
});
it('should flip the dropdown', function() {
this.choices = new Choices(this.input, {
position: 'top'
});
const container = this.choices.containerOuter;
this.choices.input.focus();
expect(container.classList.contains(this.choices.config.classNames.flippedState)).toBe(true);
});
it('shouldn\'t flip the dropdown', function() {
this.choices = new Choices(this.input, {
position: 'bottom'
});
const container = this.choices.containerOuter;
this.choices.input.focus();
expect(container.classList.contains(this.choices.config.classNames.flippedState)).toBe(false);
});
});
}); });

50
version.js Normal file
View file

@ -0,0 +1,50 @@
// Example usage: npm --newVersion=2.7.2 run version
const fs = require('fs'),
path = require('path'),
config = {
files: ['bower.json', 'package.json', 'index.html']
};
/**
* Convert node arguments into an object
* @return {Object} Arguments
*/
const argvToObject = () => {
const args = {};
let arg = null;
process.argv.forEach((val, index) => {
if(/^--/.test(val)) {
arg = {
index: index,
name: val.replace(/^--/, '')
}
return;
}
if(arg && ((arg.index+1 === index ))) {
args[arg.name] = val;
}
});
return args;
};
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');
let contents = fs.readFileSync(filePath, 'utf-8');
contents = contents.replace(regex, newVersion);
fs.writeFileSync(filePath, contents);
});
console.log(`Updated version to ${newVersion}`);
};
updateVersion(config);