// TODO - factor out data munging into separate object
/** @module controller */
var loadJSON = require("./utilities/ajax").loadJSON,
escapeRegExp = require("./utilities/regexp").escapeRegExp;
module.exports = {
Controller: Controller,
AJAXController: AJAXController,
};
/**
* @callback searchCallback
* @param {Object[]} data - An array of objects that represent possible matches to data. The data are mapped over a formatter to provide a consistent interface.
*/
function Controller(formatter, view, options) {
this.format = formatter;
this.view = view;
this.database = this.munge(options.data);
this.max = options.max;
}
/**
* @interface
* @param {function} formatter - Munges data
* @param {View} view
* @param {object} options
* @prop {function} format - Munges data
* @prop {View} view
* @prop {number} max - Maximum number of matches to pass to the View.
*/
function AbstractController(formatter, view, options) {
this.format = formatter;
this.view = view;
this.max = options.max;
}
/**
* @abstract
*/
AbstractController.prototype.search = function search() {
throw new Error("NYI");
};
/**
* Transforms data to conform to config.
* @method
* @param {string} qry
* @param {searchCallback} callback
*/
AbstractController.prototype.munge = function(data) {
return data.map(this.format);
};
/**
* @constructor
* @prop {object[]} database - All possible choices for a given mention.
*/
function Controller(formatter, view, options) {
AbstractController.call(this, formatter, view, options);
this.database = this.munge(options.data);
}
Controller.prototype = Object.create(AbstractController.prototype);
Controller.prototype.search = function search(qry, callback) {
var qryRE = new RegExp(escapeRegExp(qry), "i"),
data;
data = this.database.filter(function(d) {
return qryRE.test(d.value);
}).sort(function(d1, d2) {
return d1.value.indexOf(qry) - d2.value.indexOf(qry);
});
this.view.render(data.slice(0, this.max));
if (callback) callback();
};
/**
* @constructor
* @augments Controller
* @prop {string} path - The path from which to request data.
* @prop {string} queryParameter - The name of the paramter in the request to Controller~path
* @prop {Object} _latestCall - Cached ajax call. Aborted if a new search is made.
*/
function AJAXController(formatter, view, options) {
AbstractController.call(this, formatter, view, options);
this.path = options.path;
this.queryParameter = options.queryParameter;
this._latestCall = null;
}
AJAXController.prototype = Object.create(AbstractController.prototype);
/**
* @method
* @param {String} qry
* @param {searchCallback} callback
*/
AJAXController.prototype.search = function search(qry, callback) {
if (this._latestCall) this._latestCall.abort(); // caches ajax calls so we can cancel them as the input is updated
var qryString = this.path +
"?" + this.queryParameter +
"=" + encodeURIComponent(qry);
this._latestCall = loadJSON(qryString, success.bind(this), ajaxError);
function success(data) {
this._callback(data);
if (callback) callback();
}
};
/**
* Munges the callback data
* @method
* @private
* @param {array} data
*/
AJAXController.prototype._callback = function(data) {
data = this.munge(data).slice(0, this.max);
this.view.render(data);
};
function ajaxError(error) {
console.log("Loading json errored! Likely due to aborted request, but there's the error: ", error);
}