` (if `wrapInput` set to `true`).
- * @property {string|string[]} [input] CSS class to add to the input.
- */
-
-/**
- * @typedef {Object} SearchBoxMagnifierOption
- * @property {function|string} template Template used for displaying the magnifier. Can accept a function or a Hogan string.
- * @property {{root: string}} [cssClasses] CSS classes added to the magnifier.
+ * @property {string|string[]} [root] CSS class to add to the wrapping `
`
+ * @property {string|string[]} [form] CSS class to add to the form
+ * @property {string|string[]} [input] CSS class to add to the input.
+ * @property {string|string[]} [reset] CSS classes added to the reset button.
+ * @property {string|string[]} [resetIcon] CSS classes added to the reset icon.
+ * @property {string|string[]} [loadingIndicator] CSS classes added to the loading indicator element.
+ * @property {string|string[]} [loadingIcon] CSS classes added to the loading indicator icon.
+ * @property {string|string[]} [submit] CSS classes added to the submit.
*/
/**
* @typedef {Object} SearchBoxWidgetOptions
- * @property {string|HTMLElement} container CSS Selector or HTMLElement to insert the widget. If the CSS selector or the HTMLElement is an existing input, the widget will use it.
- * @property {string} [placeholder] Input's placeholder.
- * @property {boolean|SearchBoxPoweredByOption} [poweredBy=false] Define if a "powered by Algolia" link should be added near the input.
- * @property {boolean|SearchBoxResetOption} [reset=true] Define if a reset button should be added in the input when there is a query.
- * @property {boolean|SearchBoxMagnifierOption} [magnifier=true] Define if a magnifier should be added at beginning of the input to indicate a search input.
- * @property {boolean|SearchBoxLoadingIndicatorOption} [loadingIndicator=false] Define if a loading indicator should be added at beginning of the input to indicate that search is currently stalled.
- * @property {boolean} [wrapInput=true] Wrap the input in a `div.ais-search-box`.
- * @property {boolean|string} [autofocus="auto"] autofocus on the input.
- * @property {boolean} [searchOnEnterKeyPressOnly=false] If set, trigger the search
+ * @property {string|HTMLElement} container CSS Selector or HTMLElement to insert the widget
+ * @property {string} [placeholder] The placeholder of the input
+ * @property {boolean} [autofocus=false] Whether the input should be autofocused
+ * @property {boolean} [searchAsYouType=true] If set, trigger the search
* once `` is pressed only.
- * @property {SearchBoxCSSClasses} [cssClasses] CSS classes to add.
- * @property {function} [queryHook] A function that will be called every time a new search would be done. You
- * will get the query as first parameter and a search(query) function to call as the second parameter.
- * This queryHook can be used to debounce the number of searches done from the searchBox.
+ * @property {boolean} [showReset=true] Whether to show the reset button
+ * @property {boolean} [showSubmit=true] Whether to show the submit button
+ * @property {boolean} [showLoadingIndicator=true] Whether to show the loading indicator (replaces the submit if
+ * the search is stalled)
+ * @property {SearchBoxCSSClasses} [cssClasses] CSS classes to add
+ * @property {SearchBoxTemplates} [templates] Templates used for customizing the rendering of the searchbox
+ * @property {function} [queryHook] A function that is called every time a new search is done. You
+ * will get the query as the first parameter and a search (query) function to call as the second parameter.
+ * This `queryHook` can be used to debounce the number of searches done from the search box.
*/
/**
@@ -243,25 +190,20 @@ searchBox({
* instantsearch.widgets.searchBox({
* container: '#q',
* placeholder: 'Search for products',
- * autofocus: false,
- * poweredBy: true,
- * reset: true,
- * loadingIndicator: false
* })
* );
*/
export default function searchBox({
container,
placeholder = '',
- cssClasses = {},
- poweredBy = false,
- wrapInput = true,
- autofocus = 'auto',
- searchOnEnterKeyPressOnly = false,
- reset = true,
- magnifier = true,
- loadingIndicator = false,
+ cssClasses: userCssClasses = {},
+ autofocus = false,
+ searchAsYouType = true,
+ showReset = true,
+ showSubmit = true,
+ showLoadingIndicator = true,
queryHook,
+ templates,
} = {}) {
if (!container) {
throw new Error(usage);
@@ -269,28 +211,60 @@ export default function searchBox({
const containerNode = getContainerNode(container);
- // Only possible values are 'auto', true and false
- if (typeof autofocus !== 'boolean') {
- autofocus = 'auto';
+ if (containerNode.tagName === 'INPUT') {
+ // eslint-disable-next-line
+ // FIXME: the link should be updated when the documentation is migrated in the main Algolia doc
+ throw new Error(
+ `[InstantSearch.js] Since in version 3, \`container\` can not be an \`input\` anymore.
+
+Learn more in the [migration guide](https://community.algolia.com/instantsearch.js/v3/guides/v3-migration.html).`
+ );
}
- // Convert to object if only set to true
- if (poweredBy === true) {
- poweredBy = {};
+ // eslint-disable-next-line
+ // FIXME: the link should be updated when the documentation is migrated in the main Algolia doc
+ if (typeof autofocus !== 'boolean') {
+ throw new Error(
+ `[InstantSearch.js] Since in version 3, \`autofocus\` only supports boolean values.
+
+Learn more in the [migration guide](https://community.algolia.com/instantsearch.js/v3/guides/v3-migration.html).`
+ );
}
+ const cssClasses = {
+ root: cx(suit(), userCssClasses.root),
+ form: cx(suit({ descendantName: 'form' }), userCssClasses.form),
+ input: cx(suit({ descendantName: 'input' }), userCssClasses.input),
+ submit: cx(suit({ descendantName: 'submit' }), userCssClasses.submit),
+ submitIcon: cx(
+ suit({ descendantName: 'submitIcon' }),
+ userCssClasses.submitIcon
+ ),
+ reset: cx(suit({ descendantName: 'reset' }), userCssClasses.reset),
+ resetIcon: cx(
+ suit({ descendantName: 'resetIcon' }),
+ userCssClasses.resetIcon
+ ),
+ loadingIndicator: cx(
+ suit({ descendantName: 'loadingIndicator' }),
+ userCssClasses.loadingIndicator
+ ),
+ loadingIcon: cx(
+ suit({ descendantName: 'loadingIcon' }),
+ userCssClasses.loadingIcon
+ ),
+ };
+
const specializedRenderer = renderer({
containerNode,
cssClasses,
placeholder,
- poweredBy,
- templates: defaultTemplates,
+ templates: { ...defaultTemplates, ...templates },
autofocus,
- searchOnEnterKeyPressOnly,
- wrapInput,
- reset,
- magnifier,
- loadingIndicator,
+ searchAsYouType,
+ showReset,
+ showSubmit,
+ showLoadingIndicator,
});
try {
@@ -299,62 +273,11 @@ export default function searchBox({
disposer(containerNode)
);
return makeWidget({ queryHook });
- } catch (e) {
+ } catch (error) {
throw new Error(usage);
}
}
-// the 'input' event is triggered when the input value changes
-// in any case: typing, copy pasting with mouse..
-// 'onpropertychange' is the IE8 alternative until we support IE8
-// but it's flawed: http://help.dottoro.com/ljhxklln.php
-
-function createInput(containerNode) {
- // Returns reference to targeted input if present, or create a new one
- if (containerNode.tagName === 'INPUT') {
- return containerNode;
- }
- return document.createElement('input');
-}
-
-function getInput(containerNode) {
- // Returns reference to targeted input if present, or look for it inside
- if (containerNode.tagName === 'INPUT') {
- return containerNode;
- }
- return containerNode.querySelector('input');
-}
-
-function wrapInputFn(input, cssClasses) {
- // Wrap input in a .ais-search-box div
- const wrapper = document.createElement('div');
- const CSSClassesToAdd = cx(bem(null), cssClasses.root).split(' ');
- CSSClassesToAdd.forEach(cssClass => wrapper.classList.add(cssClass));
- wrapper.appendChild(input);
- return wrapper;
-}
-
-function addListener(el, type, fn) {
- if (el.addEventListener) {
- el.addEventListener(type, fn);
- } else {
- el.attachEvent(`on${type}`, fn);
- }
-}
-
-function getValue(e) {
- return (e.currentTarget ? e.currentTarget : e.srcElement).value;
-}
-
-function ifKey(expectedKeyCode, func) {
- return actualEvent =>
- actualEvent.keyCode === expectedKeyCode && func(actualEvent);
-}
-
-function getInputValueAndCall(func) {
- return actualEvent => func(getValue(actualEvent));
-}
-
function addDefaultAttributesToInput(placeholder, input, query, cssClasses) {
const defaultAttributes = {
autocapitalize: 'off',
@@ -376,8 +299,7 @@ function addDefaultAttributesToInput(placeholder, input, query, cssClasses) {
});
// Add classes
- const CSSClassesToAdd = cx(bem('input'), cssClasses.input).split(' ');
- CSSClassesToAdd.forEach(cssClass => input.classList.add(cssClass));
+ input.className = cssClasses.input;
}
/**
@@ -385,152 +307,96 @@ function addDefaultAttributesToInput(placeholder, input, query, cssClasses) {
* 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 {object} cssClasses the object containing all the css classes
+ * @param {object} templates the templates object
* @param {function} clearFunction function called when the element is activated (clicked)
- * @returns {undefined} returns nothing
+ * @returns {undefined} Modifies the input
*/
-function addReset(input, reset, { reset: resetTemplate }, clearFunction) {
- reset = {
- cssClasses: {},
- template: resetTemplate,
- ...reset,
- };
-
- const resetCSSClasses = {
- root: cx(bem('reset'), reset.cssClasses.root),
- };
-
+function addReset(input, cssClasses, templates, clearFunction) {
const stringNode = renderTemplate({
- templateKey: 'template',
- templates: reset,
+ templateKey: 'reset',
+ templates,
data: {
- cssClasses: resetCSSClasses,
+ cssClasses,
},
});
- const htmlNode = createNodeFromString(stringNode, cx(bem('reset-wrapper')));
+ const node = document.createElement('button');
+ node.className = cssClasses.reset;
+ node.setAttribute('hidden', '');
+ node.type = 'reset';
+ node.title = 'Clear the search query';
+ node.innerHTML = stringNode;
- input.parentNode.appendChild(htmlNode);
+ input.parentNode.appendChild(node);
- htmlNode.addEventListener('click', event => {
- event.preventDefault();
+ node.addEventListener('click', () => {
+ input.focus();
clearFunction();
});
}
/**
- * Adds a magnifying glass in the searchbox widget
+ * Adds a button with 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
+ * @param {object} cssClasses the user options (cssClasses and template)
+ * @param {object} templates the object containing all the templates
+ * @returns {undefined} Modifies the input
*/
-function addMagnifier(input, magnifier, { magnifier: magnifierTemplate }) {
- magnifier = {
- cssClasses: {},
- template: magnifierTemplate,
- ...magnifier,
- };
-
- const magnifierCSSClasses = {
- root: cx(bem('magnifier'), magnifier.cssClasses.root),
- };
-
+function addSubmit(input, cssClasses, templates) {
const stringNode = renderTemplate({
- templateKey: 'template',
- templates: magnifier,
+ templateKey: 'submit',
+ templates,
data: {
- cssClasses: magnifierCSSClasses,
+ cssClasses,
},
});
- const htmlNode = createNodeFromString(
- stringNode,
- cx(bem('magnifier-wrapper'))
- );
+ const node = document.createElement('button');
+ node.className = cssClasses.submit;
+ node.type = 'submit';
+ node.title = 'Submit the search query';
+ node.innerHTML = stringNode;
- input.parentNode.appendChild(htmlNode);
+ input.parentNode.appendChild(node);
}
-function addLoadingIndicator(
- input,
- loadingIndicator,
- { loadingIndicator: loadingIndicatorTemplate }
-) {
- loadingIndicator = {
- cssClasses: {},
- template: loadingIndicatorTemplate,
- ...loadingIndicator,
- };
-
- const loadingIndicatorCSSClasses = {
- root: cx(bem('loading-indicator'), loadingIndicator.cssClasses.root),
- };
-
+/**
+ * Adds a loading indicator (spinner) to the search box
+ * @param {DomElement} input DOM element where to add the loading indicator
+ * @param {Object} cssClasses css classes definition
+ * @param {Object} templates templates of the widget
+ * @returns {undefined} Modifies the input
+ */
+function addLoadingIndicator(input, cssClasses, templates) {
const stringNode = renderTemplate({
- templateKey: 'template',
- templates: loadingIndicator,
+ templateKey: 'loadingIndicator',
+ templates,
data: {
- cssClasses: loadingIndicatorCSSClasses,
+ cssClasses,
},
});
- const htmlNode = createNodeFromString(
- stringNode,
- cx(bem('loading-indicator-wrapper'))
- );
+ const node = document.createElement('span');
+ node.setAttribute('hidden', '');
+ node.className = cssClasses.loadingIndicator;
+ node.innerHTML = stringNode;
- input.parentNode.appendChild(htmlNode);
+ input.parentNode.appendChild(node);
}
-/**
- * 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, { poweredBy: poweredbyTemplate }) {
- // Default values
- poweredBy = {
- cssClasses: {},
- template: poweredbyTemplate,
- ...poweredBy,
- };
-
- const poweredByCSSClasses = {
- root: cx(bem('powered-by'), poweredBy.cssClasses.root),
- link: cx(bem('powered-by-link'), poweredBy.cssClasses.link),
- };
-
- const url =
- 'https://www.algolia.com/?' +
- 'utm_source=instantsearch.js&' +
- 'utm_medium=website&' +
- `utm_content=${location.hostname}&` +
- 'utm_campaign=poweredby';
-
- const stringNode = renderTemplate({
- templateKey: 'template',
- templates: poweredBy,
- data: {
- cssClasses: poweredByCSSClasses,
- url,
- },
- });
+function wrapInputFn(input, cssClasses) {
+ const wrapper = document.createElement('div');
+ wrapper.className = cssClasses.root;
- const htmlNode = createNodeFromString(stringNode);
+ const form = document.createElement('form');
+ form.className = cssClasses.form;
+ form.noValidate = true;
+ form.action = ''; // show search button on iOS keyboard
- input.parentNode.insertBefore(htmlNode, input.nextSibling);
-}
+ form.appendChild(input);
+ wrapper.appendChild(form);
-// Cross-browser 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, rootClassname = '') {
- const tmpNode = document.createElement('div');
- tmpNode.innerHTML = `${stringNode.trim()}`;
- return tmpNode.firstChild;
+ return wrapper;
}