Placeholder work

This commit is contained in:
Josh Johnson 2017-08-02 14:05:26 +01:00
parent bc9adcf4ff
commit 94e3c97b4e
7 changed files with 141 additions and 40 deletions

View file

@ -1,4 +1,4 @@
export const addItem = (value, label, id, choiceId, groupId, customProperties, keyCode) => { export const addItem = (value, label, id, choiceId, groupId, customProperties, placeholder, keyCode) => {
return { return {
type: 'ADD_ITEM', type: 'ADD_ITEM',
value, value,
@ -7,7 +7,8 @@ export const addItem = (value, label, id, choiceId, groupId, customProperties, k
choiceId, choiceId,
groupId, groupId,
customProperties, customProperties,
keyCode placeholder,
keyCode,
}; };
}; };
@ -27,7 +28,7 @@ export const highlightItem = (id, highlighted) => {
}; };
}; };
export const addChoice = (value, label, id, groupId, disabled, elementId, customProperties, keyCode) => { export const addChoice = (value, label, id, groupId, disabled, elementId, customProperties, placeholder, keyCode) => {
return { return {
type: 'ADD_CHOICE', type: 'ADD_CHOICE',
value, value,
@ -37,6 +38,7 @@ export const addChoice = (value, label, id, groupId, disabled, elementId, custom
disabled, disabled,
elementId, elementId,
customProperties, customProperties,
placeholder,
keyCode keyCode
}; };
}; };

View file

@ -75,6 +75,7 @@ class Choices {
sortFilter: sortByAlpha, sortFilter: sortByAlpha,
placeholder: true, placeholder: true,
placeholderValue: null, placeholderValue: null,
searchPlaceholderValue: null,
prependValue: null, prependValue: null,
appendValue: null, appendValue: null,
renderSelectedChoices: 'auto', renderSelectedChoices: 'auto',
@ -174,6 +175,11 @@ class Choices {
this.highlightPosition = 0; this.highlightPosition = 0;
this.canSearch = this.config.searchEnabled; this.canSearch = this.config.searchEnabled;
this.placeholder = false;
if (!this.isSelectOneElement) {
this.placeholder = (this.config.placeholderValue || this.passedElement.getAttribute('placeholder')) || false;
}
// Assign preset choices from passed object // Assign preset choices from passed object
this.presetChoices = this.config.choices; this.presetChoices = this.config.choices;
@ -376,22 +382,35 @@ class Choices {
rendererableChoices = choices.filter(choice => !choice.selected); rendererableChoices = choices.filter(choice => !choice.selected);
} }
// Split array into placeholders and "normal" choices
const { placeholderChoices, normalChoices } = rendererableChoices.reduce((acc, choice) => {
if (choice.placeholder) {
acc.placeholderChoices.push(choice);
} else {
acc.normalChoices.push(choice);
}
return acc;
}, { placeholderChoices: [], normalChoices: [] });
// If sorting is enabled or the user is searching, filter choices // If sorting is enabled or the user is searching, filter choices
if (this.config.shouldSort || this.isSearching) { if (this.config.shouldSort || this.isSearching) {
rendererableChoices.sort(filter); normalChoices.sort(filter);
} }
let choiceLimit = rendererableChoices.length; let choiceLimit = rendererableChoices.length;
// Prepend placeholeder
const sortedChoices = [...placeholderChoices, ...normalChoices];
if (this.isSearching) { if (this.isSearching) {
choiceLimit = Math.min(searchResultLimit, rendererableChoices.length - 1); choiceLimit = Math.min(searchResultLimit, sortedChoices.length - 1);
} else if (renderChoiceLimit > 0 && !withinGroup) { } else if (renderChoiceLimit > 0 && !withinGroup) {
choiceLimit = Math.min(renderChoiceLimit, rendererableChoices.length - 1); choiceLimit = Math.min(renderChoiceLimit, sortedChoices.length - 1);
} }
// Add each choice to dropdown within range // Add each choice to dropdown within range
for (let i = 0; i < choiceLimit; i++) { for (let i = 0; i < choiceLimit; i++) {
appendChoice(rendererableChoices[i]); appendChoice(sortedChoices[i]);
}; };
return choicesFragment; return choicesFragment;
@ -525,15 +544,17 @@ class Choices {
// Items // Items
if (this.currentState.items !== this.prevState.items) { if (this.currentState.items !== this.prevState.items) {
// Get active items (items that can be selected)
const activeItems = this.store.getItemsFilteredByActive(); const activeItems = this.store.getItemsFilteredByActive();
if (activeItems) {
// Clear list
this.itemList.innerHTML = '';
if (activeItems && activeItems) {
// Create a fragment to store our list items // Create a fragment to store our list items
// (so we don't have to update the DOM for each item) // (so we don't have to update the DOM for each item)
const itemListFragment = this.renderItems(activeItems); const itemListFragment = this.renderItems(activeItems);
// Clear list
this.itemList.innerHTML = '';
// If we have items to add // If we have items to add
if (itemListFragment.childNodes) { if (itemListFragment.childNodes) {
// Update list // Update list
@ -858,7 +879,7 @@ class Choices {
false, false,
-1, -1,
item.customProperties, item.customProperties,
null item.placeholder
); );
} else { } else {
this._addItem( this._addItem(
@ -867,7 +888,7 @@ class Choices {
item.id, item.id,
undefined, undefined,
item.customProperties, item.customProperties,
null item.placeholder
); );
} }
} else if (itemType === 'String') { } else if (itemType === 'String') {
@ -924,6 +945,7 @@ class Choices {
foundChoice.id, foundChoice.id,
foundChoice.groupId, foundChoice.groupId,
foundChoice.customProperties, foundChoice.customProperties,
foundChoice.placeholder,
foundChoice.keyCode foundChoice.keyCode
); );
} else if (!this.config.silent) { } else if (!this.config.silent) {
@ -974,8 +996,8 @@ class Choices {
result.selected, result.selected,
result.disabled, result.disabled,
undefined, undefined,
result['customProperties'], result.customProperties,
null result.placeholder
); );
} }
}); });
@ -1124,7 +1146,8 @@ class Choices {
this._triggerChange(itemToRemove.value); this._triggerChange(itemToRemove.value);
if (this.isSelectOneElement) { if (this.isSelectOneElement) {
const placeholder = this.config.placeholder ? this.config.placeholderValue || this.passedElement.getAttribute('placeholder') : const placeholder = this.config.placeholder ?
(this.config.placeholderValue || this.passedElement.getAttribute('placeholder')) :
false; false;
if (placeholder) { if (placeholder) {
const placeholderItem = this._getTemplate('placeholder', placeholder); const placeholderItem = this._getTemplate('placeholder', placeholder);
@ -1206,6 +1229,7 @@ class Choices {
choice.id, choice.id,
choice.groupId, choice.groupId,
choice.customProperties, choice.customProperties,
choice.placeholder,
choice.keyCode choice.keyCode
); );
this._triggerChange(choice.value); this._triggerChange(choice.value);
@ -1379,10 +1403,26 @@ class Choices {
result.selected, result.selected,
result.disabled, result.disabled,
undefined, undefined,
result['customProperties'], result.customProperties,
null result.placeholder
); );
} }
if (this.passedElement.type === 'select-one') {
const placeholderChoice = this.store.getPlaceholderChoice();
if (placeholderChoice) {
this._addItem(
placeholderChoice.value,
placeholderChoice.label,
placeholderChoice.id,
placeholderChoice.groupId,
null,
placeholderChoice.placeholder
);
this._triggerChange(placeholderChoice.value);
}
}
}); });
} else { } else {
// No results, remove loading state // No results, remove loading state
@ -1405,7 +1445,7 @@ class Choices {
// If new value matches the desired length and is not the same as the current value with a space // 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} `) {
const haystack = this.store.getChoicesFilteredBySelectable(); const haystack = this.store.getSearchableChoices();
const needle = newValue; const needle = newValue;
const keys = isType('Array', this.config.searchFields) ? this.config.searchFields : [this.config.searchFields]; const keys = isType('Array', this.config.searchFields) ? this.config.searchFields : [this.config.searchFields];
const options = Object.assign(this.config.fuseOptions, { keys }); const options = Object.assign(this.config.fuseOptions, { keys });
@ -2200,7 +2240,7 @@ class Choices {
* @return {Object} Class instance * @return {Object} Class instance
* @public * @public
*/ */
_addItem(value, label = null, choiceId = -1, groupId = -1, customProperties = null, keyCode = null) { _addItem(value, label = null, choiceId = -1, groupId = -1, customProperties = null, placeholder = false, keyCode = null) {
let passedValue = isType('String', value) ? value.trim() : value; let passedValue = isType('String', value) ? value.trim() : value;
let passedKeyCode = keyCode; let passedKeyCode = keyCode;
const items = this.store.getItems(); const items = this.store.getItems();
@ -2231,6 +2271,7 @@ class Choices {
passedOptionId, passedOptionId,
groupId, groupId,
customProperties, customProperties,
placeholder,
passedKeyCode passedKeyCode
) )
); );
@ -2311,7 +2352,7 @@ class Choices {
* @return * @return
* @private * @private
*/ */
_addChoice(value, label = null, isSelected = false, isDisabled = false, groupId = -1, customProperties = null, keyCode = null) { _addChoice(value, label = null, isSelected = false, isDisabled = false, groupId = -1, customProperties = null, placeholder = false, keyCode = null) {
if (typeof value === 'undefined' || value === null) { if (typeof value === 'undefined' || value === null) {
return; return;
} }
@ -2331,6 +2372,7 @@ class Choices {
isDisabled, isDisabled,
choiceElementId, choiceElementId,
customProperties, customProperties,
placeholder,
keyCode keyCode
) )
); );
@ -2342,6 +2384,7 @@ class Choices {
choiceId, choiceId,
undefined, undefined,
customProperties, customProperties,
placeholder,
keyCode keyCode
); );
} }
@ -2395,7 +2438,8 @@ class Choices {
option.selected, option.selected,
isOptDisabled, isOptDisabled,
groupId, groupId,
option.customProperties option.customProperties,
option.placeholder
); );
}); });
} else { } else {
@ -2484,7 +2528,8 @@ class Choices {
globalClasses.item, globalClasses.item,
{ {
[globalClasses.highlightedState]: data.highlighted, [globalClasses.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.highlighted [globalClasses.itemSelectable]: !data.highlighted,
[globalClasses.placeholder]: data.placeholder
} }
); );
@ -2493,7 +2538,8 @@ class Choices {
globalClasses.item, globalClasses.item,
{ {
[globalClasses.highlightedState]: data.highlighted, [globalClasses.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.disabled [globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder
} }
); );
@ -2589,7 +2635,8 @@ class Choices {
globalClasses.itemChoice, globalClasses.itemChoice,
{ {
[globalClasses.itemDisabled]: data.disabled, [globalClasses.itemDisabled]: data.disabled,
[globalClasses.itemSelectable]: !data.disabled [globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder
} }
); );
@ -2731,6 +2778,14 @@ class Choices {
input.placeholder = placeholder; input.placeholder = placeholder;
if (!this.isSelectOneElement) { if (!this.isSelectOneElement) {
input.style.width = getWidthOfInput(input); input.style.width = getWidthOfInput(input);
} else {
// If select one element with a search placeholder value
if (this.config.searchPlaceholderValue) {
input.placeholder = this.config.searchPlaceholderValue;
} else {
const placeholderItem = this._getTemplate('placeholder', this.placeholder);
this.itemList.appendChild(placeholderItem);
}
} }
} }
@ -2774,6 +2829,7 @@ class Choices {
label: o.innerHTML, label: o.innerHTML,
selected: o.selected, selected: o.selected,
disabled: o.disabled || o.parentNode.disabled, disabled: o.disabled || o.parentNode.disabled,
placeholder: o.hasAttribute('placeholder')
}); });
}); });
@ -2798,7 +2854,8 @@ class Choices {
choice.selected, choice.selected,
choice.disabled, choice.disabled,
undefined, undefined,
choice.customProperties choice.customProperties,
choice.placeholder
); );
} else { } else {
// Otherwise pre-select the first choice in the array // Otherwise pre-select the first choice in the array
@ -2808,7 +2865,8 @@ class Choices {
true, true,
false, false,
undefined, undefined,
choice.customProperties choice.customProperties,
choice.placeholder
); );
} }
} else { } else {
@ -2818,7 +2876,8 @@ class Choices {
choice.selected, choice.selected,
choice.disabled, choice.disabled,
undefined, undefined,
choice.customProperties choice.customProperties,
choice.placeholder
); );
} }
}); });
@ -2836,7 +2895,8 @@ class Choices {
item.label, item.label,
item.id, item.id,
undefined, undefined,
item.customProperties item.customProperties,
item.placeholder
); );
} else if (itemType === 'String') { } else if (itemType === 'String') {
this._addItem(item); this._addItem(item);

View file

@ -132,13 +132,13 @@ export const extend = function() {
*/ */
export const whichTransitionEvent = function() { export const whichTransitionEvent = function() {
var t, var t,
el = document.createElement("fakeelement"); el = document.createElement('fakeelement');
var transitions = { var transitions = {
"transition": "transitionend", 'transition': 'transitionend',
"OTransition": "oTransitionEnd", 'OTransition': 'oTransitionEnd',
"MozTransition": "transitionend", 'MozTransition': 'transitionend',
"WebkitTransition": "webkitTransitionEnd" 'WebkitTransition': 'webkitTransitionEnd'
} }
for (t in transitions) { for (t in transitions) {
@ -509,6 +509,20 @@ export const getWidthOfInput = (input) => {
testEl.style.width = 'auto'; testEl.style.width = 'auto';
testEl.style.whiteSpace = 'pre'; testEl.style.whiteSpace = 'pre';
if (document.body.contains(input) && window.getComputedStyle) {
const inputStyle = window.getComputedStyle(input);
if (inputStyle) {
testEl.style.fontSize = inputStyle.fontSize;
testEl.style.fontFamily = inputStyle.fontFamily;
testEl.style.fontWeight = inputStyle.fontWeight;
testEl.style.fontStyle = inputStyle.fontStyle;
testEl.style.letterSpacing = inputStyle.letterSpacing;
testEl.style.textTransform = inputStyle.textTransform;
testEl.style.padding = inputStyle.padding;
}
}
document.body.appendChild(testEl); document.body.appendChild(testEl);
if (value && testEl.offsetWidth !== input.offsetWidth) { if (value && testEl.offsetWidth !== input.offsetWidth) {

View file

@ -17,6 +17,7 @@ const choices = (state = [], action) => {
active: true, active: true,
score: 9999, score: 9999,
customProperties: action.customProperties, customProperties: action.customProperties,
placeholder: (action.placeholder || false),
keyCode: null keyCode: null
}]; }];
} }

View file

@ -11,6 +11,7 @@ const items = (state = [], action) => {
active: true, active: true,
highlighted: false, highlighted: false,
customProperties: action.customProperties, customProperties: action.customProperties,
placeholder: (action.placeholder || false),
keyCode: null keyCode: null
}]; }];

View file

@ -85,9 +85,7 @@ export default class Store {
*/ */
getChoicesFilteredByActive() { getChoicesFilteredByActive() {
const choices = this.getChoices(); const choices = this.getChoices();
const values = choices.filter((choice) => { const values = choices.filter(choice => choice.active === true);
return choice.active === true;
}, []);
return values; return values;
} }
@ -98,13 +96,20 @@ export default class Store {
*/ */
getChoicesFilteredBySelectable() { getChoicesFilteredBySelectable() {
const choices = this.getChoices(); const choices = this.getChoices();
const values = choices.filter((choice) => { const values = choices.filter(choice => choice.disabled !== true);
return choice.disabled !== true;
}, []);
return values; return values;
} }
/**
* Get choices that can be searched (excluding placeholders)
* @return {Array} Option objects
*/
getSearchableChoices() {
const filtered = this.getChoicesFilteredBySelectable();
return filtered.filter(choice => choice.placeholder !== true);
}
/** /**
* Get single choice by it's ID * Get single choice by it's ID
* @return {Object} Found choice * @return {Object} Found choice
@ -159,6 +164,19 @@ export default class Store {
return foundGroup; return foundGroup;
} }
/**
* Get placeholder choice from store
* @return {Object} Found placeholder
*/
getPlaceholderChoice() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.placeholder === true;
}, []);
return values[0];
}
} }
module.exports = Store; module.exports = Store;

