mirror of
https://github.com/Choices-js/Choices.git
synced 2024-06-03 14:32:11 +02:00
Early returns + refactoring
This commit is contained in:
parent
30c31a406c
commit
a325a75a23
|
@ -6,7 +6,7 @@ import Input from './components/input';
|
||||||
import List from './components/list';
|
import List from './components/list';
|
||||||
import WrappedInput from './components/wrapped-input';
|
import WrappedInput from './components/wrapped-input';
|
||||||
import WrappedSelect from './components/wrapped-select';
|
import WrappedSelect from './components/wrapped-select';
|
||||||
import { DEFAULT_CONFIG, DEFAULT_CLASSNAMES, EVENTS, KEY_CODES } from './constants';
|
import { DEFAULT_CONFIG, DEFAULT_CLASSNAMES, EVENTS, KEY_CODES, SCROLLING_SPEED } from './constants';
|
||||||
import { TEMPLATES } from './templates';
|
import { TEMPLATES } from './templates';
|
||||||
import { addChoice, filterChoices, activateChoices, clearChoices } from './actions/choices';
|
import { addChoice, filterChoices, activateChoices, clearChoices } from './actions/choices';
|
||||||
import { addItem, removeItem, highlightItem } from './actions/items';
|
import { addItem, removeItem, highlightItem } from './actions/items';
|
||||||
|
@ -388,105 +388,104 @@ class Choices {
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
this.currentState = this.store.getState();
|
this.currentState = this.store.getState();
|
||||||
|
const stateChanged = (
|
||||||
|
this.currentState.choices !== this.prevState.choices ||
|
||||||
|
this.currentState.groups !== this.prevState.groups ||
|
||||||
|
this.currentState.items !== this.prevState.items
|
||||||
|
);
|
||||||
|
|
||||||
// Only render if our state has actually changed
|
if (!stateChanged) {
|
||||||
if (this.currentState !== this.prevState) {
|
return;
|
||||||
const stateChanged = (
|
|
||||||
this.currentState.choices !== this.prevState.choices ||
|
|
||||||
this.currentState.groups !== this.prevState.groups ||
|
|
||||||
this.currentState.items !== this.prevState.items
|
|
||||||
);
|
|
||||||
|
|
||||||
// Choices
|
|
||||||
if (stateChanged && this.isSelectElement) {
|
|
||||||
// Get active groups/choices
|
|
||||||
const activeGroups = this.store.getGroupsFilteredByActive();
|
|
||||||
const activeChoices = this.store.getChoicesFilteredByActive();
|
|
||||||
|
|
||||||
let choiceListFragment = document.createDocumentFragment();
|
|
||||||
|
|
||||||
// Clear choices
|
|
||||||
this.choiceList.clear();
|
|
||||||
|
|
||||||
// Scroll back to top of choices list
|
|
||||||
if (this.config.resetScrollPosition) {
|
|
||||||
this.choiceList.scrollTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have grouped options
|
|
||||||
if (activeGroups.length >= 1 && !this.isSearching) {
|
|
||||||
// If we have a placeholder choice along with groups
|
|
||||||
const activePlaceholders = activeChoices.filter(
|
|
||||||
activeChoice => activeChoice.placeholder === true && activeChoice.groupId === -1,
|
|
||||||
);
|
|
||||||
if (activePlaceholders.length >= 1) {
|
|
||||||
choiceListFragment = this.renderChoices(activePlaceholders, choiceListFragment);
|
|
||||||
}
|
|
||||||
choiceListFragment = this.renderGroups(activeGroups, activeChoices, choiceListFragment);
|
|
||||||
} else if (activeChoices.length >= 1) {
|
|
||||||
choiceListFragment = this.renderChoices(activeChoices, choiceListFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
|
||||||
const canAddItem = this._canAddItem(activeItems, this.input.getValue());
|
|
||||||
|
|
||||||
// If we have choices to show
|
|
||||||
if (choiceListFragment.childNodes && choiceListFragment.childNodes.length > 0) {
|
|
||||||
// ...and we can select them
|
|
||||||
if (canAddItem.response) {
|
|
||||||
// ...append them and highlight the first choice
|
|
||||||
this.choiceList.append(choiceListFragment);
|
|
||||||
this._highlightChoice();
|
|
||||||
} else {
|
|
||||||
// ...otherwise show a notice
|
|
||||||
this.choiceList.append(this._getTemplate('notice', canAddItem.notice));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise show a notice
|
|
||||||
let dropdownItem;
|
|
||||||
let notice;
|
|
||||||
|
|
||||||
if (this.isSearching) {
|
|
||||||
notice = isType('Function', this.config.noResultsText) ?
|
|
||||||
this.config.noResultsText() :
|
|
||||||
this.config.noResultsText;
|
|
||||||
|
|
||||||
dropdownItem = this._getTemplate('notice', notice, 'no-results');
|
|
||||||
} else {
|
|
||||||
notice = isType('Function', this.config.noChoicesText) ?
|
|
||||||
this.config.noChoicesText() :
|
|
||||||
this.config.noChoicesText;
|
|
||||||
|
|
||||||
dropdownItem = this._getTemplate('notice', notice, 'no-choices');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.choiceList.append(dropdownItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items
|
|
||||||
if (stateChanged) {
|
|
||||||
// Get active items (items that can be selected)
|
|
||||||
const activeItems = this.store.getItemsFilteredByActive() || [];
|
|
||||||
|
|
||||||
// Clear list
|
|
||||||
this.itemList.clear();
|
|
||||||
|
|
||||||
if (activeItems.length) {
|
|
||||||
// Create a fragment to store our list items
|
|
||||||
// (so we don't have to update the DOM for each item)
|
|
||||||
const itemListFragment = this.renderItems(activeItems);
|
|
||||||
|
|
||||||
// If we have items to add
|
|
||||||
if (itemListFragment.childNodes) {
|
|
||||||
// Update list
|
|
||||||
this.itemList.append(itemListFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prevState = this.currentState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Choices */
|
||||||
|
|
||||||
|
if (this.isSelectElement) {
|
||||||
|
// Get active groups/choices
|
||||||
|
const activeGroups = this.store.getGroupsFilteredByActive();
|
||||||
|
const activeChoices = this.store.getChoicesFilteredByActive();
|
||||||
|
|
||||||
|
let choiceListFragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
// Clear choices
|
||||||
|
this.choiceList.clear();
|
||||||
|
|
||||||
|
// Scroll back to top of choices list
|
||||||
|
if (this.config.resetScrollPosition) {
|
||||||
|
this.choiceList.scrollTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have grouped options
|
||||||
|
if (activeGroups.length >= 1 && !this.isSearching) {
|
||||||
|
// If we have a placeholder choice along with groups
|
||||||
|
const activePlaceholders = activeChoices.filter(
|
||||||
|
activeChoice => activeChoice.placeholder === true && activeChoice.groupId === -1,
|
||||||
|
);
|
||||||
|
if (activePlaceholders.length >= 1) {
|
||||||
|
choiceListFragment = this.renderChoices(activePlaceholders, choiceListFragment);
|
||||||
|
}
|
||||||
|
choiceListFragment = this.renderGroups(activeGroups, activeChoices, choiceListFragment);
|
||||||
|
} else if (activeChoices.length >= 1) {
|
||||||
|
choiceListFragment = this.renderChoices(activeChoices, choiceListFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
|
const canAddItem = this._canAddItem(activeItems, this.input.getValue());
|
||||||
|
|
||||||
|
// If we have choices to show
|
||||||
|
if (choiceListFragment.childNodes && choiceListFragment.childNodes.length > 0) {
|
||||||
|
// ...and we can select them
|
||||||
|
if (canAddItem.response) {
|
||||||
|
// ...append them and highlight the first choice
|
||||||
|
this.choiceList.append(choiceListFragment);
|
||||||
|
this._highlightChoice();
|
||||||
|
} else {
|
||||||
|
// ...otherwise show a notice
|
||||||
|
this.choiceList.append(this._getTemplate('notice', canAddItem.notice));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise show a notice
|
||||||
|
let dropdownItem;
|
||||||
|
let notice;
|
||||||
|
|
||||||
|
if (this.isSearching) {
|
||||||
|
notice = isType('Function', this.config.noResultsText) ?
|
||||||
|
this.config.noResultsText() :
|
||||||
|
this.config.noResultsText;
|
||||||
|
|
||||||
|
dropdownItem = this._getTemplate('notice', notice, 'no-results');
|
||||||
|
} else {
|
||||||
|
notice = isType('Function', this.config.noChoicesText) ?
|
||||||
|
this.config.noChoicesText() :
|
||||||
|
this.config.noChoicesText;
|
||||||
|
|
||||||
|
dropdownItem = this._getTemplate('notice', notice, 'no-choices');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.choiceList.append(dropdownItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Items */
|
||||||
|
|
||||||
|
// Get active items (items that can be selected)
|
||||||
|
const activeItems = this.store.getItemsFilteredByActive() || [];
|
||||||
|
// Clear list
|
||||||
|
this.itemList.clear();
|
||||||
|
|
||||||
|
if (activeItems.length) {
|
||||||
|
// Create a fragment to store our list items
|
||||||
|
// (so we don't have to update the DOM for each item)
|
||||||
|
const itemListFragment = this.renderItems(activeItems);
|
||||||
|
|
||||||
|
// If we have items to add
|
||||||
|
if (itemListFragment.childNodes) {
|
||||||
|
// Update list
|
||||||
|
this.itemList.append(itemListFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevState = this.currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -550,10 +549,7 @@ class Choices {
|
||||||
eventResponse.groupValue = group.value;
|
eventResponse.groupValue = group.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store.dispatch(
|
this.store.dispatch(highlightItem(id, false));
|
||||||
highlightItem(id, false),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.passedElement.triggerEvent(EVENTS.highlightItem, eventResponse);
|
this.passedElement.triggerEvent(EVENTS.highlightItem, eventResponse);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -1141,6 +1137,7 @@ class Choices {
|
||||||
this._triggerChange(lastItem.value);
|
this._triggerChange(lastItem.value);
|
||||||
} else {
|
} else {
|
||||||
if (!hasHighlightedItems) {
|
if (!hasHighlightedItems) {
|
||||||
|
// Highlight last item if none already highlighted
|
||||||
this.highlightItem(lastItem, false);
|
this.highlightItem(lastItem, false);
|
||||||
}
|
}
|
||||||
this.removeHighlightedItems(true);
|
this.removeHighlightedItems(true);
|
||||||
|
@ -1297,26 +1294,26 @@ class Choices {
|
||||||
this.currentValue.trim() :
|
this.currentValue.trim() :
|
||||||
this.currentValue;
|
this.currentValue;
|
||||||
|
|
||||||
// If new value matches the desired length and is not the same as the current value with a space
|
if (newValue.length < 1 && newValue === `${currentValue} `) {
|
||||||
if (newValue.length >= 1 && newValue !== `${currentValue} `) {
|
return 0;
|
||||||
const haystack = this.store.getSearchableChoices();
|
|
||||||
const needle = newValue;
|
|
||||||
const keys = isType('Array', this.config.searchFields) ?
|
|
||||||
this.config.searchFields :
|
|
||||||
[this.config.searchFields];
|
|
||||||
const options = Object.assign(this.config.fuseOptions, { keys });
|
|
||||||
const fuse = new Fuse(haystack, options);
|
|
||||||
const results = fuse.search(needle);
|
|
||||||
|
|
||||||
this.currentValue = newValue;
|
|
||||||
this.highlightPosition = 0;
|
|
||||||
this.isSearching = true;
|
|
||||||
this.store.dispatch(filterChoices(results));
|
|
||||||
|
|
||||||
return results.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
// If new value matches the desired length and is not the same as the current value with a space
|
||||||
|
const haystack = this.store.getSearchableChoices();
|
||||||
|
const needle = newValue;
|
||||||
|
const keys = isType('Array', this.config.searchFields) ?
|
||||||
|
this.config.searchFields :
|
||||||
|
[this.config.searchFields];
|
||||||
|
const options = Object.assign(this.config.fuseOptions, { keys });
|
||||||
|
const fuse = new Fuse(haystack, options);
|
||||||
|
const results = fuse.search(needle);
|
||||||
|
|
||||||
|
this.currentValue = newValue;
|
||||||
|
this.highlightPosition = 0;
|
||||||
|
this.isSearching = true;
|
||||||
|
this.store.dispatch(filterChoices(results));
|
||||||
|
|
||||||
|
return results.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1599,17 +1596,15 @@ class Choices {
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const backKey = 46;
|
const backKey = KEY_CODES.BACK_KEY;
|
||||||
const deleteKey = 8;
|
const deleteKey = KEY_CODES.DELETE_KEY;
|
||||||
|
|
||||||
// If user has removed value...
|
// If user has removed value...
|
||||||
if ((e.keyCode === backKey || e.keyCode === deleteKey) && !e.target.value) {
|
if ((e.keyCode === backKey || e.keyCode === deleteKey) && !e.target.value) {
|
||||||
// ...and it is a multiple select input, activate choices (if searching)
|
// ...and it is a multiple select input, activate choices (if searching)
|
||||||
if (!this.isTextElement && this.isSearching) {
|
if (!this.isTextElement && this.isSearching) {
|
||||||
this.isSearching = false;
|
this.isSearching = false;
|
||||||
this.store.dispatch(
|
this.store.dispatch(activateChoices(true));
|
||||||
activateChoices(true),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (this.canSearch && canAddItem.response) {
|
} else if (this.canSearch && canAddItem.response) {
|
||||||
this._handleSearch(this.input.getValue());
|
this._handleSearch(this.input.getValue());
|
||||||
|
@ -1769,33 +1764,34 @@ class Choices {
|
||||||
*/
|
*/
|
||||||
_onFocus(e) {
|
_onFocus(e) {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
// If target is something that concerns us
|
if (!this.containerOuter.element.contains(target)) {
|
||||||
if (this.containerOuter.element.contains(target)) {
|
return;
|
||||||
const focusActions = {
|
|
||||||
text: () => {
|
|
||||||
if (target === this.input.element) {
|
|
||||||
this.containerOuter.addFocusState();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'select-one': () => {
|
|
||||||
this.containerOuter.addFocusState();
|
|
||||||
if (target === this.input.element) {
|
|
||||||
// Show dropdown if it isn't already showing
|
|
||||||
this.showDropdown();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'select-multiple': () => {
|
|
||||||
if (target === this.input.element) {
|
|
||||||
// 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.addFocusState();
|
|
||||||
this.showDropdown(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
focusActions[this.passedElement.element.type]();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const focusActions = {
|
||||||
|
text: () => {
|
||||||
|
if (target === this.input.element) {
|
||||||
|
this.containerOuter.addFocusState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'select-one': () => {
|
||||||
|
this.containerOuter.addFocusState();
|
||||||
|
if (target === this.input.element) {
|
||||||
|
// Show dropdown if it isn't already showing
|
||||||
|
this.showDropdown();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'select-multiple': () => {
|
||||||
|
if (target === this.input.element) {
|
||||||
|
// 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.addFocusState();
|
||||||
|
this.showDropdown(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
focusActions[this.passedElement.element.type]();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1882,7 +1878,7 @@ class Choices {
|
||||||
choice.offsetTop;
|
choice.offsetTop;
|
||||||
|
|
||||||
const animateScroll = () => {
|
const animateScroll = () => {
|
||||||
const strength = 4;
|
const strength = SCROLLING_SPEED;
|
||||||
const choiceListScrollTop = this.choiceList.scrollPos;
|
const choiceListScrollTop = this.choiceList.scrollPos;
|
||||||
let continueAnimation = false;
|
let continueAnimation = false;
|
||||||
let easing;
|
let easing;
|
||||||
|
@ -2068,9 +2064,7 @@ class Choices {
|
||||||
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;
|
||||||
|
|
||||||
this.store.dispatch(
|
this.store.dispatch(removeItem(id, choiceId));
|
||||||
removeItem(id, choiceId),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (group && group.value) {
|
if (group && group.value) {
|
||||||
this.passedElement.triggerEvent(EVENTS.removeItem, {
|
this.passedElement.triggerEvent(EVENTS.removeItem, {
|
||||||
|
@ -2272,7 +2266,6 @@ class Choices {
|
||||||
|
|
||||||
// Wrap input in container preserving DOM ordering
|
// Wrap input in container preserving DOM ordering
|
||||||
wrap(this.passedElement.element, this.containerInner.element);
|
wrap(this.passedElement.element, this.containerInner.element);
|
||||||
|
|
||||||
// Wrapper inner container with outer container
|
// Wrapper inner container with outer container
|
||||||
wrap(this.containerInner.element, this.containerOuter.element);
|
wrap(this.containerInner.element, this.containerOuter.element);
|
||||||
|
|
||||||
|
@ -2295,7 +2288,7 @@ class Choices {
|
||||||
dropdown.appendChild(choiceList);
|
dropdown.appendChild(choiceList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isSelectMultipleElement || this.isTextElement) {
|
if (!this.isSelectOneElement) {
|
||||||
this.containerInner.element.appendChild(this.input.element);
|
this.containerInner.element.appendChild(this.input.element);
|
||||||
} else if (this.canSearch) {
|
} else if (this.canSearch) {
|
||||||
dropdown.insertBefore(input, dropdown.firstChild);
|
dropdown.insertBefore(input, dropdown.firstChild);
|
||||||
|
|
|
@ -103,3 +103,5 @@ export const KEY_CODES = {
|
||||||
PAGE_UP_KEY: 33,
|
PAGE_UP_KEY: 33,
|
||||||
PAGE_DOWN_KEY: 34,
|
PAGE_DOWN_KEY: 34,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SCROLLING_SPEED = 4;
|
||||||
|
|
Loading…
Reference in a new issue