Early returns + refactoring

This commit is contained in:
Josh Johnson 2017-10-19 12:35:26 +01:00
parent 30c31a406c
commit a325a75a23
2 changed files with 152 additions and 157 deletions

View file

@ -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);

View file

@ -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;