diff --git a/assets/scripts/src/actions/index.js b/assets/scripts/src/actions/index.js index c7bd626..7f3b600 100644 --- a/assets/scripts/src/actions/index.js +++ b/assets/scripts/src/actions/index.js @@ -35,7 +35,7 @@ export const addChoice = (value, label, id, groupId, disabled, elementId, custom id, groupId, disabled, - elementId: elementId, + elementId, customProperties, keyCode }; diff --git a/assets/scripts/src/choices.js b/assets/scripts/src/choices.js index dfa5724..aa1ac1f 100644 --- a/assets/scripts/src/choices.js +++ b/assets/scripts/src/choices.js @@ -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(); diff --git a/tests/spec/choices_spec.js b/tests/spec/choices_spec.js index 7a07433..b48f60b 100644 --- a/tests/spec/choices_spec.js +++ b/tests/spec/choices_spec.js @@ -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() {