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;
}