Use document fragments for generating lists

This commit is contained in:
Josh Johnson 2016-04-13 14:40:41 +01:00
parent 94475e8e1a
commit 3d2e998f34
2 changed files with 64 additions and 20 deletions

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,17 @@ import rootReducer from './reducers/index.js';
import { addItem, removeItem, selectItem, addOption, selectOption } from './actions/index'; import { addItem, removeItem, selectItem, addOption, selectOption } from './actions/index';
import { hasClass, wrap, getSiblings, isType, strToEl, extend } from './lib/utils.js'; import { hasClass, wrap, getSiblings, isType, strToEl, extend } from './lib/utils.js';
/**
* Choices
*
* To do:
* - Remove item by clicking a target
* - Set input width based on the size of the contents
* - Map options to items
* - Single select input support
* - Populate options by function
*/
export class Choices { export class Choices {
constructor(element = '[data-choice]', options) { constructor(element = '[data-choice]', options) {
const fakeEl = document.createElement("fakeel"); const fakeEl = document.createElement("fakeel");
@ -314,7 +325,6 @@ export class Choices {
} }
const items = this.getItems(); const items = this.getItems();
const itemObject = items.find((item) => { const itemObject = items.find((item) => {
return item.id === parseInt(id); return item.id === parseInt(id);
}); });
@ -473,11 +483,21 @@ export class Choices {
this.store.dispatch(addOption(value, id)); this.store.dispatch(addOption(value, id));
} }
/* Getters */
/**
* Get items in state
* @return {Array} Array of item objects
*/
getItems() { getItems() {
const state = this.store.getState(); const state = this.store.getState();
return state.items; return state.items;
} }
/**
* Get items in state if they are active
* @return {Array} Array of item objects
*/
getItemsFilteredByActive() { getItemsFilteredByActive() {
const items = this.getItems(); const items = this.getItems();
@ -488,7 +508,11 @@ export class Choices {
return valueArray; return valueArray;
} }
getItemsFilteredByValue() { /**
* Get items in state reduced to just their values
* @return {Array} Array of items
*/
getItemsReducedToValues() {
const items = this.getItems(); const items = this.getItems();
const valueArray = items.reduce((prev, current) => { const valueArray = items.reduce((prev, current) => {
@ -499,6 +523,10 @@ export class Choices {
return valueArray; return valueArray;
} }
/**
* Get options in state
* @return {Array} Array of item options
*/
getOptions() { getOptions() {
const state = this.store.getState(); const state = this.store.getState();
return state.options; return state.options;
@ -510,7 +538,7 @@ export class Choices {
* Create DOM structure around passed text element * Create DOM structure around passed text element
* @return * @return
*/ */
renderTextInput() { generateTextInput() {
/* /*
Template: Template:
@ -578,7 +606,7 @@ export class Choices {
* Create DOM structure around passed select element * Create DOM structure around passed select element
* @return * @return
*/ */
renderMultipleSelectInput() { generateMultipleSelectInput() {
const containerOuter = strToEl(`<div class="${ this.options.classNames.containerOuter }"></div>`); const containerOuter = strToEl(`<div class="${ this.options.classNames.containerOuter }"></div>`);
const containerInner = strToEl(`<div class="${ this.options.classNames.containerInner }"></div>`); const containerInner = strToEl(`<div class="${ this.options.classNames.containerInner }"></div>`);
@ -637,11 +665,17 @@ export class Choices {
this.addEventListeners(); this.addEventListeners();
} }
/**
* Trigger event listeners
*/
addEventListeners() { addEventListeners() {
document.addEventListener('keydown', this.onKeyDown); document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('click', this.onClick); document.addEventListener('click', this.onClick);
} }
/**
* Destroy event listeners
*/
removeEventListeners() { removeEventListeners() {
document.removeEventListener('keydown', this.onKeyDown); document.removeEventListener('keydown', this.onKeyDown);
document.removeEventListener('click', this.onClick); document.removeEventListener('click', this.onClick);
@ -661,27 +695,34 @@ export class Choices {
// Clear options // Clear options
this.dropdown.innerHTML = ''; this.dropdown.innerHTML = '';
const optionListFragment = document.createDocumentFragment();
// Add each option to dropdown // Add each option to dropdown
if(options) { if(options) {
options.forEach((option) => { options.forEach((option) => {
const dropdownItem = strToEl(`<li class="${ classNames.item } ${ classNames.itemOption } ${ option.selected ? classNames.selectedState + ' ' + classNames.itemDisabled : classNames.itemSelectable }" data-choice-selectable data-choice-id="${ option.id }" data-choice-value="${ option.value }">${ option.value }</li>`); const dropdownItem = strToEl(`<li class="${ classNames.item } ${ classNames.itemOption } ${ option.selected ? classNames.selectedState + ' ' + classNames.itemDisabled : classNames.itemSelectable }" data-choice-selectable data-choice-id="${ option.id }" data-choice-value="${ option.value }">${ option.value }</li>`);
this.dropdown.appendChild(dropdownItem); optionListFragment.appendChild(dropdownItem);
}); });
} else { } else {
const dropdownItem = strToEl(`<li class="${ classNames.item }">No options to select</li>`); const dropdownItem = strToEl(`<li class="${ classNames.item }">No options to select</li>`);
this.dropdown.appendChild(dropdownItem); optionListFragment.appendChild(dropdownItem);
} }
this.dropdown.appendChild(optionListFragment);
} }
// ITEMS // ITEMS
// Simplify store data to just values // Simplify store data to just values
const itemsFiltered = this.getItemsFilteredByValue(); const itemsFiltered = this.getItemsReducedToValues();
// Assign hidden input array of values // Assign hidden input array of values
this.passedElement.value = itemsFiltered.join(this.options.delimiter); this.passedElement.value = itemsFiltered.join(this.options.delimiter);
// Clear list // Clear list
this.list.innerHTML = ''; this.list.innerHTML = '';
// Create a fragment to store our list items (so we don't have to update the DOM for each item)
const itemListFragment = document.createDocumentFragment();
// Add each list item to list // Add each list item to list
items.forEach((item) => { items.forEach((item) => {
@ -690,10 +731,12 @@ export class Choices {
const listItem = strToEl(`<li class="${ classNames.item } ${ this.options.removeItems ? classNames.itemSelectable : '' } ${ item.selected ? classNames.selectedState : '' }" data-choice-item data-choice-id="${ item.id }" data-choice-selected="${ item.selected }">${ item.value }</li>`); const listItem = strToEl(`<li class="${ classNames.item } ${ this.options.removeItems ? classNames.itemSelectable : '' } ${ item.selected ? classNames.selectedState : '' }" data-choice-item data-choice-id="${ item.id }" data-choice-selected="${ item.selected }">${ item.value }</li>`);
// Append it to list // Append it to list
this.list.appendChild(listItem); itemListFragment.appendChild(listItem);
} }
}); });
this.list.appendChild(itemListFragment);
// Run callback if it is a function // Run callback if it is a function
if(callback){ if(callback){
if(isType('Function', callback)) { if(isType('Function', callback)) {
@ -713,16 +756,16 @@ export class Choices {
switch (input.type) { switch (input.type) {
case "text": case "text":
this.renderTextInput(); this.generateTextInput();
break; break;
case "select-one": case "select-one":
// this.renderSelectInput(); // this.generateSelectInput();
break; break;
case "select-multiple": case "select-multiple":
this.renderMultipleSelectInput(); this.generateMultipleSelectInput();
break; break;
default: default:
this.renderTextInput(); this.generateMultipleSelectInput();
break; break;
} }
} }
@ -757,16 +800,17 @@ export class Choices {
}; };
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const choices1 = new Choices('#choices-1', { const firstElement = document.getElementById('choices-1');
const choices1 = new Choices(firstElement, {
delimiter: ' ', delimiter: ' ',
editItems: true, editItems: true,
maxItems: 5, maxItems: 5,
// callbackOnRemoveItem: function(value) { callbackOnRemoveItem: function(value) {
// console.log(value); console.log(value);
// }, },
// callbackOnAddItem: function(item, value) { callbackOnAddItem: function(id, value) {
// console.log(item, value); console.log(id, value);
// }, },
callbackOnRender: function(items) { callbackOnRender: function(items) {
console.log(items); console.log(items);
} }