Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
15403 manish.sha 1
/*
2
 * typeahead.js
3
 * https://github.com/twitter/typeahead.js
4
 * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5
 */
6
 
7
(function(root) {
8
  'use strict';
9
 
10
  var old, keys;
11
 
12
  old = root.Bloodhound;
13
  keys = { data: 'data', protocol: 'protocol', thumbprint: 'thumbprint' };
14
 
15
  // add Bloodhoud to global context
16
  root.Bloodhound = Bloodhound;
17
 
18
  // constructor
19
  // -----------
20
 
21
  function Bloodhound(o) {
22
    if (!o || (!o.local && !o.prefetch && !o.remote)) {
23
      $.error('one of local, prefetch, or remote is required');
24
    }
25
 
26
    this.limit = o.limit || 5;
27
    this.sorter = getSorter(o.sorter);
28
    this.dupDetector = o.dupDetector || ignoreDuplicates;
29
 
30
    this.local = oParser.local(o);
31
    this.prefetch = oParser.prefetch(o);
32
    this.remote = oParser.remote(o);
33
 
34
    this.cacheKey = this.prefetch ?
35
      (this.prefetch.cacheKey || this.prefetch.url) : null;
36
 
37
    // the backing data structure used for fast pattern matching
38
    this.index = new SearchIndex({
39
      datumTokenizer: o.datumTokenizer,
40
      queryTokenizer: o.queryTokenizer
41
    });
42
 
43
    // only initialize storage if there's a cacheKey otherwise
44
    // loading from storage on subsequent page loads is impossible
45
    this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
46
  }
47
 
48
  // static methods
49
  // --------------
50
 
51
  Bloodhound.noConflict = function noConflict() {
52
    root.Bloodhound = old;
53
    return Bloodhound;
54
  };
55
 
56
  Bloodhound.tokenizers = tokenizers;
57
 
58
  // instance methods
59
  // ----------------
60
 
61
  _.mixin(Bloodhound.prototype, {
62
 
63
    // ### private
64
 
65
    _loadPrefetch: function loadPrefetch(o) {
66
      var that = this, serialized, deferred;
67
 
68
      if (serialized = this._readFromStorage(o.thumbprint)) {
69
        this.index.bootstrap(serialized);
70
        deferred = $.Deferred().resolve();
71
      }
72
 
73
      else {
74
        deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse);
75
      }
76
 
77
      return deferred;
78
 
79
      function handlePrefetchResponse(resp) {
80
        // clear to mirror the behavior of bootstrapping
81
        that.clear();
82
        that.add(o.filter ? o.filter(resp) : resp);
83
 
84
        that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
85
      }
86
    },
87
 
88
    _getFromRemote: function getFromRemote(query, cb) {
89
      var that = this, url, uriEncodedQuery;
90
 
91
      if (!this.transport) { return; }
92
 
93
      query = query || '';
94
      uriEncodedQuery = encodeURIComponent(query);
95
 
96
      url = this.remote.replace ?
97
        this.remote.replace(this.remote.url, query) :
98
        this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
99
 
100
      return this.transport.get(url, this.remote.ajax, handleRemoteResponse);
101
 
102
      function handleRemoteResponse(err, resp) {
103
        err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);
104
      }
105
    },
106
 
107
    _cancelLastRemoteRequest: function cancelLastRemoteRequest() {
108
      // #149: prevents outdated rate-limited requests from being sent
109
      this.transport && this.transport.cancel();
110
    },
111
 
112
    _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
113
      if (this.storage) {
114
        this.storage.set(keys.data, data, ttl);
115
        this.storage.set(keys.protocol, location.protocol, ttl);
116
        this.storage.set(keys.thumbprint, thumbprint, ttl);
117
      }
118
    },
119
 
120
    _readFromStorage: function readFromStorage(thumbprint) {
121
      var stored = {}, isExpired;
122
 
123
      if (this.storage) {
124
        stored.data = this.storage.get(keys.data);
125
        stored.protocol = this.storage.get(keys.protocol);
126
        stored.thumbprint = this.storage.get(keys.thumbprint);
127
      }
128
      // the stored data is considered expired if the thumbprints
129
      // don't match or if the protocol it was originally stored under
130
      // has changed
131
      isExpired = stored.thumbprint !== thumbprint ||
132
        stored.protocol !== location.protocol;
133
 
134
      return stored.data && !isExpired ? stored.data : null;
135
    },
136
 
137
    _initialize: function initialize() {
138
      var that = this, local = this.local, deferred;
139
 
140
      deferred = this.prefetch ?
141
        this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
142
 
143
      // make sure local is added to the index after prefetch
144
      local && deferred.done(addLocalToIndex);
145
 
146
      this.transport = this.remote ? new Transport(this.remote) : null;
147
 
148
      return (this.initPromise = deferred.promise());
149
 
150
      function addLocalToIndex() {
151
        // local can be a function that returns an array of datums
152
        that.add(_.isFunction(local) ? local() : local);
153
      }
154
    },
155
 
156
    // ### public
157
 
158
    initialize: function initialize(force) {
159
      return !this.initPromise || force ? this._initialize() : this.initPromise;
160
    },
161
 
162
    add: function add(data) {
163
      this.index.add(data);
164
    },
165
 
166
    get: function get(query, cb) {
167
      var that = this, matches = [], cacheHit = false;
168
 
169
      matches = this.index.get(query);
170
      matches = this.sorter(matches).slice(0, this.limit);
171
 
172
      matches.length < this.limit ?
173
        (cacheHit = this._getFromRemote(query, returnRemoteMatches)) :
174
        this._cancelLastRemoteRequest();
175
 
176
      // if a cache hit occurred, skip rendering local matches
177
      // because the rendering of local/remote matches is already
178
      // in the event loop
179
      if (!cacheHit) {
180
        // only render if there are some local suggestions or we're
181
        // going to the network to backfill
182
        (matches.length > 0 || !this.transport) && cb && cb(matches);
183
      }
184
 
185
      function returnRemoteMatches(remoteMatches) {
186
        var matchesWithBackfill = matches.slice(0);
187
 
188
        _.each(remoteMatches, function(remoteMatch) {
189
          var isDuplicate;
190
 
191
          // checks for duplicates
192
          isDuplicate = _.some(matchesWithBackfill, function(match) {
193
            return that.dupDetector(remoteMatch, match);
194
          });
195
 
196
          !isDuplicate && matchesWithBackfill.push(remoteMatch);
197
 
198
          // if we're at the limit, we no longer need to process
199
          // the remote results and can break out of the each loop
200
          return matchesWithBackfill.length < that.limit;
201
        });
202
        cb && cb(that.sorter(matchesWithBackfill));
203
      }
204
    },
205
 
206
    clear: function clear() {
207
      this.index.reset();
208
    },
209
 
210
    clearPrefetchCache: function clearPrefetchCache() {
211
      this.storage && this.storage.clear();
212
    },
213
 
214
    clearRemoteCache: function clearRemoteCache() {
215
      this.transport && Transport.resetCache();
216
    },
217
 
218
    ttAdapter: function ttAdapter() { return _.bind(this.get, this); }
219
  });
220
 
221
  return Bloodhound;
222
 
223
  // helper functions
224
  // ----------------
225
 
226
  function getSorter(sortFn) {
227
    return _.isFunction(sortFn) ? sort : noSort;
228
 
229
    function sort(array) { return array.sort(sortFn); }
230
    function noSort(array) { return array; }
231
  }
232
 
233
  function ignoreDuplicates() { return false; }
234
})(this);