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 {
type: 'ADD_ITEM',
value,
@ -7,7 +7,8 @@ export const addItem = (value, label, id, choiceId, groupId, customProperties, k
choiceId,
groupId,
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 {
type: 'ADD_CHOICE',
value,
@ -37,6 +38,7 @@ export const addChoice = (value, label, id, groupId, disabled, elementId, custom
disabled,
elementId,
customProperties,
placeholder,
keyCode
};
};

View file

@ -75,6 +75,7 @@ class Choices {
sortFilter: sortByAlpha,
placeholder: true,
placeholderValue: null,
searchPlaceholderValue: null,
prependValue: null,
appendValue: null,
renderSelectedChoices: 'auto',
@ -174,6 +175,11 @@ class Choices {
this.highlightPosition = 0;
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
this.presetChoices = this.config.choices;
@ -376,22 +382,35 @@ class Choices {
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 (this.config.shouldSort || this.isSearching) {
rendererableChoices.sort(filter);
normalChoices.sort(filter);
}
let choiceLimit = rendererableChoices.length;
// Prepend placeholeder
const sortedChoices = [...placeholderChoices, ...normalChoices];
if (this.isSearching) {
choiceLimit = Math.min(searchResultLimit, rendererableChoices.length - 1);
choiceLimit = Math.min(searchResultLimit, sortedChoices.length - 1);
} 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
for (let i = 0; i < choiceLimit; i++) {
appendChoice(rendererableChoices[i]);
appendChoice(sortedChoices[i]);
};
return choicesFragment;
@ -525,15 +544,17 @@ class Choices {
// Items
if (this.currentState.items !== this.prevState.items) {
// Get active items (items that can be selected)
const activeItems = this.store.getItemsFilteredByActive();
if (activeItems) {
// Clear list
this.itemList.innerHTML = '';
if (activeItems && activeItems) {
// 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);
// Clear list
this.itemList.innerHTML = '';
// If we have items to add
if (itemListFragment.childNodes) {
// Update list
@ -858,7 +879,7 @@ class Choices {
false,
-1,
item.customProperties,
null
item.placeholder
);
} else {
this._addItem(
@ -867,7 +888,7 @@ class Choices {
item.id,
undefined,
item.customProperties,
null
item.placeholder
);
}
} else if (itemType === 'String') {
@ -924,6 +945,7 @@ class Choices {
foundChoice.id,
foundChoice.groupId,
foundChoice.customProperties,
foundChoice.placeholder,
foundChoice.keyCode
);
} else if (!this.config.silent) {
@ -974,8 +996,8 @@ class Choices {
result.selected,
result.disabled,
undefined,
result['customProperties'],
null
result.customProperties,
result.placeholder
);
}
});
@ -1124,7 +1146,8 @@ class Choices {
this._triggerChange(itemToRemove.value);
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;
if (placeholder) {
const placeholderItem = this._getTemplate('placeholder', placeholder);
@ -1206,6 +1229,7 @@ class Choices {
choice.id,
choice.groupId,
choice.customProperties,
choice.placeholder,
choice.keyCode
);
this._triggerChange(choice.value);
@ -1379,10 +1403,26 @@ class Choices {
result.selected,
result.disabled,
undefined,
result['customProperties'],
null
result.customProperties,
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 {
// 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 (newValue.length >= 1 && newValue !== `${currentValue} `) {
const haystack = this.store.getChoicesFilteredBySelectable();
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 });
@ -2200,7 +2240,7 @@ class Choices {
* @return {Object} Class instance
* @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 passedKeyCode = keyCode;
const items = this.store.getItems();
@ -2231,6 +2271,7 @@ class Choices {
passedOptionId,
groupId,
customProperties,
placeholder,
passedKeyCode
)
);
@ -2311,7 +2352,7 @@ class Choices {
* @return
* @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) {
return;
}
@ -2331,6 +2372,7 @@ class Choices {
isDisabled,
choiceElementId,
customProperties,
placeholder,
keyCode
)
);
@ -2342,6 +2384,7 @@ class Choices {
choiceId,
undefined,
customProperties,
placeholder,
keyCode
);
}
@ -2395,7 +2438,8 @@ class Choices {
option.selected,
isOptDisabled,
groupId,
option.customProperties
option.customProperties,
option.placeholder
);
});
} else {
@ -2484,7 +2528,8 @@ class Choices {
globalClasses.item,
{
[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.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.disabled
[globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder
}
);
@ -2589,7 +2635,8 @@ class Choices {
globalClasses.itemChoice,
{
[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;
if (!this.isSelectOneElement) {
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,
selected: o.selected,
disabled: o.disabled || o.parentNode.disabled,
placeholder: o.hasAttribute('placeholder')
});
});
@ -2798,7 +2854,8 @@ class Choices {
choice.selected,
choice.disabled,
undefined,
choice.customProperties
choice.customProperties,
choice.placeholder
);
} else {
// Otherwise pre-select the first choice in the array
@ -2808,7 +2865,8 @@ class Choices {
true,
false,
undefined,
choice.customProperties
choice.customProperties,
choice.placeholder
);
}
} else {
@ -2818,7 +2876,8 @@ class Choices {
choice.selected,
choice.disabled,
undefined,
choice.customProperties
choice.customProperties,
choice.placeholder
);
}
});
@ -2836,7 +2895,8 @@ class Choices {
item.label,
item.id,
undefined,
item.customProperties
item.customProperties,
item.placeholder
);
} else if (itemType === 'String') {
this._addItem(item);

View file

@ -132,13 +132,13 @@ export const extend = function() {
*/
export const whichTransitionEvent = function() {
var t,
el = document.createElement("fakeelement");
el = document.createElement('fakeelement');
var transitions = {
"transition": "transitionend",
"OTransition": "oTransitionEnd",
"MozTransition": "transitionend",
"WebkitTransition": "webkitTransitionEnd"
'transition': 'transitionend',
'OTransition': 'oTransitionEnd',
'MozTransition': 'transitionend',
'WebkitTransition': 'webkitTransitionEnd'
}
for (t in transitions) {
@ -509,6 +509,20 @@ export const getWidthOfInput = (input) => {
testEl.style.width = 'auto';
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);
if (value && testEl.offsetWidth !== input.offsetWidth) {

View file

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

View file

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

View file

@ -85,9 +85,7 @@ export default class Store {
*/
getChoicesFilteredByActive() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.active === true;
}, []);
const values = choices.filter(choice => choice.active === true);
return values;
}
@ -98,13 +96,20 @@ export default class Store {
*/
getChoicesFilteredBySelectable() {
const choices = this.getChoices();
const values = choices.filter((choice) => {
return choice.disabled !== true;
}, []);
const values = choices.filter(choice => choice.disabled !== true);
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
* @return {Object} Found choice
@ -159,6 +164,19 @@ export default class Store {
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;

View file

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