From ba09fb00e6122848237d863f12b2b95669ded49c Mon Sep 17 00:00:00 2001 From: Glade Date: Tue, 12 Feb 2019 09:56:21 +1100 Subject: [PATCH] callback to filter items before adding (#485) * Add item custom callback * Minor unit test updates * Test updates, Changed callback name to more clearly distinguish it's function * Fix description wording in cypress * Update README * Updated filter item callback name to be addItemFilter --- README.md | 19 ++++++++++++++++++ cypress/integration/text.spec.js | 33 ++++++++++++++++++++++++++++++++ public/test/text.html | 11 +++++++++++ src/scripts/choices.js | 13 +++++++++++++ src/scripts/constants.js | 2 ++ src/scripts/constants.test.js | 2 ++ 6 files changed, 80 insertions(+) diff --git a/README.md b/README.md index b5d5b9a..d0eef2d 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ will be returned. If you target one element, that instance will be returned. position: 'auto', resetScrollPosition: true, regexFilter: null, + addItemFilter: null, shouldSort: true, shouldSortItems: false, sortFn: () => {...}, @@ -339,6 +340,24 @@ Pass an array of objects: **Usage:** Whether the scroll position should reset after adding an item. +### addItemFilter +**Type:** `Function` **Default:** `null` + +**Input types affected:** `text` + +**Usage:** A callback function that will need to return `true` for a user to successfully add an item. + +**Example:** + +```js +// Only adds items matching the text test +new Choices(element, { + addItemFilter: function (value) { + return (value !== 'test') + } +}); +``` + ### regexFilter **Type:** `Regex` **Default:** `null` diff --git a/cypress/integration/text.spec.js b/cypress/integration/text.spec.js index b65ce34..abdc4a2 100644 --- a/cypress/integration/text.spec.js +++ b/cypress/integration/text.spec.js @@ -293,6 +293,39 @@ describe('Choices - text element', () => { }); }); + describe('custom add item callback', () => { + describe('inputting a value that satisfies the addItemFilter', () => { + const input = 'test'; + + it('allows me to add choice', () => { + cy.get('[data-test-hook=add-item-callback]') + .find('.choices__input--cloned') + .type(input) + .type('{enter}'); + + cy.get('[data-test-hook=add-item-callback]') + .find('.choices__list--multiple .choices__item') + .last() + .should($choice => { + expect($choice.text().trim()).to.equal(input); + }); + }); + }); + + describe('inputting a value that does not satisfy the callback', () => { + it('displays dropdown prompt', () => { + cy.get('[data-test-hook=add-item-callback]') + .find('.choices__input--cloned') + .type(`this is not the allowed text`) + .type('{enter}'); + + cy.get('[data-test-hook=add-item-callback]') + .find('.choices__list--dropdown') + .should('not.be.visible'); + }); + }); + }); + describe('disabled via attribute', () => { it('does not allow me to input data', () => { cy.get('[data-test-hook=disabled-via-attr]') diff --git a/public/test/text.html b/public/test/text.html index cc5d648..dd7427f 100644 --- a/public/test/text.html +++ b/public/test/text.html @@ -64,6 +64,11 @@ +
+ + +
+
@@ -120,6 +125,12 @@ addItems: false, }); + new Choices('#choices-add-item-callback', { + addItemFilter: function (value) { + return (value !== 'test') + } + }); + new Choices('#choices-disabled-via-attr'); new Choices('#choices-prepend-append', { diff --git a/src/scripts/choices.js b/src/scripts/choices.js index d8e2155..170c968 100644 --- a/src/scripts/choices.js +++ b/src/scripts/choices.js @@ -987,6 +987,19 @@ class Choices { ? this.config.uniqueItemText(value) : this.config.uniqueItemText; } + + if ( + isType('Function', this.config.addItemFilter) && + this.config.addItemFilter(value) && + this._isTextElement && + this.config.addItems && + canAddItem + ) { + canAddItem = false; + notice = isType('Function', this.config.customAddItemText) + ? this.config.customAddItemText(value) + : this.config.customAddItemText; + } } return { diff --git a/src/scripts/constants.js b/src/scripts/constants.js index 1f844c3..64aac00 100644 --- a/src/scripts/constants.js +++ b/src/scripts/constants.js @@ -64,6 +64,7 @@ export const DEFAULT_CONFIG = { noChoicesText: 'No choices to choose from', itemSelectText: 'Press to select', uniqueItemText: 'Only unique values can be added', + customAddItemText: 'Only values matching specific conditions can be added.', addItemText: value => `Press Enter to add "${stripHTML(value)}"`, maxItemText: maxItemCount => `Only ${maxItemCount} values can be added`, itemComparer: (choice, item) => choice === item, @@ -71,6 +72,7 @@ export const DEFAULT_CONFIG = { includeScore: true, }, callbackOnInit: null, + addItemFilter: null, callbackOnCreateTemplates: null, classNames: DEFAULT_CLASSNAMES, }; diff --git a/src/scripts/constants.test.js b/src/scripts/constants.test.js index f52dc81..01a1f6e 100644 --- a/src/scripts/constants.test.js +++ b/src/scripts/constants.test.js @@ -82,9 +82,11 @@ describe('constants', () => { expect(DEFAULT_CONFIG.noChoicesText).to.be.a('string'); expect(DEFAULT_CONFIG.itemSelectText).to.be.a('string'); expect(DEFAULT_CONFIG.uniqueItemText).to.be.a('string'); + expect(DEFAULT_CONFIG.customAddItemText).to.be.a('string'); expect(DEFAULT_CONFIG.addItemText).to.be.a('function'); expect(DEFAULT_CONFIG.maxItemText).to.be.a('function'); expect(DEFAULT_CONFIG.fuseOptions).to.be.an('object'); + expect(DEFAULT_CONFIG.addItemFilter).to.equal(null); expect(DEFAULT_CONFIG.callbackOnInit).to.equal(null); expect(DEFAULT_CONFIG.callbackOnCreateTemplates).to.equal(null); });