isomorphic-search
Version:
A configurable search interface.
155 lines (128 loc) • 4.12 kB
JavaScript
/**
* Client-side
*/
let presentation = {
// Inserts element into DOM at proper index.
insertAt($container, action, index, content) {
let siblings = $container.children();
if (siblings.length) {
return siblings.eq(index).after(content);
}
return $container[action](content);
},
// Sets given template on view.
render($container, content, {action='append', index=false}) {
if (!index && index !== 0) {
$container[action](content);
} else {
this.insertAt($container, content, {action, index});
}
return
},
/**
* Renders search results to the view.
*
* @param {array} results
* @param {object} abstraction
* @return {array} results
*/
renderSearchResults(results, abstraction) {
// Reset view.
abstraction.$el.noResults.remove();
// Get elements.
let {$el, el, attr, identifier, template} = abstraction,
hiddenClass = attr.hidden,
$results = $el.results,
$resultsList = $el.resultsList,
$li = $el.result,
li = el.result;
// Empty view before rendering results only.
$li.addClass(hiddenClass);
if (!results || !results.length) {
$resultsList.prepend(template.noResults(abstraction));
return results;
}
results.forEach((result) => {
// Display matching search result.
$results.
find(`${li}[data-${identifier}="${result[identifier]}"]`).
removeClass(hiddenClass);
});
return results;
},
search: {
// Cancel search.
cancel: (event, {attr, $el, icon, metadata}) => {
let cancelClass = icon.cancel,
hiddenClass = attr.hidden,
iconClasses = `${cancelClass} ${icon.search}`,
{input} = metadata,
search = $el.input.val();
// Reset value of search field.
$el.input.val('');
// Remove no results view.
$el.noResults.remove();
// Hide results from view.
$el.results.addClass(hiddenClass);
// Reveal individual results so they'll be exposed with container.
$el.result.removeClass(hiddenClass);
// Toggle from cancel button to search button.
$el.container.find(`.${cancelClass}`).toggleClass(iconClasses);
// Execute callback.
if (input && input.cancel && input.cancel.callback) {
input.cancel.callback(event, search);
}
},
// Update input.
// @TODO Review change method.
change: (event, abstraction, data, engine, searchCallback) => {
const {input} = abstraction.metadata;
// Execute search.
let search = searchCallback(abstraction, data, engine);
// Execute callback on change.
if (input && input.change && input.change.callback) {
input.change.callback(event, search);
}
},
// Focus on input.
focus: (event, abstraction) => {
let {$el, attr, icon, template, metadata} = abstraction,
{input} = metadata,
hiddenClass = attr.hidden,
search = $el.input.val();
// Replace the search icon with the cancel icon.
$el.submit.removeClass(icon.search).addClass(icon.cancel);
// Display all potential results.
$el.results.removeClass(hiddenClass);
// Execute callback.
if (input && input.focus && input.focus.callback) {
input.focus.callback(event, search);
}
return true;
}
},
// Set events on view.
bind(abstraction, data, engine, onChangeCallback) {
let {attr, $el, icon} = abstraction;
// Set events on search input.
$el.input.
off().
on('focus', (event) => {
this.search.focus(event, abstraction);
}).
on('keyup, paste, input', (event) => {
this.search.change(event, abstraction, data, engine, onChangeCallback);
});
$el.menu.
off().
// Set events on search icon.
on('click', `.${icon.search}`, (event) => {
this.search.focus(event, abstraction);
}).
// Set events on cancel icon.
on('click', `.${icon.cancel}`, (event) => {
this.search.cancel(event, abstraction);
});
}
};
export default presentation;