Open dropdown if typing

This commit is contained in:
Josh Johnson 2016-04-29 18:23:06 +01:00
parent 17e00f10fb
commit 2ccac3083d
2 changed files with 115 additions and 108 deletions

File diff suppressed because one or more lines are too long

View file

@ -10,7 +10,6 @@ import Sifter from 'sifter';
* Choices * Choices
* *
* To do: * To do:
* - Dispatch events
* - Remove item by clicking a target * - Remove item by clicking a target
* - Set input width based on the size of the contents * - Set input width based on the size of the contents
* - Single select input support * - Single select input support
@ -20,9 +19,8 @@ export class Choices {
constructor(element = '[data-choice]', userOptions = {}) { constructor(element = '[data-choice]', userOptions = {}) {
// Cutting the mustard // Cutting the mustard
const fakeEl = document.createElement("fakeel"); const cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in document.createElement("div");
const cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in fakeEl; if (!cuttingTheMustard) console.error('init: Your browser doesn\'t support Choices');
if (!cuttingTheMustard) console.error('init: Your browser doesn\'nt support Choices');
// If there are multiple elements, create a new instance // If there are multiple elements, create a new instance
// for each element besides the first one (as that already has an instance) // for each element besides the first one (as that already has an instance)
@ -104,7 +102,8 @@ export class Choices {
this.init = this.init.bind(this); this.init = this.init.bind(this);
this.render = this.render.bind(this); this.render = this.render.bind(this);
this.destroy = this.destroy.bind(this); this.destroy = this.destroy.bind(this);
// Bind event handlers
this.onFocus = this.onFocus.bind(this); this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this); this.onBlur = this.onBlur.bind(this);
this.onKeyUp = this.onKeyUp.bind(this); this.onKeyUp = this.onKeyUp.bind(this);
@ -113,7 +112,6 @@ export class Choices {
this.onPaste = this.onPaste.bind(this); this.onPaste = this.onPaste.bind(this);
const classNames = this.options.classNames; const classNames = this.options.classNames;
this.templates = { this.templates = {
containerOuter: () => { containerOuter: () => {
return strToEl(`<div class="${ classNames.containerOuter }"></div>`); return strToEl(`<div class="${ classNames.containerOuter }"></div>`);
@ -258,9 +256,8 @@ export class Choices {
* @return * @return
*/ */
onKeyDown(e) { onKeyDown(e) {
const activeItems = this.getItemsFilteredByActive(); if(e.target !== this.input) return;
const activeOptions = this.getOptionsFilteredByActive();
const inputIsFocussed = this.input === document.activeElement;
const ctrlDownKey = e.ctrlKey || e.metaKey; const ctrlDownKey = e.ctrlKey || e.metaKey;
const backKey = 46; const backKey = 46;
const deleteKey = 8; const deleteKey = 8;
@ -269,89 +266,95 @@ export class Choices {
const escapeKey = 27; const escapeKey = 27;
const upKey = 38; const upKey = 38;
const downKey = 40; const downKey = 40;
const hasActiveDropDown = this.dropdown && this.dropdown.classList.contains(this.options.classNames.activeState);
const activeItems = this.getItemsFilteredByActive();
const activeOptions = this.getOptionsFilteredByActive();
const hasFocussedInput = this.input === document.activeElement;
const hasActiveDropdown = this.dropdown && this.dropdown.classList.contains(this.options.classNames.activeState);
const hasItems = this.list && this.list.children; const hasItems = this.list && this.list.children;
const keyString = String.fromCharCode(event.keyCode);
// If we are typing in the input // If a user is typing and the dropdown is not active
if(e.target === this.input) { if(/[a-zA-Z0-9-_ ]/.test(keyString) && this.dropdown && !hasActiveDropdown) {
// this.input.style.width = getWidthOfInput(this.input); this.toggleDropdown();
switch (e.keyCode) { }
case aKey:
// If CTRL + A or CMD + A have been pressed and there are items to select switch (e.keyCode) {
if(ctrlDownKey && hasItems) { case aKey:
if(this.options.removeItems && !this.input.value && this.options.selectAll && this.input === document.activeElement) { // If CTRL + A or CMD + A have been pressed and there are items to select
this.selectAll(this.list.children); if(ctrlDownKey && hasItems) {
} if(this.options.removeItems && !this.input.value && this.options.selectAll && this.input === document.activeElement) {
this.selectAll(this.list.children);
} }
break; }
case enterKey: break;
// If enter key is pressed and the input has a value case enterKey:
if(e.target.value && this.passedElement.type === 'text') { // If enter key is pressed and the input has a value
const value = this.input.value; if(e.target.value && this.passedElement.type === 'text') {
this.handleEnter(activeItems, value); const value = this.input.value;
this.handleEnter(activeItems, value);
}
if(this.passedElement.type === 'select-multiple' && hasActiveDropdown) {
const highlighted = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
if(highlighted) {
const value = highlighted.getAttribute('data-choice-value');
const label = highlighted.innerHTML;
const id = highlighted.getAttribute('data-choice-id');
this.addItem(value, label, id);
this.input.value = "";
}
}
break;
case escapeKey:
if(this.passedElement.type === 'select-multiple' && hasActiveDropdown) {
this.toggleDropdown();
}
break;
case downKey:
case upKey:
// If up or down key is pressed, traverse through options
if(this.passedElement.type === 'select-multiple' && hasActiveDropdown) {
const selectableOptions = activeOptions.filter((option) => {
return !option.selected;
});
let canHighlight = true;
if(e.keyCode === downKey) {
this.highlightPosition < (selectableOptions.length - 1) ? this.highlightPosition++ : canHighlight = false;
} else if(e.keyCode === upKey) {
this.highlightPosition > 0 ? this.highlightPosition-- : canHighlight = false;
} }
if(this.passedElement.type === 'select-multiple' && hasActiveDropDown) { if(canHighlight) {
const highlighted = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`); const option = selectableOptions[this.highlightPosition];
if(option) {
if(highlighted) { const previousElement = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
const value = highlighted.getAttribute('data-choice-value'); const currentElement = this.dropdown.querySelector(`[data-choice-id="${option.id}"]`);
const label = highlighted.innerHTML;
const id = highlighted.getAttribute('data-choice-id');
this.addItem(value, label, id);
this.input.value = "";
}
}
break;
case escapeKey:
if(this.passedElement.type === 'select-multiple' && hasActiveDropDown) {
this.toggleDropdown();
}
break;
case downKey:
case upKey:
// If up or down key is pressed, traverse through options
if(this.passedElement.type === 'select-multiple' && hasActiveDropDown) {
const selectableOptions = activeOptions.filter((option) => {
return !option.selected;
});
let canHighlight = true; if(previousElement) {
previousElement.classList.remove(this.options.classNames.highlightedState);
}
if(e.keyCode === downKey) { if(currentElement) {
this.highlightPosition < (selectableOptions.length - 1) ? this.highlightPosition++ : canHighlight = false; currentElement.classList.add(this.options.classNames.highlightedState);
} else if(e.keyCode === upKey) {
this.highlightPosition > 0 ? this.highlightPosition-- : canHighlight = false;
}
if(canHighlight) {
const option = selectableOptions[this.highlightPosition];
if(option) {
const previousElement = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
const currentElement = this.dropdown.querySelector(`[data-choice-id="${option.id}"]`);
if(previousElement) {
previousElement.classList.remove(this.options.classNames.highlightedState);
}
if(currentElement) {
currentElement.classList.add(this.options.classNames.highlightedState);
}
} }
} }
} }
break }
case backKey: break
case deleteKey: case backKey:
// If backspace or delete key is pressed and the input has no value case deleteKey:
if(inputIsFocussed && !e.target.value) { // If backspace or delete key is pressed and the input has no value
this.handleBackspaceKey(activeItems); if(hasFocussedInput && !e.target.value) {
e.preventDefault(); this.handleBackspaceKey(activeItems);
} e.preventDefault();
break; }
default: break;
break; default:
} break;
} }
} }
@ -361,31 +364,35 @@ export class Choices {
* @return * @return
*/ */
onKeyUp(e) { onKeyUp(e) {
if(e.target === this.input) { if(e.target !== this.input) return;
if(this.passedElement.type === 'select-multiple' && this.options.allowSearch) {
const charStr = String.fromCharCode(e.keyCode); if(this.passedElement.type === 'select-multiple' && this.options.allowSearch) {
if(this.input === document.activeElement && /[a-z0-9]/i.test(charStr)) { const options = this.getOptions();
if(this.input.value) { const hasUnactiveOptions = options.some((option) => {
// If we have a value, filter options based on it return option.active !== true;
const handleFilter = debounce(() => { });
const options = this.getOptionsFiltedBySelectable();
const sifter = new Sifter(options); if(this.input === document.activeElement) {
const results = sifter.search(this.input.value, { if(this.input.value) {
fields: ['label', 'value'], // If we have a value, filter options based on it
sort: [{field: 'value', direction: 'asc'}], const handleFilter = debounce(() => {
limit: 10 const options = this.getOptionsFiltedBySelectable();
}); const sifter = new Sifter(options);
this.store.dispatch(filterOptions(results)); const results = sifter.search(this.input.value, {
}, 100) fields: ['label', 'value'],
sort: [{field: 'value', direction: 'asc'}],
handleFilter(); limit: 10
} else { });
// Otherwise reset options to active this.store.dispatch(filterOptions(results));
this.store.dispatch(activateOptions()); }, 100)
}
handleFilter();
} else if(hasUnactiveOptions) {
// Otherwise reset options to active
this.store.dispatch(activateOptions());
} }
} }
} }
} }