Merge pull request #206 from maximmig/master

Various minor corrections.
This commit is contained in:
Josh Johnson 2017-07-17 08:50:07 +01:00 committed by GitHub
commit 23871cc943
5 changed files with 548 additions and 526 deletions

2
.gitignore vendored
View file

@ -1,6 +1,8 @@
node_modules
npm-debug.log
.DS_Store
.gitignore
.idea
.vscode
package-lock.json

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

@ -32,7 +32,6 @@ import {
from './lib/utils.js';
import './lib/polyfills.js';
/**
* Choices
*/
@ -149,8 +148,10 @@ class Choices {
this.element = element;
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
this.isTextElement = this.passedElement.type === 'text';
this.isSelectElement = this.passedElement.type === 'select-one' ||
this.passedElement.type === 'select-multiple';
this.isSelectOneElement = this.passedElement.type === 'select-one';
this.isSelectMultipleElement = this.passedElement.type === 'select-multiple';
this.isSelectElement = this.isSelectOneElement || this.isSelectMultipleElement;
this.isValidElementType = this.isTextElement || this.isSelectElement;
if (!this.passedElement) {
if (!this.config.silent) {
@ -159,7 +160,7 @@ class Choices {
return;
}
if (this.config.shouldSortItems === true && this.passedElement.type === 'select-one') {
if (this.config.shouldSortItems === true && this.isSelectOneElement) {
if (!this.config.silent) {
console.warn('shouldSortElements: Type of passed element is \'select-one\', falling back to false.');
}
@ -212,9 +213,7 @@ class Choices {
console.error('Choices: Your browser doesn\'t support Choices');
}
// Input type check
const isValidType = ['select-one', 'select-multiple', 'text'].some(type => type === this.passedElement.type);
const canInit = isElement(this.passedElement) && isValidType;
const canInit = isElement(this.passedElement) && this.isValidElementType;
if (canInit) {
// If element has already been initalised with Choices
@ -282,9 +281,16 @@ class Choices {
// Reinstate passed element
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
this.passedElement.removeAttribute('tabindex');
this.passedElement.removeAttribute('style', 'display:none;');
// restore original styles if any
const oldStyle = this.passedElement.getAttribute('data-choices-js-orig-style');
if (Boolean(oldStyle)) {
this.passedElement.removeAttribute('data-choices-js-orig-style');
this.passedElement.setAttribute('style', oldStyle);
} else {
this.passedElement.removeAttribute('style');
}
this.passedElement.removeAttribute('aria-hidden');
this.passedElement.removeAttribute('data-choice', 'active');
this.passedElement.removeAttribute('data-choice');
// Re-assign values - this is weird, I know
this.passedElement.value = this.passedElement.value;
@ -324,7 +330,7 @@ class Choices {
groups.forEach((group) => {
// Grab options that are children of this group
const groupChoices = choices.filter((choice) => {
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
return choice.groupId === group.id;
}
return choice.groupId === group.id && !choice.selected;
@ -354,7 +360,7 @@ class Choices {
const { renderSelectedChoices } = this.config;
const appendChoice = (choice) => {
const shouldRender = renderSelectedChoices === 'auto'
? this.passedElement.type === 'select-one' || !choice.selected
? this.isSelectOneElement || !choice.selected
: true;
if (shouldRender) {
const dropdownItem = this._getTemplate('choice', choice);
@ -384,16 +390,16 @@ class Choices {
/**
* Render items into a DOM fragment and append to items list
* @param {Array} items Items to add to list
* @param {DocumentFragment} fragment Fragrment to add items to (optional)
* @param {DocumentFragment} [fragment] Fragment to add items to (optional)
* @return
* @private
*/
renderItems(items, fragment) {
renderItems(items, fragment = null) {
// Create fragment to add elements to
const itemListFragment = fragment || document.createDocumentFragment();
// If sorting is enabled, filter items
if (this.config.shouldSortItems && this.passedElement.type !== 'select-one') {
if (this.config.shouldSortItems && !this.isSelectOneElement) {
items.sort(this.config.sortFilter);
}
@ -442,8 +448,7 @@ class Choices {
// Choices
if (this.currentState.choices !== this.prevState.choices ||
this.currentState.groups !== this.prevState.groups) {
if (this.passedElement.type === 'select-multiple' ||
this.passedElement.type === 'select-one') {
if (this.isSelectElement) {
// Get active groups/choices
const activeGroups = this.store.getGroupsFilteredByActive();
const activeChoices = this.store.getChoicesFilteredByActive();
@ -529,12 +534,13 @@ class Choices {
/**
* Select item (a selected item can be deleted)
* @param {Element} item Element to select
* @param {Boolean} [runEvent=true] Whether to trigger 'highlightItem' event
* @return {Object} Class instance
* @public
*/
highlightItem(item, runEvent = true) {
if (!item) {
return;
return this;
}
const id = item.id;
@ -571,7 +577,7 @@ class Choices {
*/
unhighlightItem(item) {
if (!item) {
return;
return this;
}
const id = item.id;
@ -634,7 +640,7 @@ class Choices {
*/
removeItemsByValue(value) {
if (!value || !isType('String', value)) {
return;
return this;
}
const items = this.store.getItemsFilteredByActive();
@ -798,7 +804,7 @@ class Choices {
}
});
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
return selectedItems[0];
}
@ -816,7 +822,6 @@ class Choices {
if (this.initialised === true) {
// Convert args to an iterable array
const values = [...args],
passedElementType = this.passedElement.type,
handleValue = (item) => {
const itemType = getType(item);
if (itemType === 'Object') {
@ -826,7 +831,7 @@ 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') {
if (!this.isTextElement) {
this._addChoice(
item.value,
item.label,
@ -845,7 +850,7 @@ class Choices {
);
}
} else if (itemType === 'String') {
if (passedElementType !== 'text') {
if (!this.isTextElement) {
this._addChoice(
item,
item,
@ -877,7 +882,7 @@ class Choices {
* @public
*/
setValueByChoice(value) {
if (this.passedElement.type !== 'text') {
if (!this.isTextElement) {
const choices = this.store.getChoices();
// If only one value has been passed, convert to array
const choiceValue = isType('Array', value) ? value : [value];
@ -922,7 +927,7 @@ class Choices {
if (this.initialised === true) {
if (this.isSelectElement) {
if (!isType('Array', choices) || !value) {
return;
return this;
}
// Clear choices if needed
if (replaceChoices) {
@ -931,7 +936,7 @@ class Choices {
// Add choices if passed
if (choices && choices.length) {
this.containerOuter.classList.remove(this.config.classNames.loadingState);
choices.forEach((result, index) => {
choices.forEach((result) => {
if (result.choices) {
this._addGroup(
result,
@ -973,11 +978,13 @@ class Choices {
* @public
*/
clearInput() {
if (this.input.value) this.input.value = '';
if (this.passedElement.type !== 'select-one') {
if (this.input.value){
this.input.value = '';
}
if (!this.isSelectOneElement) {
this._setInputWidth();
}
if (this.passedElement.type !== 'text' && this.config.searchEnabled) {
if (!this.isTextElement && this.config.searchEnabled) {
this.isSearching = false;
this.store.dispatch(activateChoices(true));
}
@ -997,7 +1004,7 @@ class Choices {
this.input.removeAttribute('disabled');
this.containerOuter.classList.remove(this.config.classNames.disabledState);
this.containerOuter.removeAttribute('aria-disabled');
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
this.containerOuter.setAttribute('tabindex', '0');
}
}
@ -1018,7 +1025,7 @@ class Choices {
this.input.setAttribute('disabled', '');
this.containerOuter.classList.add(this.config.classNames.disabledState);
this.containerOuter.setAttribute('aria-disabled', 'true');
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
this.containerOuter.setAttribute('tabindex', '-1');
}
}
@ -1027,7 +1034,7 @@ class Choices {
/**
* Populate options via ajax callback
* @param {Function} fn Passed
* @param {Function} fn Function that actually makes an AJAX request
* @return {Object} Class instance
* @public
*/
@ -1088,7 +1095,7 @@ class Choices {
this._removeItem(itemToRemove);
this._triggerChange(itemToRemove.value);
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
const placeholder = this.config.placeholder ? this.config.placeholderValue || this.passedElement.getAttribute('placeholder') :
false;
if (placeholder) {
@ -1113,7 +1120,7 @@ class Choices {
}
// If we are clicking on an item
if (this.config.removeItems && this.passedElement.type !== 'select-one') {
if (this.config.removeItems && !this.isSelectOneElement) {
const passedId = element.getAttribute('data-id');
// We only want to select one item with a click
@ -1141,7 +1148,7 @@ class Choices {
* Process click of a choice
* @param {Array} activeItems The currently active items
* @param {Element} element Choice being interacted with
* @return {[type]} [description]
* @return
*/
_handleChoiceAction(activeItems, element) {
if (!activeItems || !element) {
@ -1172,10 +1179,10 @@ class Choices {
}
}
this.clearInput(this.passedElement);
this.clearInput();
// We wont to close the dropdown if we are dealing with a single select box
if (hasActiveDropdown && this.passedElement.type === 'select-one') {
if (hasActiveDropdown && this.isSelectOneElement) {
this.hideDropdown();
this.containerOuter.focus();
}
@ -1221,7 +1228,7 @@ class Choices {
this.config.addItemText(value) :
this.config.addItemText;
if (this.passedElement.type === 'select-multiple' || this.passedElement.type === 'text') {
if (this.isSelectMultipleElement || this.isTextElement) {
if (this.config.maxItemCount > 0 && this.config.maxItemCount <= activeItems.length) {
// If there is a max entry limit and we have reached that limit
// don't update
@ -1232,7 +1239,7 @@ class Choices {
}
}
if (this.passedElement.type === 'text' && this.config.addItems && canAddItem) {
if (this.isTextElement && this.config.addItems && canAddItem) {
// If a user has supplied a regular expression filter
if (this.config.regexFilter) {
// Determine whether we can update based on whether
@ -1254,7 +1261,7 @@ class Choices {
if (
!isUnique &&
!this.config.duplicateItems &&
this.passedElement.type !== 'select-one' &&
!this.isSelectOneElement &&
canAddItem
) {
canAddItem = false;
@ -1280,7 +1287,7 @@ class Choices {
if (isLoading) {
this.containerOuter.classList.add(this.config.classNames.loadingState);
this.containerOuter.setAttribute('aria-busy', 'true');
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
if (!placeholderItem) {
placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
this.itemList.appendChild(placeholderItem);
@ -1298,7 +1305,7 @@ class Choices {
this.passedElement.getAttribute('placeholder') :
false;
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
placeholderItem.innerHTML = placeholder || '';
} else {
this.input.placeholder = placeholder || '';
@ -1323,7 +1330,7 @@ class Choices {
// Remove loading states/text
this._handleLoadingState(false);
// Add each result as a choice
parsedResults.forEach((result, index) => {
parsedResults.forEach((result) => {
if (result.choices) {
const groupId = (result.id || null);
this._addGroup(
@ -1427,7 +1434,7 @@ class Choices {
document.addEventListener('mousedown', this._onMouseDown);
document.addEventListener('mouseover', this._onMouseOver);
if (this.passedElement.type && this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
this.containerOuter.addEventListener('focus', this._onFocus);
this.containerOuter.addEventListener('blur', this._onBlur);
}
@ -1452,7 +1459,7 @@ class Choices {
document.removeEventListener('mousedown', this._onMouseDown);
document.removeEventListener('mouseover', this._onMouseOver);
if (this.passedElement.type && this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
this.containerOuter.removeEventListener('focus', this._onFocus);
this.containerOuter.removeEventListener('blur', this._onBlur);
}
@ -1495,7 +1502,6 @@ class Choices {
}
const target = e.target;
const passedElementType = this.passedElement.type;
const activeItems = this.store.getItemsFilteredByActive();
const hasFocusedInput = this.input === document.activeElement;
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
@ -1514,7 +1520,7 @@ class Choices {
const ctrlDownKey = e.ctrlKey || e.metaKey;
// If a user is typing and the dropdown is not active
if (passedElementType !== 'text' && /[a-zA-Z0-9-_ ]/.test(keyString) && !hasActiveDropdown) {
if (!this.isTextElement && /[a-zA-Z0-9-_ ]/.test(keyString) && !hasActiveDropdown) {
this.showDropdown(true);
}
@ -1526,14 +1532,14 @@ class Choices {
this.canSearch = false;
if (this.config.removeItems && !this.input.value && this.input === document.activeElement) {
// Highlight items
this.highlightAll(this.itemList.children);
this.highlightAll();
}
}
};
const onEnterKey = () => {
// If enter key is pressed and the input has a value
if (passedElementType === 'text' && target.value) {
if (this.isTextElement && target.value) {
const value = this.input.value;
const canAddItem = this._canAddItem(activeItems, value);
@ -1544,7 +1550,7 @@ class Choices {
}
this._addItem(value);
this._triggerChange(value);
this.clearInput(this.passedElement);
this.clearInput();
}
}
@ -1562,7 +1568,7 @@ class Choices {
this._handleChoiceAction(activeItems, highlighted);
}
} else if (passedElementType === 'select-one') {
} else if (this.isSelectOneElement) {
// Open single select dropdown if it's not active
if (!hasActiveDropdown) {
this.showDropdown(true);
@ -1580,7 +1586,7 @@ class Choices {
const onDirectionKey = () => {
// If up or down key is pressed, traverse through options
if (hasActiveDropdown || passedElementType === 'select-one') {
if (hasActiveDropdown || this.isSelectOneElement) {
// Show dropdown if focus
if (!hasActiveDropdown) {
this.showDropdown(true);
@ -1624,7 +1630,7 @@ class Choices {
const onDeleteKey = () => {
// If backspace or delete key is pressed and the input has no value
if (hasFocusedInput && !e.target.value && passedElementType !== 'select-one') {
if (hasFocusedInput && !e.target.value && !this.isSelectOneElement) {
this._handleBackspace(activeItems);
e.preventDefault();
}
@ -1692,7 +1698,7 @@ class Choices {
// If user has removed value...
if ((e.keyCode === backKey || e.keyCode === deleteKey) && !e.target.value) {
// ...and it is a multiple select input, activate choices (if searching)
if (this.passedElement.type !== 'text' && this.isSearching) {
if (!this.isTextElement && this.isSearching) {
this.isSearching = false;
this.store.dispatch(activateChoices(true));
}
@ -1706,19 +1712,17 @@ class Choices {
/**
* Input event
* @param {Object} e Event
* @return
* @private
*/
_onInput() {
if (this.passedElement.type !== 'select-one') {
if (!this.isSelectOneElement) {
this._setInputWidth();
}
}
/**
* Touch move event
* @param {Object} e Event
* @return
* @private
*/
@ -1741,7 +1745,7 @@ class Choices {
// If a user tapped within our container...
if (this.wasTap === true && this.containerOuter.contains(target)) {
// ...and we aren't dealing with a single select box, show dropdown/focus input
if ((target === this.containerOuter || target === this.containerInner) && this.passedElement.type !== 'select-one') {
if ((target === this.containerOuter || target === this.containerInner) && !this.isSelectOneElement) {
if (this.isTextElement) {
// If text element, we only want to focus the input (if it isn't already)
if (document.activeElement !== this.input) {
@ -1818,7 +1822,7 @@ class Choices {
this.containerOuter.focus();
}
}
} else if (this.passedElement.type === 'select-one' && target !== this.input && !this.dropdown.contains(target)) {
} else if (this.isSelectOneElement && target !== this.input && !this.dropdown.contains(target)) {
this.hideDropdown(true);
}
} else {
@ -1893,7 +1897,7 @@ class Choices {
},
'select-multiple': () => {
if (target === this.input) {
// If element is a select box, the focussed element is the container and the dropdown
// If element is a select box, the focused element is the container and the dropdown
// isn't already open, focus and show dropdown
this.containerOuter.classList.add(this.config.classNames.focusState);
@ -1977,7 +1981,7 @@ class Choices {
*/
_regexFilter(value) {
if (!value) {
return;
return false;
}
const regex = this.config.regexFilter;
@ -1987,7 +1991,7 @@ class Choices {
/**
* Scroll to an option element
* @param {HTMLElement} option Option to scroll to
* @param {HTMLElement} choice Option to scroll to
* @param {Number} direction Whether option is above or below
* @return
* @private
@ -2045,11 +2049,11 @@ class Choices {
/**
* Highlight choice
* @param {HTMLElement} el Element to highlight
* @param {HTMLElement} [el] Element to highlight
* @return
* @private
*/
_highlightChoice(el) {
_highlightChoice(el = null) {
// Highlight first element in dropdown
const choices = Array.from(this.dropdown.querySelectorAll('[data-choice-selectable]'));
let passedEl = el;
@ -2091,14 +2095,14 @@ 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
* @param {String} [label] Label to add to store
* @param {Number} [choiceId=-1] ID of the associated choice that was selected
* @param {Number} [groupId=-1] 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, customProperties = null) {
_addItem(value, label = null, choiceId = -1, groupId = -1, customProperties = null) {
let passedValue = isType('String', value) ? value.trim() : value;
const items = this.store.getItems();
const passedLabel = label || passedValue;
@ -2122,7 +2126,7 @@ class Choices {
this.store.dispatch(addItem(passedValue, passedLabel, id, passedOptionId, groupId, customProperties));
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
this.removeActiveItems(id);
}
@ -2148,13 +2152,12 @@ class Choices {
/**
* Remove item from store
* @param {Object} item Item to remove
* @param {Function} callback Callback to trigger
* @return {Object} Class instance
* @public
*/
_removeItem(item) {
if (!item || !isType('Object', item)) {
return;
return this;
}
const id = item.id;
@ -2187,15 +2190,15 @@ class Choices {
/**
* Add choice to dropdown
* @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
* @param {String} [label] Label of choice
* @param {Boolean} [isSelected=false] Whether choice is selected
* @param {Boolean} [isDisabled=false] Whether choice is disabled
* @param {Number} [groupId=-1] ID of group choice is within. Negative number indicates no group
* @param {Object} [customProperties] Object containing user defined properties
* @return
* @private
*/
_addChoice(value, label, isSelected = false, isDisabled = false, groupId = -1, customProperties = null) {
_addChoice(value, label = null, isSelected = false, isDisabled = false, groupId = -1, customProperties = null) {
if (typeof value === 'undefined' || value === null) {
return;
}
@ -2293,7 +2296,7 @@ class Choices {
*/
_getTemplate(template, ...args) {
if (!template) {
return;
return null;
}
const templates = this.config.templates;
return templates[template](...args);
@ -2317,7 +2320,7 @@ class Choices {
''
}
data-type="${this.passedElement.type}"
${this.passedElement.type === 'select-one' ?
${this.isSelectOneElement ?
'tabindex="0"' :
''
}
@ -2337,8 +2340,8 @@ class Choices {
const localClasses = classNames(
globalClasses.list,
{
[globalClasses.listSingle]: (this.passedElement.type === 'select-one'),
[globalClasses.listItems]: (this.passedElement.type !== 'select-one')
[globalClasses.listSingle]: (this.isSelectOneElement),
[globalClasses.listItems]: (!this.isSelectOneElement)
}
);
@ -2425,7 +2428,7 @@ class Choices {
class="${globalClasses.list}"
dir="ltr"
role="listbox"
${this.passedElement.type !== 'select-one' ?
${!this.isSelectOneElement ?
'aria-multiselectable="true"' :
''
}
@ -2581,6 +2584,11 @@ class Choices {
);
this.passedElement.tabIndex = '-1';
// persist original styles if any
const oldStyle = this.passedElement.getAttribute('style');
if (Boolean(oldStyle)) {
this.passedElement.setAttribute('data-choices-js-orig-style', oldStyle);
}
this.passedElement.setAttribute('style', 'display:none;');
this.passedElement.setAttribute('aria-hidden', 'true');
this.passedElement.setAttribute('data-choice', 'active');
@ -2594,7 +2602,7 @@ class Choices {
// If placeholder has been enabled and we have a value
if (placeholder) {
input.placeholder = placeholder;
if (this.passedElement.type !== 'select-one') {
if (!this.isSelectOneElement) {
input.style.width = getWidthOfInput(input);
}
}
@ -2607,11 +2615,11 @@ class Choices {
containerOuter.appendChild(dropdown);
containerInner.appendChild(itemList);
if (this.passedElement.type !== 'text') {
if (!this.isTextElement) {
dropdown.appendChild(choiceList);
}
if (this.passedElement.type === 'select-multiple' || this.passedElement.type === 'text') {
if (this.isSelectMultipleElement || this.isTextElement) {
containerInner.appendChild(input);
} else if (this.canSearch) {
dropdown.insertBefore(input, dropdown.firstChild);
@ -2653,7 +2661,7 @@ class Choices {
// Add each choice
allChoices.forEach((choice, index) => {
// Pre-select first choice if it's a single select
if (this.passedElement.type === 'select-one') {
if (this.isSelectOneElement) {
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