UNPKG

isomorphic-search

Version:
230 lines (206 loc) 5.97 kB
/** * Search module's abstraction */ const identifier = 'id'; const label = 'name'; let abstraction = { // Set the identifying property among the objects in the list. identifier, // Set the labeling property among the objects in the list. label, // The notification system used when set-up fails. See control.set method. notify: console ? console.log : (error) => { return error; }, // Set default parent selector. parent: 'header', // Empowers searching by representing the current search criteria. searchBy: false, // Threshold for error when matching search to potential results. threshold: 0.2, // Search parameters. keys: [ { name: identifier, weight: 0.3 }, { name: label, weight: 0.7 }, ], // Represents icon classes. icon: { search: 'fa-search', cancel: 'fa-close' }, // Options passed to the search engine. set options(options) { let existing = Object.create(this._options || null); delete this._options; this._options = Object.assign(existing, options); }, get options() { return this._options = Object.assign({ keys: this.keys, threshold: this.threshold, }, this._options); }, // Classes and other identifiers to be attached to elements. set attr(attr) { let existing = Object.create(this._attr || null); delete this._attr; this._attr = Object.assign(existing, attr); }, get attr() { // @TODO Abstract. const defaults = { body: 'body', container: 'search', parent: this.parent, input: 'search-field', hidden: 'invisible', instructions: 'instructions', menu: 'search-menu', noResults: 'no-results', result: 'result', results: 'search-results', resultsList: 'search-results-list', searchFieldContainer: 'search-field-container', searchContainer: 'search-container', submit: 'search-submit', title: 'search-title' }; return this._attr = Object.assign(defaults, this._attr || null); }, // Elements set el(element) { let existing = Object.create(this._el || null); delete this._el; this._el = Object.assign(existing, element); }, get el() { let existing = this._el, elements = this.attr, _el = {}; Object.keys(elements).forEach((el) => { if (el === 'parent' || el === 'body') { _el[el] = elements[el]; } else { _el[el] = `.${elements[el]}`; } }); return this._el = _el; }, // DOM Nodes set $el(element) { let existing = Object.create(this._$el || null); delete this._$el; this._$el = Object.assign(existing, element); }, get $el() { let existing = this._$el, el = this.el, _$el = {}; Object.keys(el).forEach((element) => { let isWrapper = ['body', 'container', 'parent'].indexOf(element) !== -1; if (!isWrapper) { _$el[element] = $(el.container).find(el[element]); } else { _$el[element] = $(el[element]); } }); return this._$el = _$el; }, // Validate data. validate(data) { if (!data) { return this.messages.unavailable; } if (!data.length) { return this.messages.empty; } return true; }, // Errors messages: { empty: { message: `The search list is empty.`, name: 'empty', type: 'warn' }, engine: { message: 'The search feature requires an engine.', name: 'engine', type: 'warn' }, noResults: { message: `Your search returned no results.`, name: 'no results', type: 'info' }, unavailable: { message: `The search list is unavailable.`, name: 'unavailable', type: 'warn' }, }, // Metadata event default method. metadataCallback: function(event, search) { return {event, search}; }, // Elements' metadata set metadata(metadata) { let existing = Object.create(this._metadata || null); delete this._metadata; this._metadata = Object.assign(existing, metadata); }, get metadata() { const defaults = { input: { placeholder: 'Search', cancel: this.metadataCallback, change: this.metadataCallback, focus: this.metadataCallback, }, results: { instructions: 'Choose from the list.' } }; return this._metadata = Object.assign(defaults, this._metadata || null); }, // Templates. template: { error: ({attr}) => { return `<div class="${attr.noResults}"> We found no results matching your search. </div>`; }, form: ({icon, attr, metadata: {input: {placeholder}}}) => { return `<div class="${attr.container}"> <div class="${attr.searchFieldContainer}"> <input class="${attr.input}" placeholder="${placeholder}" type="text" /> <div class="${attr.menu}"> <i class="${attr.submit} fa ${icon.search}"></i> </div> </div> </div>`; }, header: (title) => { return `<h1>${title}</h1>`; }, // @TODO Compute `id` & `name` from `this.label` & `this.identifier`. list: ({id, name}, {attr}) => { return `<li class="${attr.result}" data-id="${id}" data-name="${name}"> ${name} </li>`; }, noResults: ({attr, messages}) => { return `<div class="${attr.noResults}">${messages.noResults.message}</div>`; }, result: ({attr}) => { return `<div class="${attr.hidden} ${attr.searchResults}"></div>`; }, results: ({attr, metadata}) => { const {className, instructions, title} = metadata.results; return `<div class="${attr.results} ${attr.hidden}"> ${title ? `<div class="${attr.title}">${title}</div>` : ''} ${instructions ? `<div class="${attr.instructions}">${instructions}</div>` : ''} <ul class="${className} ${attr.resultsList}"></ul> </div>`; } } }; export default abstraction;