isomorphic-search
Version:
A configurable search interface.
725 lines (602 loc) • 22.7 kB
JavaScript
var Search =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/Users/luzmcosta/Documents/development/isomorphic-search/build";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports, __webpack_require__(1), __webpack_require__(2)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports !== "undefined") {
factory(exports, require('./abstraction'), require('./presentation'));
} else {
var mod = {
exports: {}
};
factory(mod.exports, global.abstraction, global.presentation);
global.control = mod.exports;
}
})(this, function (exports, _abstraction, _presentation) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _abstraction2 = _interopRequireDefault(_abstraction);
var _presentation2 = _interopRequireDefault(_presentation);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
/**
* Search agent's control.
*/
var control = {
constructor: function constructor() {
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// Configure.
this.abstraction = Object.assign(_abstraction2.default, options);
this.presentation = _presentation2.default;
// Run.
this.set(data);
// Enable chaining.
return this;
},
engine: function engine(_engine, data, options) {
// Notify if search engine has not been set.
if (!_engine) {
return this.abstraction.notify(this.abstraction.messages.engine);
}
// Instantiate.
if (typeof _engine === 'function') {
_engine = new _engine(data, options);
}
// Get search method.
if (_engine.search && typeof _engine.search === 'function') {
_engine = _engine.search.bind(_engine);
}
// Save instantiated engine to abstraction.
this.abstraction.search = _engine;
return _engine;
},
onSearch: function onSearch() {
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var search = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var engine = arguments[2];
var results = engine(search);
this.presentation.renderSearchResults(results, this.abstraction);
return results;
},
execute: function execute(abstraction, data, engine) {
var $el = abstraction.$el,
attr = abstraction.attr,
$input = $el.input,
search = $input.val(),
hiddenClass = attr.hidden,
eachResult = $el.result;
// Handle empty search value.
if (!search) {
// Show all possible results.
eachResult.removeClass(hiddenClass);
return search;
}
// Initiate search for user input.
this.onSearch(data, search, engine);
return search;
},
set: function set(data) {
var model = this.abstraction,
valid = model.validate(data);
if (valid !== true) {
return this.abstraction.notify(valid);
}
// Render form and bind events to it.
this.renderForm(data);
// Render results.
this.renderResults(model, data);
return data;
},
renderForm: function renderForm(data) {
var model = this.abstraction,
$parent = model.$el.parent,
form = model.template.form,
engine = this.engine(model.engine, data, model.options),
metadata = model.metadata,
callback = metadata && metadata.form && metadata.form.callback ? metadata.form.callback : this.execute.bind(this);
// Render form.
this.presentation.render($parent, form(model), { action: 'prepend' });
// Bind events to view.
this.presentation.bind(model, data, engine, callback);
return true;
},
renderResults: function renderResults(model, data) {
var _this = this;
var $el = model.$el,
metadata = model.metadata,
template = model.template,
callback = metadata && metadata.result ? metadata.result.callback : false;
// Render results view.
this.presentation.render($el.container, template.results(model), { action: 'append' });
// Get results list.
var resultsList = model.$el.resultsList;
// Render each potential result.
data.forEach(function (datum) {
// Render.
_this.presentation.render(resultsList, template.list(datum, model), { action: 'append' });
// Execute callback on this list item.
if (callback) {
callback(datum);
}
});
}
};
exports.default = control;
});
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.abstraction = mod.exports;
}
})(this, function (exports) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* Search module's abstraction
*/
var identifier = 'id';
var label = 'name';
var abstraction = {
// Set the identifying property among the objects in the list.
identifier: identifier,
// Set the labeling property among the objects in the list.
label: label,
// The notification system used when set-up fails. See control.set method.
notify: console ? console.log : function (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) {
var 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) {
var existing = Object.create(this._attr || null);
delete this._attr;
this._attr = Object.assign(existing, attr);
},
get attr() {
// @TODO Abstract.
var 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) {
var existing = Object.create(this._el || null);
delete this._el;
this._el = Object.assign(existing, element);
},
get el() {
var existing = this._el,
elements = this.attr,
_el = {};
Object.keys(elements).forEach(function (el) {
if (el === 'parent' || el === 'body') {
_el[el] = elements[el];
} else {
_el[el] = '.' + elements[el];
}
});
return this._el = _el;
},
// DOM Nodes
set $el(element) {
var existing = Object.create(this._$el || null);
delete this._$el;
this._$el = Object.assign(existing, element);
},
get $el() {
var existing = this._$el,
el = this.el,
_$el = {};
Object.keys(el).forEach(function (element) {
var 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: function 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 metadataCallback(event, search) {
return { event: event, search: search };
},
// Elements' metadata
set metadata(metadata) {
var existing = Object.create(this._metadata || null);
delete this._metadata;
this._metadata = Object.assign(existing, metadata);
},
get metadata() {
var 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: function error(_ref) {
var attr = _ref.attr;
return '<div class="' + attr.noResults + '">\n We found no results matching your search.\n </div>';
},
form: function form(_ref2) {
var icon = _ref2.icon,
attr = _ref2.attr,
placeholder = _ref2.metadata.input.placeholder;
return '<div class="' + attr.container + '">\n <div class="' + attr.searchFieldContainer + '">\n <input class="' + attr.input + '" placeholder="' + placeholder + '" type="text" />\n <div class="' + attr.menu + '">\n <i class="' + attr.submit + ' fa ' + icon.search + '"></i>\n </div>\n </div>\n </div>';
},
header: function header(title) {
return '<h1>' + title + '</h1>';
},
// @TODO Compute `id` & `name` from `this.label` & `this.identifier`.
list: function list(_ref3, _ref4) {
var id = _ref3.id,
name = _ref3.name;
var attr = _ref4.attr;
return '<li class="' + attr.result + '" data-id="' + id + '" data-name="' + name + '">\n ' + name + '\n </li>';
},
noResults: function noResults(_ref5) {
var attr = _ref5.attr,
messages = _ref5.messages;
return '<div class="' + attr.noResults + '">' + messages.noResults.message + '</div>';
},
result: function result(_ref6) {
var attr = _ref6.attr;
return '<div class="' + attr.hidden + ' ' + attr.searchResults + '"></div>';
},
results: function results(_ref7) {
var attr = _ref7.attr,
metadata = _ref7.metadata;
var _metadata$results = metadata.results,
className = _metadata$results.className,
instructions = _metadata$results.instructions,
title = _metadata$results.title;
return '<div class="' + attr.results + ' ' + attr.hidden + '">\n ' + (title ? '<div class="' + attr.title + '">' + title + '</div>' : '') + '\n ' + (instructions ? '<div class="' + attr.instructions + '">' + instructions + '</div>' : '') + '\n <ul class="' + className + ' ' + attr.resultsList + '"></ul>\n </div>';
}
}
};
exports.default = abstraction;
});
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.presentation = mod.exports;
}
})(this, function (exports) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* Client-side
*/
var presentation = {
insertAt: function insertAt($container, action, index, content) {
var siblings = $container.children();
if (siblings.length) {
return siblings.eq(index).after(content);
}
return $container[action](content);
},
render: function render($container, content, _ref) {
var _ref$action = _ref.action,
action = _ref$action === undefined ? 'append' : _ref$action,
_ref$index = _ref.index,
index = _ref$index === undefined ? false : _ref$index;
if (!index && index !== 0) {
$container[action](content);
} else {
this.insertAt($container, content, { action: action, index: index });
}
return;
},
renderSearchResults: function renderSearchResults(results, abstraction) {
// Reset view.
abstraction.$el.noResults.remove();
// Get elements.
var $el = abstraction.$el,
el = abstraction.el,
attr = abstraction.attr,
identifier = abstraction.identifier,
template = abstraction.template,
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(function (result) {
// Display matching search result.
$results.find(li + '[data-' + identifier + '="' + result[identifier] + '"]').removeClass(hiddenClass);
});
return results;
},
search: {
// Cancel search.
cancel: function cancel(event, _ref2) {
var attr = _ref2.attr,
$el = _ref2.$el,
icon = _ref2.icon,
metadata = _ref2.metadata;
var cancelClass = icon.cancel,
hiddenClass = attr.hidden,
iconClasses = cancelClass + ' ' + icon.search,
input = metadata.input,
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: function change(event, abstraction, data, engine, searchCallback) {
var input = abstraction.metadata.input;
// Execute search.
var 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: function focus(event, abstraction) {
var $el = abstraction.$el,
attr = abstraction.attr,
icon = abstraction.icon,
template = abstraction.template,
metadata = abstraction.metadata,
input = metadata.input,
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;
}
},
bind: function bind(abstraction, data, engine, onChangeCallback) {
var _this = this;
var attr = abstraction.attr,
$el = abstraction.$el,
icon = abstraction.icon;
// Set events on search input.
$el.input.off().on('focus', function (event) {
_this.search.focus(event, abstraction);
}).on('keyup, paste, input', function (event) {
_this.search.change(event, abstraction, data, engine, onChangeCallback);
});
$el.menu.off().
// Set events on search icon.
on('click', '.' + icon.search, function (event) {
_this.search.focus(event, abstraction);
}).
// Set events on cancel icon.
on('click', '.' + icon.cancel, function (event) {
_this.search.cancel(event, abstraction);
});
}
};
exports.default = presentation;
});
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [module, __webpack_require__(0)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports !== "undefined") {
factory(module, require('./agent/control'));
} else {
var mod = {
exports: {}
};
factory(mod, global.control);
global.searchBrowser = mod.exports;
}
})(this, function (module, _control) {
'use strict';
var _control2 = _interopRequireDefault(_control);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
module.exports = _control2.default;
});
/***/ })
/******/ ]);