diff --git a/dev/app/builtin/stories/search-box.stories.js b/dev/app/builtin/stories/search-box.stories.js index 4af5b958f8..6620a421a7 100644 --- a/dev/app/builtin/stories/search-box.stories.js +++ b/dev/app/builtin/stories/search-box.stories.js @@ -20,6 +20,27 @@ export default () => { ); }) ) + .add( + 'with custom templates', + wrapWithHits(container => { + window.search.addWidget( + instantsearch.widgets.searchBox({ + container, + placeholder: 'Search for products', + poweredBy: true, + magnifier: { + template: '
🔍
', + }, + reset: { + template: '
✖️
', + }, + templates: { + poweredBy: 'Algolia', + }, + }) + ); + }) + ) .add( 'search on enter', wrapWithHits(container => { diff --git a/package.json b/package.json index 0eb0150064..e49227f4cb 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "scripts": { "build": "./scripts/build.sh", "dev": "./scripts/dev.sh", + "dev-novel": "NODE_ENV=development webpack-dev-server --config dev/webpack.config.js", "doctoc": "doctoc --no-title --maxlevel 3 README.md CONTRIBUTING.md", "lint": "npm run lint:js && npm run lint:css", "lint:js": "eslint .", diff --git a/src/widgets/search-box/__tests__/search-box-test.js b/src/widgets/search-box/__tests__/search-box-test.js index 5b522be16e..da82721c32 100644 --- a/src/widgets/search-box/__tests__/search-box-test.js +++ b/src/widgets/search-box/__tests__/search-box-test.js @@ -221,7 +221,7 @@ describe('searchBox()', () => { widget.init(defaultInitOptions); // Then - expect($('button[type="reset"]')[0].style.display).toBe('none'); + expect($('.ais-search-box--reset-wrapper')[0].style.display).toBe('none'); }); it('should be shown when there is a query', () => { @@ -233,7 +233,9 @@ describe('searchBox()', () => { simulateInputEvent('test', 'tes', widget, helper, state, container); // Then - expect($('button[type="reset"]')[0].style.display).toBe('block'); + expect($('.ais-search-box--reset-wrapper')[0].style.display).toBe( + 'block' + ); }); it('should clear the query', () => { @@ -243,7 +245,7 @@ describe('searchBox()', () => { simulateInputEvent('test', 'tes', widget, helper, state, container); // When - $('button[type="reset"]')[0].click(); + $('.ais-search-box--reset-wrapper')[0].click(); // Then expect(helper.setQuery.called).toBe(true); @@ -277,7 +279,7 @@ describe('searchBox()', () => { widget.init(defaultInitOptions); // Then - expect($('button[type="reset"]').length).toEqual(0); + expect($('.ais-search-box--reset-wrapper').length).toEqual(0); }); }); diff --git a/src/widgets/search-box/search-box.js b/src/widgets/search-box/search-box.js index 7634bad13e..fd5ea613be 100644 --- a/src/widgets/search-box/search-box.js +++ b/src/widgets/search-box/search-box.js @@ -114,15 +114,12 @@ const renderer = ({ } if (reset) { - const resetButtonContainer = - containerNode.tagName === 'INPUT' - ? containerNode.parentNode - : containerNode; - + const resetBtnSelector = `.${cx(bem('reset-wrapper'))}`; // hide reset button when there is no query - const resetButton = resetButtonContainer.querySelector( - 'button[type="reset"]' - ); + const resetButton = + containerNode.tagName === 'INPUT' + ? containerNode.parentNode.querySelector(resetBtnSelector) + : containerNode.querySelector(resetBtnSelector); resetButton.style.display = query && query.trim() ? 'block' : 'none'; } }; @@ -336,6 +333,16 @@ function addDefaultAttributesToInput(placeholder, input, query, cssClasses) { CSSClassesToAdd.forEach(cssClass => input.classList.add(cssClass)); } +/** + * Adds a reset element in the searchbox widget. When this reset element is clicked on + * it should reset the query. + * @private + * @param {HTMLElement} input the DOM node of the input of the searchbox + * @param {object} reset the user options (cssClasses and template) + * @param {object} $2 the default templates + * @param {function} clearFunction function called when the element is activated (clicked) + * @returns {undefined} returns nothing + */ function addReset(input, reset, { reset: resetTemplate }, clearFunction) { reset = { cssClasses: {}, @@ -348,7 +355,7 @@ function addReset(input, reset, { reset: resetTemplate }, clearFunction) { cssClasses: resetCSSClasses, }); - const htmlNode = createNodeFromString(stringNode); + const htmlNode = createNodeFromString(stringNode, cx(bem('reset-wrapper'))); input.parentNode.appendChild(htmlNode); htmlNode.addEventListener('click', event => { @@ -357,6 +364,14 @@ function addReset(input, reset, { reset: resetTemplate }, clearFunction) { }); } +/** + * Adds a magnifying glass in the searchbox widget + * @private + * @param {HTMLElement} input the DOM node of the input of the searchbox + * @param {object} magnifier the user options (cssClasses and template) + * @param {object} $2 the default templates + * @returns {undefined} returns nothing + */ function addMagnifier(input, magnifier, { magnifier: magnifierTemplate }) { magnifier = { cssClasses: {}, @@ -371,10 +386,21 @@ function addMagnifier(input, magnifier, { magnifier: magnifierTemplate }) { cssClasses: magnifierCSSClasses, }); - const htmlNode = createNodeFromString(stringNode); + const htmlNode = createNodeFromString( + stringNode, + cx(bem('magnifier-wrapper')) + ); input.parentNode.appendChild(htmlNode); } +/** + * Adds a powered by in the searchbox widget + * @private + * @param {HTMLElement} input the DOM node of the input of the searchbox + * @param {object} poweredBy the user options (cssClasses and template) + * @param {object} templates the default templates + * @returns {undefined} returns nothing + */ function addPoweredBy(input, poweredBy, templates) { // Default values poweredBy = { @@ -408,9 +434,9 @@ function addPoweredBy(input, poweredBy, templates) { // Crossbrowser way to create a DOM node from a string. We wrap in // a `span` to make sure we have one and only one node. -function createNodeFromString(stringNode) { +function createNodeFromString(stringNode, rootClassname = '') { const tmpNode = document.createElement('div'); - tmpNode.innerHTML = `${stringNode.trim()}`; + tmpNode.innerHTML = `${stringNode.trim()}`; return tmpNode.firstChild; }