View file

@ -69,6 +69,7 @@ describe('Choices', () => {
expect(this.choices.config.shouldSortItems).toEqual(jasmine.any(Boolean)); expect(this.choices.config.shouldSortItems).toEqual(jasmine.any(Boolean));
expect(this.choices.config.placeholder).toEqual(jasmine.any(Boolean)); expect(this.choices.config.placeholder).toEqual(jasmine.any(Boolean));
expect(this.choices.config.placeholderValue).toEqual(null); expect(this.choices.config.placeholderValue).toEqual(null);
expect(this.choices.config.searchPlaceholderValue).toEqual(null);
expect(this.choices.config.prependValue).toEqual(null); expect(this.choices.config.prependValue).toEqual(null);
expect(this.choices.config.appendValue).toEqual(null); expect(this.choices.config.appendValue).toEqual(null);
expect(this.choices.config.renderSelectedChoices).toEqual(jasmine.any(String)); expect(this.choices.config.renderSelectedChoices).toEqual(jasmine.any(String));
@ -1045,6 +1046,7 @@ describe('Choices', () => {
customProperties: { customProperties: {
foo: 'bar' foo: 'bar'
}, },
placeholder: false,
keyCode: null keyCode: null
}; };
@ -1057,6 +1059,7 @@ describe('Choices', () => {
active: true, active: true,
highlighted: false, highlighted: false,
customProperties: randomItem.customProperties, customProperties: randomItem.customProperties,
placeholder: false,
keyCode: randomItem.keyCode keyCode: randomItem.keyCode
}]; }];
@ -1084,6 +1087,7 @@ describe('Choices', () => {
customProperties: { customProperties: {
foo: 'bar' foo: 'bar'
}, },
placeholder: false,
keyCode: null keyCode: null
}; };
@ -1098,6 +1102,7 @@ describe('Choices', () => {
active: true, active: true,
score: 9999, score: 9999,
customProperties: randomChoice.customProperties, customProperties: randomChoice.customProperties,
placeholder: randomChoice.placeholder,
keyCode: randomChoice.keyCode keyCode: randomChoice.keyCode
}]; }];