Ability to only render a portion of choices

This commit is contained in:
Josh Johnson 2017-07-24 14:46:30 +01:00
parent 04aca852c3
commit ca3bda1f70
3 changed files with 79 additions and 25 deletions

View file

@ -35,7 +35,7 @@ export const addChoice = (value, label, id, groupId, disabled, elementId, custom
id,
groupId,
disabled,
elementId: elementId,
elementId,
customProperties,
keyCode
};

View file

@ -53,6 +53,7 @@ class Choices {
silent: false,
items: [],
choices: [],
renderChoiceLimit: -1,
maxItemCount: -1,
addItems: true,
removeItems: true,
@ -338,7 +339,7 @@ class Choices {
if (groupChoices.length >= 1) {
const dropdownGroup = this._getTemplate('choiceGroup', group);
groupFragment.appendChild(dropdownGroup);
this.renderChoices(groupChoices, groupFragment);
this.renderChoices(groupChoices, groupFragment, true);
}
});
@ -352,37 +353,45 @@ class Choices {
* @return {DocumentFragment} Populated choices fragment
* @private
*/
renderChoices(choices, fragment) {
renderChoices(choices, fragment, withinGroup = false) {
// Create a fragment to store our list items (so we don't have to update the DOM for each item)
const choicesFragment = fragment || document.createDocumentFragment();
const { renderSelectedChoices, searchResultLimit, renderChoiceLimit } = this.config;
const filter = this.isSearching ? sortByScore : this.config.sortFilter;
const { renderSelectedChoices } = this.config;
const appendChoice = (choice) => {
const shouldRender = renderSelectedChoices === 'auto'
? this.isSelectOneElement || !choice.selected
: true;
const shouldRender = renderSelectedChoices === 'auto' ?
(this.isSelectOneElement || !choice.selected) :
true;
if (shouldRender) {
const dropdownItem = this._getTemplate('choice', choice);
choicesFragment.appendChild(dropdownItem);
}
};
// If sorting is enabled or the user is searching, filter choices
if (this.config.shouldSort || this.isSearching) {
choices.sort(filter);
let rendererableChoices = choices;
if (renderSelectedChoices === 'auto' && !this.isSelectOneElement) {
rendererableChoices = choices.filter(choice => !choice.selected);
}
if (this.isSearching) {
for (let i = 0; i < this.config.searchResultLimit; i++) {
const choice = choices[i];
if (choice) {
appendChoice(choice);
}
}
} else {
choices.forEach(choice => appendChoice(choice));
// If sorting is enabled or the user is searching, filter choices
if (this.config.shouldSort || this.isSearching) {
rendererableChoices.sort(filter);
}
let choiceLimit = rendererableChoices.length;
if (this.isSearching) {
choiceLimit = Math.min(searchResultLimit, rendererableChoices.length - 1);
} else if (renderChoiceLimit > 0 && !withinGroup) {
choiceLimit = Math.min(renderChoiceLimit, rendererableChoices.length - 1);
}
// Add each choice to dropdown within range
for (let i = 0; i < choiceLimit; i++) {
appendChoice(rendererableChoices[i]);
};
return choicesFragment;
}
@ -447,8 +456,11 @@ class Choices {
// Only render if our state has actually changed
if (this.currentState !== this.prevState) {
// Choices
if (this.currentState.choices !== this.prevState.choices ||
this.currentState.groups !== this.prevState.groups) {
if (
this.currentState.choices !== this.prevState.choices ||
this.currentState.groups !== this.prevState.groups ||
this.currentState.items !== this.prevState.items
) {
if (this.isSelectElement) {
// Get active groups/choices
const activeGroups = this.store.getGroupsFilteredByActive();

View file

@ -48,6 +48,7 @@ describe('Choices', () => {
expect(this.choices.config.silent).toEqual(jasmine.any(Boolean));
expect(this.choices.config.items).toEqual(jasmine.any(Array));
expect(this.choices.config.choices).toEqual(jasmine.any(Array));
expect(this.choices.config.renderChoiceLimit).toEqual(jasmine.any(Number));
expect(this.choices.config.maxItemCount).toEqual(jasmine.any(Number));
expect(this.choices.config.addItems).toEqual(jasmine.any(Boolean));
expect(this.choices.config.removeItems).toEqual(jasmine.any(Boolean));
@ -293,7 +294,9 @@ describe('Choices', () => {
});
it('should highlight the choices on keydown', function() {
this.choices = new Choices(this.input);
this.choices = new Choices(this.input, {
renderChoiceLimit: -1
});
this.choices.input.focus();
for (let i = 0; i < 2; i++) {
@ -930,7 +933,7 @@ describe('Choices', () => {
it('should flip the dropdown', function() {
this.choices = new Choices(this.input, {
position: 'top'
position: 'top',
});
const container = this.choices.containerOuter;
@ -950,7 +953,8 @@ describe('Choices', () => {
it('should render selected choices', function() {
this.choices = new Choices(this.input, {
renderSelectedChoices: 'always'
renderSelectedChoices: 'always',
renderChoiceLimit: -1
});
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
expect(renderedChoices.length).toEqual(3);
@ -958,11 +962,49 @@ describe('Choices', () => {
it('shouldn\'t render selected choices', function() {
this.choices = new Choices(this.input, {
renderSelectedChoices: 'auto'
renderSelectedChoices: 'auto',
renderChoiceLimit: -1
});
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
expect(renderedChoices.length).toEqual(1);
});
it('shouldn\'t render choices up to a render limit', function() {
// Remove existing choices (to make test simpler)
while (this.input.firstChild) {
this.input.removeChild(this.input.firstChild);
}
this.choices = new Choices(this.input, {
choices: [
{
value: 'Option 1',
selected: false,
},
{
value: 'Option 2',
selected: false,
},
{
value: 'Option 3',
selected: false,
},
{
value: 'Option 4',
selected: false,
},
{
value: 'Option 5',
selected: false,
},
],
renderSelectedChoices: 'auto',
renderChoiceLimit: 4
});
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
expect(renderedChoices.length).toEqual(4);
});
});
describe('should allow custom properties provided by the user on items or choices', function() {