Skip to content

Commit

Permalink
feat(connectors): connectSearchBox (iteration2)
Browse files Browse the repository at this point in the history
  • Loading branch information
iam4x committed Mar 22, 2017
1 parent e77ce1b commit 3161c9b
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 140 deletions.
18 changes: 2 additions & 16 deletions src/connectors/search-box/__tests__/connectSearchBox-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


import sinon from 'sinon';

import jsHelper from 'algoliasearch-helper';
Expand Down Expand Up @@ -39,14 +37,8 @@ describe('connectSearchBox', () => {
expect(isFirstRendering).toBe(true);

// should provide good values for the first rendering
const {query, containerNode, poweredBy,
autofocus, searchOnEnterKeyPressOnly, placeholder} = rendering.lastCall.args[0];
expect(containerNode).toBe(container);
const {query} = rendering.lastCall.args[0];
expect(query).toBe(helper.state.query);
expect(poweredBy).toBe(false);
expect(autofocus).toBe('auto');
expect(searchOnEnterKeyPressOnly).toBe(false);
expect(placeholder).toBe('');
}

widget.render({
Expand All @@ -62,14 +54,8 @@ describe('connectSearchBox', () => {
expect(isFirstRendering).toBe(false);

// should provide good values after the first search
const {query, containerNode, poweredBy,
autofocus, searchOnEnterKeyPressOnly, placeholder} = rendering.lastCall.args[0];
expect(containerNode).toBe(container);
const {query} = rendering.lastCall.args[0];
expect(query).toBe(helper.state.query);
expect(poweredBy).toBe(false);
expect(autofocus).toBe('auto');
expect(searchOnEnterKeyPressOnly).toBe(false);
expect(placeholder).toBe('');
}
});

Expand Down
122 changes: 41 additions & 81 deletions src/connectors/search-box/connectSearchBox.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import {
getContainerNode,
} from '../../lib/utils.js';
import defaultTemplates from './defaultTemplates.js';
import {checkRendering} from '../../lib/utils.js';

const usage = `Usage:
var customSearchBox = connectMenu(function render(params, isFirstRendering) {
// params = {
// query,
// onHistoryChange,
// search,
// }
});
search.addWidget(
customSearchBox({
[ onQueryHook ]
});
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectSearchBox.html
`;

/**
* Instantiate a searchbox
Expand All @@ -27,95 +40,42 @@ import defaultTemplates from './defaultTemplates.js';
* @return {Object}
*/

const usage = `Usage:
searchBox({
container,
[ placeholder ],
[ cssClasses.{input,poweredBy} ],
[ poweredBy=false || poweredBy.{template, cssClasses.{root,link}} ],
[ wrapInput ],
[ autofocus ],
[ searchOnEnterKeyPressOnly ],
[ queryHook ]
})`;
const connectSearchBox = searchBoxRendering => ({
container,
placeholder = '',
cssClasses = {},
poweredBy = false,
wrapInput = true,
autofocus = 'auto',
searchOnEnterKeyPressOnly = false,
queryHook,
}) => {
if (!container) {
throw new Error(usage);
}

const containerNode = getContainerNode(container);

// Only possible values are 'auto', true and false
if (typeof autofocus !== 'boolean') {
autofocus = 'auto';
}

// Convert to object if only set to true
if (poweredBy === true) {
poweredBy = {};
}
export default function connectSearchBox(renderFn) {
checkRendering(renderFn, usage);

const makeSearch = helper => {
let previousQuery;
return ({queryHook}) => ({
init({helper, onHistoryChange}) {
this._search = (() => {
let previousQuery;

const setQueryAndSearch = (q, doSearch = true) => {
if (q !== helper.state.query) {
previousQuery = helper.state.query;
helper.setQuery(q);
}
if (doSearch && previousQuery !== undefined && previousQuery !== q) helper.search();
};
const setQueryAndSearch = (q, doSearch = true) => {
if (q !== helper.state.query) {
previousQuery = helper.state.query;
helper.setQuery(q);
}
if (doSearch && previousQuery !== undefined && previousQuery !== q) helper.search();
};

return queryHook ?
q => queryHook(q, setQueryAndSearch) :
setQueryAndSearch;
};
return queryHook ?
q => queryHook(q, setQueryAndSearch) :
setQueryAndSearch;
})();

return {
init({helper, onHistoryChange}) {
this._search = makeSearch(helper);
this._onHistoryChange = onHistoryChange;
searchBoxRendering({

renderFn({
query: helper.state.query,
containerNode,
onHistoryChange: this._onHistoryChange,
poweredBy,
wrapInput,
autofocus,
searchOnEnterKeyPressOnly,
placeholder,
cssClasses,
templates: defaultTemplates,
search: this._search,
}, true);
},

render({helper}) {
searchBoxRendering({
renderFn({
query: helper.state.query,
containerNode,
onHistoryChange: this.onHistoryChange,
poweredBy,
helper,
wrapInput,
autofocus,
queryHook,
searchOnEnterKeyPressOnly,
placeholder,
cssClasses,
templates: defaultTemplates,
onHistoryChange: this._onHistoryChange,
search: this._search,
}, false);
},
};
};

export default connectSearchBox;
});
}
File renamed without changes.
145 changes: 102 additions & 43 deletions src/widgets/search-box/search-box.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,35 @@
import {
bemHelper,
} from '../../lib/utils.js';
import forEach from 'lodash/forEach';
import isString from 'lodash/isString';
import isFunction from 'lodash/isFunction';
import cx from 'classnames';
import Hogan from 'hogan.js';

const bem = bemHelper('ais-search-box');
const KEY_ENTER = 13;
const KEY_SUPPRESS = 8;

import connectSearchBox from '../../connectors/search-box/connectSearchBox.js';
import defaultTemplates from './defaultTemplates.js';

/**
* Instantiate a searchbox
* @function searchBox
* @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @param {string} [options.placeholder] Input's placeholder [*]
* @param {boolean|Object} [options.poweredBy=false] Define if a "powered by Algolia" link should be added near the input
* @param {function|string} [options.poweredBy.template] Template used for displaying the link. Can accept a function or a Hogan string.
* @param {number} [options.poweredBy.cssClasses] CSS classes to add
* @param {string|string[]} [options.poweredBy.cssClasses.root] CSS class to add to the root element
* @param {string|string[]} [options.poweredBy.cssClasses.link] CSS class to add to the link element
* @param {boolean} [options.wrapInput=true] Wrap the input in a `div.ais-search-box`
* @param {boolean|string} [autofocus='auto'] autofocus on the input
* @param {boolean} [options.searchOnEnterKeyPressOnly=false] If set, trigger the search
* once `<Enter>` is pressed only
* @param {Object} [options.cssClasses] CSS classes to add
* @param {string|string[]} [options.cssClasses.root] CSS class to add to the
* wrapping div (if `wrapInput` set to `true`)
* @param {string|string[]} [options.cssClasses.input] CSS class to add to the input
* @param {function} [options.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.
* @return {Object}
*/
import {
bemHelper,
getContainerNode,
} from '../../lib/utils.js';

export default connectSearchBox(defaultRendering);
const bem = bemHelper('ais-search-box');
const KEY_ENTER = 13;
const KEY_SUPPRESS = 8;

// 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 defaultRendering({
query,
const renderer = ({
containerNode,
onHistoryChange,
cssClasses,
placeholder,
poweredBy,
wrapInput,
templates,
autofocus,
searchOnEnterKeyPressOnly,
placeholder,
cssClasses,
templates,
wrapInput,
}) => ({
search,
}, isFirstRendering) {
query,
onHistoryChange,
}, isFirstRendering) => {
if (isFirstRendering) {
const INPUT_EVENT = window.addEventListener ?
'input' :
Expand Down Expand Up @@ -125,8 +99,93 @@ function defaultRendering({
input.value = query;
}
}
};

const usage = `Usage:
searchBox({
container,
[ placeholder ],
[ cssClasses.{input,poweredBy} ],
[ poweredBy=false || poweredBy.{template, cssClasses.{root,link}} ],
[ wrapInput ],
[ autofocus ],
[ searchOnEnterKeyPressOnly ],
[ queryHook ]
})`;

/**
* Instantiate a searchbox
* @function searchBox
* @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @param {string} [options.placeholder] Input's placeholder [*]
* @param {boolean|Object} [options.poweredBy=false] Define if a "powered by Algolia" link should be added near the input
* @param {function|string} [options.poweredBy.template] Template used for displaying the link. Can accept a function or a Hogan string.
* @param {number} [options.poweredBy.cssClasses] CSS classes to add
* @param {string|string[]} [options.poweredBy.cssClasses.root] CSS class to add to the root element
* @param {string|string[]} [options.poweredBy.cssClasses.link] CSS class to add to the link element
* @param {boolean} [options.wrapInput=true] Wrap the input in a `div.ais-search-box`
* @param {boolean|string} [autofocus='auto'] autofocus on the input
* @param {boolean} [options.searchOnEnterKeyPressOnly=false] If set, trigger the search
* once `<Enter>` is pressed only
* @param {Object} [options.cssClasses] CSS classes to add
* @param {string|string[]} [options.cssClasses.root] CSS class to add to the
* wrapping div (if `wrapInput` set to `true`)
* @param {string|string[]} [options.cssClasses.input] CSS class to add to the input
* @param {function} [options.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.
* @return {Object} widget
*/
export default function searchBox({
container,
placeholder = '',
cssClasses = {},
poweredBy = false,
wrapInput = true,
autofocus = 'auto',
searchOnEnterKeyPressOnly = false,
queryHook,
}) {
if (!container) {
throw new Error(usage);
}

const containerNode = getContainerNode(container);

// Only possible values are 'auto', true and false
if (typeof autofocus !== 'boolean') {
autofocus = 'auto';
}

// Convert to object if only set to true
if (poweredBy === true) {
poweredBy = {};
}

const specializedRenderer = renderer({
containerNode,
cssClasses,
placeholder,
poweredBy,
templates: defaultTemplates,
autofocus,
searchOnEnterKeyPressOnly,
wrapInput,
});

try {
const makeWidget = connectSearchBox(specializedRenderer);
return makeWidget({queryHook});
} catch (e) {
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') {
Expand Down

0 comments on commit 3161c9b

Please sign in to comment.