UNPKG

alpaca

Version:

Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide

235 lines (173 loc) 6.84 kB
/* * typeahead.js * https://github.com/twitter/typeahead.js * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT */ (function(root) { 'use strict'; var old, keys; old = root.Bloodhound; keys = { data: 'data', protocol: 'protocol', thumbprint: 'thumbprint' }; // add Bloodhoud to global context root.Bloodhound = Bloodhound; // constructor // ----------- function Bloodhound(o) { if (!o || (!o.local && !o.prefetch && !o.remote)) { $.error('one of local, prefetch, or remote is required'); } this.limit = o.limit || 5; this.sorter = getSorter(o.sorter); this.dupDetector = o.dupDetector || ignoreDuplicates; this.local = oParser.local(o); this.prefetch = oParser.prefetch(o); this.remote = oParser.remote(o); this.cacheKey = this.prefetch ? (this.prefetch.cacheKey || this.prefetch.url) : null; // the backing data structure used for fast pattern matching this.index = new SearchIndex({ datumTokenizer: o.datumTokenizer, queryTokenizer: o.queryTokenizer }); // only initialize storage if there's a cacheKey otherwise // loading from storage on subsequent page loads is impossible this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; } // static methods // -------------- Bloodhound.noConflict = function noConflict() { root.Bloodhound = old; return Bloodhound; }; Bloodhound.tokenizers = tokenizers; // instance methods // ---------------- _.mixin(Bloodhound.prototype, { // ### private _loadPrefetch: function loadPrefetch(o) { var that = this, serialized, deferred; if (serialized = this._readFromStorage(o.thumbprint)) { this.index.bootstrap(serialized); deferred = $.Deferred().resolve(); } else { deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); } return deferred; function handlePrefetchResponse(resp) { // clear to mirror the behavior of bootstrapping that.clear(); that.add(o.filter ? o.filter(resp) : resp); that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); } }, _getFromRemote: function getFromRemote(query, cb) { var that = this, url, uriEncodedQuery; if (!this.transport) { return; } query = query || ''; uriEncodedQuery = encodeURIComponent(query); url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); return this.transport.get(url, this.remote.ajax, handleRemoteResponse); function handleRemoteResponse(err, resp) { err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); } }, _cancelLastRemoteRequest: function cancelLastRemoteRequest() { // #149: prevents outdated rate-limited requests from being sent this.transport && this.transport.cancel(); }, _saveToStorage: function saveToStorage(data, thumbprint, ttl) { if (this.storage) { this.storage.set(keys.data, data, ttl); this.storage.set(keys.protocol, location.protocol, ttl); this.storage.set(keys.thumbprint, thumbprint, ttl); } }, _readFromStorage: function readFromStorage(thumbprint) { var stored = {}, isExpired; if (this.storage) { stored.data = this.storage.get(keys.data); stored.protocol = this.storage.get(keys.protocol); stored.thumbprint = this.storage.get(keys.thumbprint); } // the stored data is considered expired if the thumbprints // don't match or if the protocol it was originally stored under // has changed isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; return stored.data && !isExpired ? stored.data : null; }, _initialize: function initialize() { var that = this, local = this.local, deferred; deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); // make sure local is added to the index after prefetch local && deferred.done(addLocalToIndex); this.transport = this.remote ? new Transport(this.remote) : null; return (this.initPromise = deferred.promise()); function addLocalToIndex() { // local can be a function that returns an array of datums that.add(_.isFunction(local) ? local() : local); } }, // ### public initialize: function initialize(force) { return !this.initPromise || force ? this._initialize() : this.initPromise; }, add: function add(data) { this.index.add(data); }, get: function get(query, cb) { var that = this, matches = [], cacheHit = false; matches = this.index.get(query); matches = this.sorter(matches).slice(0, this.limit); matches.length < this.limit ? (cacheHit = this._getFromRemote(query, returnRemoteMatches)) : this._cancelLastRemoteRequest(); // if a cache hit occurred, skip rendering local matches // because the rendering of local/remote matches is already // in the event loop if (!cacheHit) { // only render if there are some local suggestions or we're // going to the network to backfill (matches.length > 0 || !this.transport) && cb && cb(matches); } function returnRemoteMatches(remoteMatches) { var matchesWithBackfill = matches.slice(0); _.each(remoteMatches, function(remoteMatch) { var isDuplicate; // checks for duplicates isDuplicate = _.some(matchesWithBackfill, function(match) { return that.dupDetector(remoteMatch, match); }); !isDuplicate && matchesWithBackfill.push(remoteMatch); // if we're at the limit, we no longer need to process // the remote results and can break out of the each loop return matchesWithBackfill.length < that.limit; }); cb && cb(that.sorter(matchesWithBackfill)); } }, clear: function clear() { this.index.reset(); }, clearPrefetchCache: function clearPrefetchCache() { this.storage && this.storage.clear(); }, clearRemoteCache: function clearRemoteCache() { this.transport && Transport.resetCache(); }, ttAdapter: function ttAdapter() { return _.bind(this.get, this); } }); return Bloodhound; // helper functions // ---------------- function getSorter(sortFn) { return _.isFunction(sortFn) ? sort : noSort; function sort(array) { return array.sort(sortFn); } function noSort(array) { return array; } } function ignoreDuplicates() { return false; } })(this);