typeahead-rails 0.9.3.4 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/typeahead/rails/version.rb +1 -1
- data/vendor/assets/javascripts/typeahead.js +1227 -708
- data/vendor/assets/javascripts/typeahead.min.js +3 -3
- metadata +3 -3
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* typeahead.js 0.
|
|
3
|
-
* https://github.com/twitter/typeahead
|
|
2
|
+
* typeahead.js 0.10.1
|
|
3
|
+
* https://github.com/twitter/typeahead.js
|
|
4
4
|
* Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
(function($) {
|
|
8
|
-
var
|
|
9
|
-
var utils = {
|
|
8
|
+
var _ = {
|
|
10
9
|
isMsie: function() {
|
|
11
|
-
|
|
12
|
-
return match ? parseInt(match[2], 10) : false;
|
|
10
|
+
return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
|
|
13
11
|
},
|
|
14
12
|
isBlankString: function(str) {
|
|
15
13
|
return !str || /^\s*$/.test(str);
|
|
@@ -30,21 +28,12 @@
|
|
|
30
28
|
return typeof obj === "undefined";
|
|
31
29
|
},
|
|
32
30
|
bind: $.proxy,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
each: function(collection, cb) {
|
|
32
|
+
$.each(collection, reverseArgs);
|
|
33
|
+
function reverseArgs(index, value) {
|
|
34
|
+
return cb(value, index);
|
|
37
35
|
}
|
|
38
36
|
},
|
|
39
|
-
indexOf: function(haystack, needle) {
|
|
40
|
-
for (var i = 0; i < haystack.length; i++) {
|
|
41
|
-
if (haystack[i] === needle) {
|
|
42
|
-
return i;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return -1;
|
|
46
|
-
},
|
|
47
|
-
each: $.each,
|
|
48
37
|
map: $.map,
|
|
49
38
|
filter: $.grep,
|
|
50
39
|
every: function(obj, test) {
|
|
@@ -78,6 +67,12 @@
|
|
|
78
67
|
return counter++;
|
|
79
68
|
};
|
|
80
69
|
}(),
|
|
70
|
+
templatify: function templatify(obj) {
|
|
71
|
+
return $.isFunction(obj) ? obj : template;
|
|
72
|
+
function template() {
|
|
73
|
+
return String(obj);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
81
76
|
defer: function(fn) {
|
|
82
77
|
setTimeout(fn, 0);
|
|
83
78
|
},
|
|
@@ -123,69 +118,69 @@
|
|
|
123
118
|
return result;
|
|
124
119
|
};
|
|
125
120
|
},
|
|
126
|
-
tokenizeQuery: function(str) {
|
|
127
|
-
return $.trim(str).toLowerCase().split(/[\s]+/);
|
|
128
|
-
},
|
|
129
|
-
tokenizeText: function(str) {
|
|
130
|
-
return $.trim(str).toLowerCase().split(/[\s\-_]+/);
|
|
131
|
-
},
|
|
132
|
-
getProtocol: function() {
|
|
133
|
-
return location.protocol;
|
|
134
|
-
},
|
|
135
121
|
noop: function() {}
|
|
136
122
|
};
|
|
137
|
-
var
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
123
|
+
var VERSION = "0.10.1";
|
|
124
|
+
var LruCache = function(root, undefined) {
|
|
125
|
+
function LruCache(maxSize) {
|
|
126
|
+
this.maxSize = maxSize || 100;
|
|
127
|
+
this.size = 0;
|
|
128
|
+
this.hash = {};
|
|
129
|
+
this.list = new List();
|
|
130
|
+
}
|
|
131
|
+
_.mixin(LruCache.prototype, {
|
|
132
|
+
set: function set(key, val) {
|
|
133
|
+
var tailItem = this.list.tail, node;
|
|
134
|
+
if (this.size >= this.maxSize) {
|
|
135
|
+
this.list.remove(tailItem);
|
|
136
|
+
delete this.hash[tailItem.key];
|
|
144
137
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
if (node = this.hash[key]) {
|
|
139
|
+
node.val = val;
|
|
140
|
+
this.list.moveToFront(node);
|
|
141
|
+
} else {
|
|
142
|
+
node = new Node(key, val);
|
|
143
|
+
this.list.add(node);
|
|
144
|
+
this.hash[key] = node;
|
|
145
|
+
this.size++;
|
|
150
146
|
}
|
|
151
|
-
return this;
|
|
152
147
|
},
|
|
153
|
-
|
|
154
|
-
var
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
events = events.split(eventSplitter);
|
|
159
|
-
while (event = events.shift()) {
|
|
160
|
-
if (callbacks = this._callbacks[event]) {
|
|
161
|
-
for (var i = 0; i < callbacks.length; i += 1) {
|
|
162
|
-
callbacks[i].call(this, {
|
|
163
|
-
type: event,
|
|
164
|
-
data: data
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
148
|
+
get: function get(key) {
|
|
149
|
+
var node = this.hash[key];
|
|
150
|
+
if (node) {
|
|
151
|
+
this.list.moveToFront(node);
|
|
152
|
+
return node.val;
|
|
168
153
|
}
|
|
169
|
-
return this;
|
|
170
154
|
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
var namespace = "typeahead:";
|
|
175
|
-
function EventBus(o) {
|
|
176
|
-
if (!o || !o.el) {
|
|
177
|
-
$.error("EventBus initialized without el");
|
|
178
|
-
}
|
|
179
|
-
this.$el = $(o.el);
|
|
155
|
+
});
|
|
156
|
+
function List() {
|
|
157
|
+
this.head = this.tail = null;
|
|
180
158
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
159
|
+
_.mixin(List.prototype, {
|
|
160
|
+
add: function add(node) {
|
|
161
|
+
if (this.head) {
|
|
162
|
+
node.next = this.head;
|
|
163
|
+
this.head.prev = node;
|
|
164
|
+
}
|
|
165
|
+
this.head = node;
|
|
166
|
+
this.tail = this.tail || node;
|
|
167
|
+
},
|
|
168
|
+
remove: function remove(node) {
|
|
169
|
+
node.prev ? node.prev.next = node.next : this.head = node.next;
|
|
170
|
+
node.next ? node.next.prev = node.prev : this.tail = node.prev;
|
|
171
|
+
},
|
|
172
|
+
moveToFront: function(node) {
|
|
173
|
+
this.remove(node);
|
|
174
|
+
this.add(node);
|
|
185
175
|
}
|
|
186
176
|
});
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
function Node(key, val) {
|
|
178
|
+
this.key = key;
|
|
179
|
+
this.val = val;
|
|
180
|
+
this.prev = this.next = null;
|
|
181
|
+
}
|
|
182
|
+
return LruCache;
|
|
183
|
+
}(this);
|
|
189
184
|
var PersistentStorage = function() {
|
|
190
185
|
var ls, methods;
|
|
191
186
|
try {
|
|
@@ -215,7 +210,7 @@
|
|
|
215
210
|
return decode(ls.getItem(this._prefix(key)));
|
|
216
211
|
},
|
|
217
212
|
set: function(key, val, ttl) {
|
|
218
|
-
if (
|
|
213
|
+
if (_.isNumber(ttl)) {
|
|
219
214
|
ls.setItem(this._ttlKey(key), encode(now() + ttl));
|
|
220
215
|
} else {
|
|
221
216
|
ls.removeItem(this._ttlKey(key));
|
|
@@ -241,416 +236,784 @@
|
|
|
241
236
|
},
|
|
242
237
|
isExpired: function(key) {
|
|
243
238
|
var ttl = decode(ls.getItem(this._ttlKey(key)));
|
|
244
|
-
return
|
|
239
|
+
return _.isNumber(ttl) && now() > ttl ? true : false;
|
|
245
240
|
}
|
|
246
241
|
};
|
|
247
242
|
} else {
|
|
248
243
|
methods = {
|
|
249
|
-
get:
|
|
250
|
-
set:
|
|
251
|
-
remove:
|
|
252
|
-
clear:
|
|
253
|
-
isExpired:
|
|
244
|
+
get: _.noop,
|
|
245
|
+
set: _.noop,
|
|
246
|
+
remove: _.noop,
|
|
247
|
+
clear: _.noop,
|
|
248
|
+
isExpired: _.noop
|
|
254
249
|
};
|
|
255
250
|
}
|
|
256
|
-
|
|
251
|
+
_.mixin(PersistentStorage.prototype, methods);
|
|
257
252
|
return PersistentStorage;
|
|
258
253
|
function now() {
|
|
259
254
|
return new Date().getTime();
|
|
260
255
|
}
|
|
261
256
|
function encode(val) {
|
|
262
|
-
return JSON.stringify(
|
|
257
|
+
return JSON.stringify(_.isUndefined(val) ? null : val);
|
|
263
258
|
}
|
|
264
259
|
function decode(val) {
|
|
265
260
|
return JSON.parse(val);
|
|
266
261
|
}
|
|
267
262
|
}();
|
|
268
|
-
var RequestCache = function() {
|
|
269
|
-
function RequestCache(o) {
|
|
270
|
-
utils.bindAll(this);
|
|
271
|
-
o = o || {};
|
|
272
|
-
this.sizeLimit = o.sizeLimit || 10;
|
|
273
|
-
this.cache = {};
|
|
274
|
-
this.cachedKeysByAge = [];
|
|
275
|
-
}
|
|
276
|
-
utils.mixin(RequestCache.prototype, {
|
|
277
|
-
get: function(url) {
|
|
278
|
-
return this.cache[url];
|
|
279
|
-
},
|
|
280
|
-
set: function(url, resp) {
|
|
281
|
-
var requestToEvict;
|
|
282
|
-
if (this.cachedKeysByAge.length === this.sizeLimit) {
|
|
283
|
-
requestToEvict = this.cachedKeysByAge.shift();
|
|
284
|
-
delete this.cache[requestToEvict];
|
|
285
|
-
}
|
|
286
|
-
this.cache[url] = resp;
|
|
287
|
-
this.cachedKeysByAge.push(url);
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
return RequestCache;
|
|
291
|
-
}();
|
|
292
263
|
var Transport = function() {
|
|
293
|
-
var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache;
|
|
264
|
+
var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10);
|
|
294
265
|
function Transport(o) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
} : o;
|
|
299
|
-
requestCache = requestCache || new RequestCache();
|
|
300
|
-
maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6;
|
|
301
|
-
this.url = o.url;
|
|
302
|
-
this.wildcard = o.wildcard || "%QUERY";
|
|
303
|
-
this.filter = o.filter;
|
|
304
|
-
this.replace = o.replace;
|
|
305
|
-
this.ajaxSettings = {
|
|
306
|
-
type: "get",
|
|
307
|
-
cache: o.cache,
|
|
308
|
-
timeout: o.timeout,
|
|
309
|
-
dataType: o.dataType || "json",
|
|
310
|
-
beforeSend: o.beforeSend
|
|
311
|
-
};
|
|
312
|
-
this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300);
|
|
266
|
+
o = o || {};
|
|
267
|
+
this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax;
|
|
268
|
+
this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get;
|
|
313
269
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
270
|
+
Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
|
|
271
|
+
maxPendingRequests = num;
|
|
272
|
+
};
|
|
273
|
+
Transport.resetCache = function clearCache() {
|
|
274
|
+
requestCache = new LruCache(10);
|
|
275
|
+
};
|
|
276
|
+
_.mixin(Transport.prototype, {
|
|
277
|
+
_get: function(url, o, cb) {
|
|
278
|
+
var that = this, jqXhr;
|
|
279
|
+
if (jqXhr = pendingRequests[url]) {
|
|
280
|
+
jqXhr.done(done);
|
|
281
|
+
} else if (pendingRequestsCount < maxPendingRequests) {
|
|
282
|
+
pendingRequestsCount++;
|
|
283
|
+
pendingRequests[url] = this._send(url, o).done(done).always(always);
|
|
319
284
|
} else {
|
|
320
285
|
this.onDeckRequestArgs = [].slice.call(arguments, 0);
|
|
321
286
|
}
|
|
322
287
|
function done(resp) {
|
|
323
|
-
|
|
324
|
-
cb && cb(data);
|
|
288
|
+
cb && cb(resp);
|
|
325
289
|
requestCache.set(url, resp);
|
|
326
290
|
}
|
|
327
|
-
},
|
|
328
|
-
_sendRequest: function(url) {
|
|
329
|
-
var that = this, jqXhr = pendingRequests[url];
|
|
330
|
-
if (!jqXhr) {
|
|
331
|
-
incrementPendingRequests();
|
|
332
|
-
jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always);
|
|
333
|
-
}
|
|
334
|
-
return jqXhr;
|
|
335
291
|
function always() {
|
|
336
|
-
|
|
337
|
-
pendingRequests[url]
|
|
292
|
+
pendingRequestsCount--;
|
|
293
|
+
delete pendingRequests[url];
|
|
338
294
|
if (that.onDeckRequestArgs) {
|
|
339
295
|
that._get.apply(that, that.onDeckRequestArgs);
|
|
340
296
|
that.onDeckRequestArgs = null;
|
|
341
297
|
}
|
|
342
298
|
}
|
|
343
299
|
},
|
|
344
|
-
get: function(
|
|
345
|
-
var that = this,
|
|
346
|
-
|
|
347
|
-
|
|
300
|
+
get: function(url, o, cb) {
|
|
301
|
+
var that = this, resp;
|
|
302
|
+
if (_.isFunction(o)) {
|
|
303
|
+
cb = o;
|
|
304
|
+
o = {};
|
|
305
|
+
}
|
|
348
306
|
if (resp = requestCache.get(url)) {
|
|
349
|
-
|
|
350
|
-
cb
|
|
307
|
+
_.defer(function() {
|
|
308
|
+
cb && cb(resp);
|
|
351
309
|
});
|
|
352
310
|
} else {
|
|
353
|
-
this._get(url, cb);
|
|
311
|
+
this._get(url, o, cb);
|
|
354
312
|
}
|
|
355
313
|
return !!resp;
|
|
356
314
|
}
|
|
357
315
|
});
|
|
358
316
|
return Transport;
|
|
359
|
-
function
|
|
360
|
-
|
|
317
|
+
function callbackToDeferred(fn) {
|
|
318
|
+
return function customSendWrapper(url, o) {
|
|
319
|
+
var deferred = $.Deferred();
|
|
320
|
+
fn(url, o, onSuccess, onError);
|
|
321
|
+
return deferred;
|
|
322
|
+
function onSuccess(resp) {
|
|
323
|
+
_.defer(function() {
|
|
324
|
+
deferred.resolve(resp);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
function onError(err) {
|
|
328
|
+
_.defer(function() {
|
|
329
|
+
deferred.reject(err);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
};
|
|
361
333
|
}
|
|
362
|
-
|
|
363
|
-
|
|
334
|
+
}();
|
|
335
|
+
var SearchIndex = function() {
|
|
336
|
+
function SearchIndex(o) {
|
|
337
|
+
o = o || {};
|
|
338
|
+
if (!o.datumTokenizer || !o.queryTokenizer) {
|
|
339
|
+
$.error("datumTokenizer and queryTokenizer are both required");
|
|
340
|
+
}
|
|
341
|
+
this.datumTokenizer = o.datumTokenizer;
|
|
342
|
+
this.queryTokenizer = o.queryTokenizer;
|
|
343
|
+
this.datums = [];
|
|
344
|
+
this.trie = newNode();
|
|
364
345
|
}
|
|
365
|
-
|
|
366
|
-
|
|
346
|
+
_.mixin(SearchIndex.prototype, {
|
|
347
|
+
bootstrap: function bootstrap(o) {
|
|
348
|
+
this.datums = o.datums;
|
|
349
|
+
this.trie = o.trie;
|
|
350
|
+
},
|
|
351
|
+
add: function(data) {
|
|
352
|
+
var that = this;
|
|
353
|
+
data = _.isArray(data) ? data : [ data ];
|
|
354
|
+
_.each(data, function(datum) {
|
|
355
|
+
var id, tokens;
|
|
356
|
+
id = that.datums.push(datum) - 1;
|
|
357
|
+
tokens = normalizeTokens(that.datumTokenizer(datum));
|
|
358
|
+
_.each(tokens, function(token) {
|
|
359
|
+
var node, chars, ch, ids;
|
|
360
|
+
node = that.trie;
|
|
361
|
+
chars = token.split("");
|
|
362
|
+
while (ch = chars.shift()) {
|
|
363
|
+
node = node.children[ch] || (node.children[ch] = newNode());
|
|
364
|
+
node.ids.push(id);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
},
|
|
369
|
+
get: function get(query) {
|
|
370
|
+
var that = this, tokens, matches;
|
|
371
|
+
tokens = normalizeTokens(this.queryTokenizer(query));
|
|
372
|
+
_.each(tokens, function(token) {
|
|
373
|
+
var node, chars, ch, ids;
|
|
374
|
+
if (matches && matches.length === 0) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
node = that.trie;
|
|
378
|
+
chars = token.split("");
|
|
379
|
+
while (node && (ch = chars.shift())) {
|
|
380
|
+
node = node.children[ch];
|
|
381
|
+
}
|
|
382
|
+
if (node && chars.length === 0) {
|
|
383
|
+
ids = node.ids.slice(0);
|
|
384
|
+
matches = matches ? getIntersection(matches, ids) : ids;
|
|
385
|
+
} else {
|
|
386
|
+
matches = [];
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return matches ? _.map(unique(matches), function(id) {
|
|
391
|
+
return that.datums[id];
|
|
392
|
+
}) : [];
|
|
393
|
+
},
|
|
394
|
+
serialize: function serialize() {
|
|
395
|
+
return {
|
|
396
|
+
datums: this.datums,
|
|
397
|
+
trie: this.trie
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
return SearchIndex;
|
|
402
|
+
function normalizeTokens(tokens) {
|
|
403
|
+
tokens = _.filter(tokens, function(token) {
|
|
404
|
+
return !!token;
|
|
405
|
+
});
|
|
406
|
+
tokens = _.map(tokens, function(token) {
|
|
407
|
+
return token.toLowerCase();
|
|
408
|
+
});
|
|
409
|
+
return tokens;
|
|
410
|
+
}
|
|
411
|
+
function newNode() {
|
|
412
|
+
return {
|
|
413
|
+
ids: [],
|
|
414
|
+
children: {}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function unique(array) {
|
|
418
|
+
var seen = {}, uniques = [];
|
|
419
|
+
for (var i = 0; i < array.length; i++) {
|
|
420
|
+
if (!seen[array[i]]) {
|
|
421
|
+
seen[array[i]] = true;
|
|
422
|
+
uniques.push(array[i]);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return uniques;
|
|
426
|
+
}
|
|
427
|
+
function getIntersection(arrayA, arrayB) {
|
|
428
|
+
var ai = 0, bi = 0, intersection = [];
|
|
429
|
+
arrayA = arrayA.sort(compare);
|
|
430
|
+
arrayB = arrayB.sort(compare);
|
|
431
|
+
while (ai < arrayA.length && bi < arrayB.length) {
|
|
432
|
+
if (arrayA[ai] < arrayB[bi]) {
|
|
433
|
+
ai++;
|
|
434
|
+
} else if (arrayA[ai] > arrayB[bi]) {
|
|
435
|
+
bi++;
|
|
436
|
+
} else {
|
|
437
|
+
intersection.push(arrayA[ai]);
|
|
438
|
+
ai++;
|
|
439
|
+
bi++;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return intersection;
|
|
443
|
+
function compare(a, b) {
|
|
444
|
+
return a - b;
|
|
445
|
+
}
|
|
367
446
|
}
|
|
368
447
|
}();
|
|
369
|
-
var
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
adjacencyList: "adjacencyList"
|
|
448
|
+
var oParser = function() {
|
|
449
|
+
return {
|
|
450
|
+
local: getLocal,
|
|
451
|
+
prefetch: getPrefetch,
|
|
452
|
+
remote: getRemote
|
|
375
453
|
};
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
if (
|
|
379
|
-
|
|
454
|
+
function getLocal(o) {
|
|
455
|
+
var local = o.local || null;
|
|
456
|
+
if (_.isFunction(local)) {
|
|
457
|
+
local = local.call(null);
|
|
458
|
+
}
|
|
459
|
+
return local;
|
|
460
|
+
}
|
|
461
|
+
function getPrefetch(o) {
|
|
462
|
+
var prefetch, defaults;
|
|
463
|
+
defaults = {
|
|
464
|
+
url: null,
|
|
465
|
+
thumbprint: "",
|
|
466
|
+
ttl: 24 * 60 * 60 * 1e3,
|
|
467
|
+
filter: null,
|
|
468
|
+
ajax: {}
|
|
469
|
+
};
|
|
470
|
+
if (prefetch = o.prefetch || null) {
|
|
471
|
+
prefetch = _.isString(prefetch) ? {
|
|
472
|
+
url: prefetch
|
|
473
|
+
} : prefetch;
|
|
474
|
+
prefetch = _.mixin(defaults, prefetch);
|
|
475
|
+
prefetch.thumbprint = VERSION + prefetch.thumbprint;
|
|
476
|
+
prefetch.ajax.type = prefetch.ajax.type || "GET";
|
|
477
|
+
prefetch.ajax.dataType = prefetch.ajax.dataType || "json";
|
|
478
|
+
!prefetch.url && $.error("prefetch requires url to be set");
|
|
479
|
+
}
|
|
480
|
+
return prefetch;
|
|
481
|
+
}
|
|
482
|
+
function getRemote(o) {
|
|
483
|
+
var remote, defaults;
|
|
484
|
+
defaults = {
|
|
485
|
+
url: null,
|
|
486
|
+
wildcard: "%QUERY",
|
|
487
|
+
replace: null,
|
|
488
|
+
rateLimitBy: "debounce",
|
|
489
|
+
rateLimitWait: 300,
|
|
490
|
+
send: null,
|
|
491
|
+
filter: null,
|
|
492
|
+
ajax: {}
|
|
493
|
+
};
|
|
494
|
+
if (remote = o.remote || null) {
|
|
495
|
+
remote = _.isString(remote) ? {
|
|
496
|
+
url: remote
|
|
497
|
+
} : remote;
|
|
498
|
+
remote = _.mixin(defaults, remote);
|
|
499
|
+
remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait);
|
|
500
|
+
remote.ajax.type = remote.ajax.type || "GET";
|
|
501
|
+
remote.ajax.dataType = remote.ajax.dataType || "json";
|
|
502
|
+
delete remote.rateLimitBy;
|
|
503
|
+
delete remote.rateLimitWait;
|
|
504
|
+
!remote.url && $.error("remote requires url to be set");
|
|
505
|
+
}
|
|
506
|
+
return remote;
|
|
507
|
+
function byDebounce(wait) {
|
|
508
|
+
return function(fn) {
|
|
509
|
+
return _.debounce(fn, wait);
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function byThrottle(wait) {
|
|
513
|
+
return function(fn) {
|
|
514
|
+
return _.throttle(fn, wait);
|
|
515
|
+
};
|
|
380
516
|
}
|
|
381
|
-
|
|
517
|
+
}
|
|
518
|
+
}();
|
|
519
|
+
var Bloodhound = window.Bloodhound = function() {
|
|
520
|
+
var keys;
|
|
521
|
+
keys = {
|
|
522
|
+
data: "data",
|
|
523
|
+
protocol: "protocol",
|
|
524
|
+
thumbprint: "thumbprint"
|
|
525
|
+
};
|
|
526
|
+
function Bloodhound(o) {
|
|
527
|
+
if (!o || !o.local && !o.prefetch && !o.remote) {
|
|
382
528
|
$.error("one of local, prefetch, or remote is required");
|
|
383
529
|
}
|
|
384
|
-
this.name = o.name || utils.getUniqueId();
|
|
385
530
|
this.limit = o.limit || 5;
|
|
386
|
-
this.
|
|
387
|
-
this.
|
|
388
|
-
this.
|
|
389
|
-
this.
|
|
390
|
-
this.
|
|
391
|
-
this.
|
|
392
|
-
this.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.storage =
|
|
531
|
+
this.sorter = getSorter(o.sorter);
|
|
532
|
+
this.dupDetector = o.dupDetector || ignoreDuplicates;
|
|
533
|
+
this.local = oParser.local(o);
|
|
534
|
+
this.prefetch = oParser.prefetch(o);
|
|
535
|
+
this.remote = oParser.remote(o);
|
|
536
|
+
this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null;
|
|
537
|
+
this.index = new SearchIndex({
|
|
538
|
+
datumTokenizer: o.datumTokenizer,
|
|
539
|
+
queryTokenizer: o.queryTokenizer
|
|
540
|
+
});
|
|
541
|
+
this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;
|
|
397
542
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
543
|
+
Bloodhound.tokenizers = {
|
|
544
|
+
whitespace: function whitespaceTokenizer(s) {
|
|
545
|
+
return s.split(/\s+/);
|
|
401
546
|
},
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
o = utils.isString(o) ? {
|
|
412
|
-
url: o
|
|
413
|
-
} : o;
|
|
414
|
-
o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3;
|
|
415
|
-
if (storedItemHash && storedAdjacencyList && !isExpired) {
|
|
416
|
-
this._mergeProcessedData({
|
|
417
|
-
itemHash: storedItemHash,
|
|
418
|
-
adjacencyList: storedAdjacencyList
|
|
419
|
-
});
|
|
547
|
+
nonword: function nonwordTokenizer(s) {
|
|
548
|
+
return s.split(/\W+/);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
_.mixin(Bloodhound.prototype, {
|
|
552
|
+
_loadPrefetch: function loadPrefetch(o) {
|
|
553
|
+
var that = this, serialized, deferred;
|
|
554
|
+
if (serialized = this._readFromStorage(o.thumbprint)) {
|
|
555
|
+
this.index.bootstrap(serialized);
|
|
420
556
|
deferred = $.Deferred().resolve();
|
|
421
557
|
} else {
|
|
422
|
-
deferred = $.
|
|
558
|
+
deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse);
|
|
423
559
|
}
|
|
424
560
|
return deferred;
|
|
425
|
-
function
|
|
426
|
-
var
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
that.storage.set(keys.thumbprint, thumbprint, o.ttl);
|
|
431
|
-
that.storage.set(keys.protocol, utils.getProtocol(), o.ttl);
|
|
432
|
-
}
|
|
433
|
-
that._mergeProcessedData(processedData);
|
|
561
|
+
function handlePrefetchResponse(resp) {
|
|
562
|
+
var filtered;
|
|
563
|
+
filtered = o.filter ? o.filter(resp) : resp;
|
|
564
|
+
that.add(filtered);
|
|
565
|
+
that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);
|
|
434
566
|
}
|
|
435
567
|
},
|
|
436
|
-
|
|
437
|
-
var
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
item.datum = datum;
|
|
568
|
+
_getFromRemote: function getFromRemote(query, cb) {
|
|
569
|
+
var that = this, url, uriEncodedQuery;
|
|
570
|
+
query = query || "";
|
|
571
|
+
uriEncodedQuery = encodeURIComponent(query);
|
|
572
|
+
url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
|
|
573
|
+
return this.transport.get(url, this.remote.ajax, handleRemoteResponse);
|
|
574
|
+
function handleRemoteResponse(resp) {
|
|
575
|
+
var filtered = that.remote.filter ? that.remote.filter(resp) : resp;
|
|
576
|
+
cb(filtered);
|
|
446
577
|
}
|
|
447
|
-
item.tokens = utils.filter(item.tokens, function(token) {
|
|
448
|
-
return !utils.isBlankString(token);
|
|
449
|
-
});
|
|
450
|
-
item.tokens = utils.map(item.tokens, function(token) {
|
|
451
|
-
return token.toLowerCase();
|
|
452
|
-
});
|
|
453
|
-
return item;
|
|
454
|
-
},
|
|
455
|
-
_processData: function(data) {
|
|
456
|
-
var that = this, itemHash = {}, adjacencyList = {};
|
|
457
|
-
utils.each(data, function(i, datum) {
|
|
458
|
-
var item = that._transformDatum(datum), id = utils.getUniqueId(item.value);
|
|
459
|
-
itemHash[id] = item;
|
|
460
|
-
utils.each(item.tokens, function(i, token) {
|
|
461
|
-
var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]);
|
|
462
|
-
!~utils.indexOf(adjacency, id) && adjacency.push(id);
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
return {
|
|
466
|
-
itemHash: itemHash,
|
|
467
|
-
adjacencyList: adjacencyList
|
|
468
|
-
};
|
|
469
578
|
},
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
});
|
|
579
|
+
_saveToStorage: function saveToStorage(data, thumbprint, ttl) {
|
|
580
|
+
if (this.storage) {
|
|
581
|
+
this.storage.set(keys.data, data, ttl);
|
|
582
|
+
this.storage.set(keys.protocol, location.protocol, ttl);
|
|
583
|
+
this.storage.set(keys.thumbprint, thumbprint, ttl);
|
|
584
|
+
}
|
|
477
585
|
},
|
|
478
|
-
|
|
479
|
-
var
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
utils.each(firstChars, function(i, firstChar) {
|
|
485
|
-
var list = that.adjacencyList[firstChar];
|
|
486
|
-
if (!list) {
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
lists.push(list);
|
|
490
|
-
if (!shortestList || list.length < shortestList.length) {
|
|
491
|
-
shortestList = list;
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
if (lists.length < firstChars.length) {
|
|
495
|
-
return [];
|
|
586
|
+
_readFromStorage: function readFromStorage(thumbprint) {
|
|
587
|
+
var stored = {}, isExpired;
|
|
588
|
+
if (this.storage) {
|
|
589
|
+
stored.data = this.storage.get(keys.data);
|
|
590
|
+
stored.protocol = this.storage.get(keys.protocol);
|
|
591
|
+
stored.thumbprint = this.storage.get(keys.thumbprint);
|
|
496
592
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
isCandidate = utils.every(lists, function(list) {
|
|
500
|
-
return ~utils.indexOf(list, id);
|
|
501
|
-
});
|
|
502
|
-
isMatch = isCandidate && utils.every(terms, function(term) {
|
|
503
|
-
return utils.some(item.tokens, function(token) {
|
|
504
|
-
return token.indexOf(term) === 0;
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
isMatch && suggestions.push(item);
|
|
508
|
-
});
|
|
509
|
-
return suggestions;
|
|
593
|
+
isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;
|
|
594
|
+
return stored.data && !isExpired ? stored.data : null;
|
|
510
595
|
},
|
|
511
|
-
initialize: function() {
|
|
512
|
-
var deferred;
|
|
513
|
-
this.
|
|
596
|
+
initialize: function initialize() {
|
|
597
|
+
var that = this, deferred;
|
|
598
|
+
deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();
|
|
599
|
+
this.local && deferred.done(addLocalToIndex);
|
|
514
600
|
this.transport = this.remote ? new Transport(this.remote) : null;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
this.initialize = function() {
|
|
518
|
-
return deferred;
|
|
601
|
+
this.initialize = function initialize() {
|
|
602
|
+
return deferred.promise();
|
|
519
603
|
};
|
|
520
|
-
return deferred;
|
|
604
|
+
return deferred.promise();
|
|
605
|
+
function addLocalToIndex() {
|
|
606
|
+
that.add(that.local);
|
|
607
|
+
}
|
|
521
608
|
},
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
609
|
+
add: function add(data) {
|
|
610
|
+
this.index.add(data);
|
|
611
|
+
},
|
|
612
|
+
get: function get(query, cb) {
|
|
613
|
+
var that = this, matches, cacheHit = false;
|
|
614
|
+
matches = this.index.get(query);
|
|
615
|
+
matches = this.sorter(matches).slice(0, this.limit);
|
|
616
|
+
if (matches.length < this.limit && this.transport) {
|
|
617
|
+
cacheHit = this._getFromRemote(query, returnRemoteMatches);
|
|
526
618
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
suggestions = suggestions.slice(0);
|
|
535
|
-
utils.each(data, function(i, datum) {
|
|
536
|
-
var item = that._transformDatum(datum), isDuplicate;
|
|
537
|
-
isDuplicate = utils.some(suggestions, function(suggestion) {
|
|
538
|
-
return item.value === suggestion.value;
|
|
619
|
+
!cacheHit && cb && cb(matches);
|
|
620
|
+
function returnRemoteMatches(remoteMatches) {
|
|
621
|
+
var matchesWithBackfill = matches.slice(0);
|
|
622
|
+
_.each(remoteMatches, function(remoteMatch) {
|
|
623
|
+
var isDuplicate;
|
|
624
|
+
isDuplicate = _.some(matchesWithBackfill, function(match) {
|
|
625
|
+
return that.dupDetector(remoteMatch, match);
|
|
539
626
|
});
|
|
540
|
-
!isDuplicate &&
|
|
541
|
-
return
|
|
627
|
+
!isDuplicate && matchesWithBackfill.push(remoteMatch);
|
|
628
|
+
return matchesWithBackfill.length < that.limit;
|
|
542
629
|
});
|
|
543
|
-
cb && cb(
|
|
630
|
+
cb && cb(that.sorter(matchesWithBackfill));
|
|
544
631
|
}
|
|
632
|
+
},
|
|
633
|
+
ttAdapter: function ttAdapter() {
|
|
634
|
+
return _.bind(this.get, this);
|
|
545
635
|
}
|
|
546
636
|
});
|
|
547
|
-
return
|
|
548
|
-
function
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
637
|
+
return Bloodhound;
|
|
638
|
+
function getSorter(sortFn) {
|
|
639
|
+
return _.isFunction(sortFn) ? sort : noSort;
|
|
640
|
+
function sort(array) {
|
|
641
|
+
return array.sort(sortFn);
|
|
642
|
+
}
|
|
643
|
+
function noSort(array) {
|
|
644
|
+
return array;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function ignoreDuplicates() {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}();
|
|
651
|
+
var html = {
|
|
652
|
+
wrapper: '<span class="twitter-typeahead"></span>',
|
|
653
|
+
dropdown: '<span class="tt-dropdown-menu"></span>',
|
|
654
|
+
dataset: '<div class="tt-dataset-%CLASS%"></div>',
|
|
655
|
+
suggestions: '<span class="tt-suggestions"></span>',
|
|
656
|
+
suggestion: '<div class="tt-suggestion">%BODY%</div>'
|
|
657
|
+
};
|
|
658
|
+
var css = {
|
|
659
|
+
wrapper: {
|
|
660
|
+
position: "relative",
|
|
661
|
+
display: "inline-block"
|
|
662
|
+
},
|
|
663
|
+
hint: {
|
|
664
|
+
position: "absolute",
|
|
665
|
+
top: "0",
|
|
666
|
+
left: "0",
|
|
667
|
+
borderColor: "transparent",
|
|
668
|
+
boxShadow: "none"
|
|
669
|
+
},
|
|
670
|
+
input: {
|
|
671
|
+
position: "relative",
|
|
672
|
+
verticalAlign: "top",
|
|
673
|
+
backgroundColor: "transparent"
|
|
674
|
+
},
|
|
675
|
+
inputWithNoHint: {
|
|
676
|
+
position: "relative",
|
|
677
|
+
verticalAlign: "top"
|
|
678
|
+
},
|
|
679
|
+
dropdown: {
|
|
680
|
+
position: "absolute",
|
|
681
|
+
top: "100%",
|
|
682
|
+
left: "0",
|
|
683
|
+
zIndex: "100",
|
|
684
|
+
display: "none"
|
|
685
|
+
},
|
|
686
|
+
suggestions: {
|
|
687
|
+
display: "block"
|
|
688
|
+
},
|
|
689
|
+
suggestion: {
|
|
690
|
+
whiteSpace: "nowrap",
|
|
691
|
+
cursor: "pointer"
|
|
692
|
+
},
|
|
693
|
+
suggestionChild: {
|
|
694
|
+
whiteSpace: "normal"
|
|
695
|
+
},
|
|
696
|
+
ltr: {
|
|
697
|
+
left: "0",
|
|
698
|
+
right: "auto"
|
|
699
|
+
},
|
|
700
|
+
rtl: {
|
|
701
|
+
left: "auto",
|
|
702
|
+
right: " 0"
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
if (_.isMsie()) {
|
|
706
|
+
_.mixin(css.input, {
|
|
707
|
+
backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
if (_.isMsie() && _.isMsie() <= 7) {
|
|
711
|
+
_.mixin(css.input, {
|
|
712
|
+
marginTop: "-1px"
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
var EventBus = function() {
|
|
716
|
+
var namespace = "typeahead:";
|
|
717
|
+
function EventBus(o) {
|
|
718
|
+
if (!o || !o.el) {
|
|
719
|
+
$.error("EventBus initialized without el");
|
|
720
|
+
}
|
|
721
|
+
this.$el = $(o.el);
|
|
722
|
+
}
|
|
723
|
+
_.mixin(EventBus.prototype, {
|
|
724
|
+
trigger: function(type) {
|
|
725
|
+
var args = [].slice.call(arguments, 1);
|
|
726
|
+
this.$el.trigger(namespace + type, args);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
return EventBus;
|
|
730
|
+
}();
|
|
731
|
+
var EventEmitter = function() {
|
|
732
|
+
var splitter = /\s+/, nextTick = getNextTick();
|
|
733
|
+
return {
|
|
734
|
+
onSync: onSync,
|
|
735
|
+
onAsync: onAsync,
|
|
736
|
+
off: off,
|
|
737
|
+
trigger: trigger
|
|
738
|
+
};
|
|
739
|
+
function on(method, types, cb, context) {
|
|
740
|
+
var type;
|
|
741
|
+
if (!cb) {
|
|
742
|
+
return this;
|
|
743
|
+
}
|
|
744
|
+
types = types.split(splitter);
|
|
745
|
+
cb = context ? bindContext(cb, context) : cb;
|
|
746
|
+
this._callbacks = this._callbacks || {};
|
|
747
|
+
while (type = types.shift()) {
|
|
748
|
+
this._callbacks[type] = this._callbacks[type] || {
|
|
749
|
+
sync: [],
|
|
750
|
+
async: []
|
|
751
|
+
};
|
|
752
|
+
this._callbacks[type][method].push(cb);
|
|
753
|
+
}
|
|
754
|
+
return this;
|
|
755
|
+
}
|
|
756
|
+
function onAsync(types, cb, context) {
|
|
757
|
+
return on.call(this, "async", types, cb, context);
|
|
758
|
+
}
|
|
759
|
+
function onSync(types, cb, context) {
|
|
760
|
+
return on.call(this, "sync", types, cb, context);
|
|
761
|
+
}
|
|
762
|
+
function off(types) {
|
|
763
|
+
var type;
|
|
764
|
+
if (!this._callbacks) {
|
|
765
|
+
return this;
|
|
766
|
+
}
|
|
767
|
+
types = types.split(splitter);
|
|
768
|
+
while (type = types.shift()) {
|
|
769
|
+
delete this._callbacks[type];
|
|
770
|
+
}
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
function trigger(types) {
|
|
774
|
+
var that = this, type, callbacks, args, syncFlush, asyncFlush;
|
|
775
|
+
if (!this._callbacks) {
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
types = types.split(splitter);
|
|
779
|
+
args = [].slice.call(arguments, 1);
|
|
780
|
+
while ((type = types.shift()) && (callbacks = this._callbacks[type])) {
|
|
781
|
+
syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));
|
|
782
|
+
asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));
|
|
783
|
+
syncFlush() && nextTick(asyncFlush);
|
|
784
|
+
}
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
function getFlush(callbacks, context, args) {
|
|
788
|
+
return flush;
|
|
789
|
+
function flush() {
|
|
790
|
+
var cancelled;
|
|
791
|
+
for (var i = 0; !cancelled && i < callbacks.length; i += 1) {
|
|
792
|
+
cancelled = callbacks[i].apply(context, args) === false;
|
|
793
|
+
}
|
|
794
|
+
return !cancelled;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function getNextTick() {
|
|
798
|
+
var nextTickFn, messageChannel;
|
|
799
|
+
if (window.setImmediate) {
|
|
800
|
+
nextTickFn = function nextTickSetImmediate(fn) {
|
|
801
|
+
setImmediate(function() {
|
|
802
|
+
fn();
|
|
803
|
+
});
|
|
804
|
+
};
|
|
555
805
|
} else {
|
|
556
|
-
|
|
557
|
-
|
|
806
|
+
nextTickFn = function nextTickSetTimeout(fn) {
|
|
807
|
+
setTimeout(function() {
|
|
808
|
+
fn();
|
|
809
|
+
}, 0);
|
|
558
810
|
};
|
|
559
811
|
}
|
|
560
|
-
return
|
|
812
|
+
return nextTickFn;
|
|
561
813
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
var that = this;
|
|
566
|
-
utils.bindAll(this);
|
|
567
|
-
this.specialKeyCodeMap = {
|
|
568
|
-
9: "tab",
|
|
569
|
-
27: "esc",
|
|
570
|
-
37: "left",
|
|
571
|
-
39: "right",
|
|
572
|
-
13: "enter",
|
|
573
|
-
38: "up",
|
|
574
|
-
40: "down"
|
|
814
|
+
function bindContext(fn, context) {
|
|
815
|
+
return fn.bind ? fn.bind(context) : function() {
|
|
816
|
+
fn.apply(context, [].slice.call(arguments, 0));
|
|
575
817
|
};
|
|
818
|
+
}
|
|
819
|
+
}();
|
|
820
|
+
var highlight = function(doc) {
|
|
821
|
+
var defaults = {
|
|
822
|
+
node: null,
|
|
823
|
+
pattern: null,
|
|
824
|
+
tagName: "strong",
|
|
825
|
+
className: null,
|
|
826
|
+
wordsOnly: false,
|
|
827
|
+
caseSensitive: false
|
|
828
|
+
};
|
|
829
|
+
return function hightlight(o) {
|
|
830
|
+
var regex;
|
|
831
|
+
o = _.mixin({}, defaults, o);
|
|
832
|
+
if (!o.node || !o.pattern) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];
|
|
836
|
+
regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
|
|
837
|
+
traverse(o.node, hightlightTextNode);
|
|
838
|
+
function hightlightTextNode(textNode) {
|
|
839
|
+
var match, patternNode;
|
|
840
|
+
if (match = regex.exec(textNode.data)) {
|
|
841
|
+
wrapperNode = doc.createElement(o.tagName);
|
|
842
|
+
o.className && (wrapperNode.className = o.className);
|
|
843
|
+
patternNode = textNode.splitText(match.index);
|
|
844
|
+
patternNode.splitText(match[0].length);
|
|
845
|
+
wrapperNode.appendChild(patternNode.cloneNode(true));
|
|
846
|
+
textNode.parentNode.replaceChild(wrapperNode, patternNode);
|
|
847
|
+
}
|
|
848
|
+
return !!match;
|
|
849
|
+
}
|
|
850
|
+
function traverse(el, hightlightTextNode) {
|
|
851
|
+
var childNode, TEXT_NODE_TYPE = 3;
|
|
852
|
+
for (var i = 0; i < el.childNodes.length; i++) {
|
|
853
|
+
childNode = el.childNodes[i];
|
|
854
|
+
if (childNode.nodeType === TEXT_NODE_TYPE) {
|
|
855
|
+
i += hightlightTextNode(childNode) ? 1 : 0;
|
|
856
|
+
} else {
|
|
857
|
+
traverse(childNode, hightlightTextNode);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
function getRegex(patterns, caseSensitive, wordsOnly) {
|
|
863
|
+
var escapedPatterns = [], regexStr;
|
|
864
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
865
|
+
escapedPatterns.push(_.escapeRegExChars(patterns[i]));
|
|
866
|
+
}
|
|
867
|
+
regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")";
|
|
868
|
+
return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i");
|
|
869
|
+
}
|
|
870
|
+
}(window.document);
|
|
871
|
+
var Input = function() {
|
|
872
|
+
var specialKeyCodeMap;
|
|
873
|
+
specialKeyCodeMap = {
|
|
874
|
+
9: "tab",
|
|
875
|
+
27: "esc",
|
|
876
|
+
37: "left",
|
|
877
|
+
39: "right",
|
|
878
|
+
13: "enter",
|
|
879
|
+
38: "up",
|
|
880
|
+
40: "down"
|
|
881
|
+
};
|
|
882
|
+
function Input(o) {
|
|
883
|
+
var that = this, onBlur, onFocus, onKeydown, onInput;
|
|
884
|
+
o = o || {};
|
|
885
|
+
if (!o.input) {
|
|
886
|
+
$.error("input is missing");
|
|
887
|
+
}
|
|
888
|
+
onBlur = _.bind(this._onBlur, this);
|
|
889
|
+
onFocus = _.bind(this._onFocus, this);
|
|
890
|
+
onKeydown = _.bind(this._onKeydown, this);
|
|
891
|
+
onInput = _.bind(this._onInput, this);
|
|
576
892
|
this.$hint = $(o.hint);
|
|
577
|
-
this.$input = $(o.input).on("blur.tt",
|
|
578
|
-
if (
|
|
579
|
-
this
|
|
893
|
+
this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown);
|
|
894
|
+
if (this.$hint.length === 0) {
|
|
895
|
+
this.setHintValue = this.getHintValue = this.clearHint = _.noop;
|
|
896
|
+
}
|
|
897
|
+
if (!_.isMsie()) {
|
|
898
|
+
this.$input.on("input.tt", onInput);
|
|
580
899
|
} else {
|
|
581
900
|
this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
|
|
582
|
-
if (
|
|
901
|
+
if (specialKeyCodeMap[$e.which || $e.keyCode]) {
|
|
583
902
|
return;
|
|
584
903
|
}
|
|
585
|
-
|
|
904
|
+
_.defer(_.bind(that._onInput, that, $e));
|
|
586
905
|
});
|
|
587
906
|
}
|
|
588
907
|
this.query = this.$input.val();
|
|
589
908
|
this.$overflowHelper = buildOverflowHelper(this.$input);
|
|
590
909
|
}
|
|
591
|
-
|
|
592
|
-
|
|
910
|
+
Input.normalizeQuery = function(str) {
|
|
911
|
+
return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
|
|
912
|
+
};
|
|
913
|
+
_.mixin(Input.prototype, EventEmitter, {
|
|
914
|
+
_onBlur: function onBlur($e) {
|
|
915
|
+
this.resetInputValue();
|
|
916
|
+
this.trigger("blurred");
|
|
917
|
+
},
|
|
918
|
+
_onFocus: function onFocus($e) {
|
|
593
919
|
this.trigger("focused");
|
|
594
920
|
},
|
|
595
|
-
|
|
596
|
-
|
|
921
|
+
_onKeydown: function onKeydown($e) {
|
|
922
|
+
var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
|
|
923
|
+
this._managePreventDefault(keyName, $e);
|
|
924
|
+
if (keyName && this._shouldTrigger(keyName, $e)) {
|
|
925
|
+
this.trigger(keyName + "Keyed", $e);
|
|
926
|
+
}
|
|
597
927
|
},
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
keyName && this.trigger(keyName + "Keyed", $e);
|
|
928
|
+
_onInput: function onInput($e) {
|
|
929
|
+
this._checkInputValue();
|
|
601
930
|
},
|
|
602
|
-
|
|
603
|
-
var
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
931
|
+
_managePreventDefault: function managePreventDefault(keyName, $e) {
|
|
932
|
+
var preventDefault, hintValue, inputValue;
|
|
933
|
+
switch (keyName) {
|
|
934
|
+
case "tab":
|
|
935
|
+
hintValue = this.getHintValue();
|
|
936
|
+
inputValue = this.getInputValue();
|
|
937
|
+
preventDefault = hintValue && hintValue !== inputValue && !withModifier($e);
|
|
938
|
+
break;
|
|
939
|
+
|
|
940
|
+
case "up":
|
|
941
|
+
case "down":
|
|
942
|
+
preventDefault = !withModifier($e);
|
|
943
|
+
break;
|
|
944
|
+
|
|
945
|
+
default:
|
|
946
|
+
preventDefault = false;
|
|
612
947
|
}
|
|
948
|
+
preventDefault && $e.preventDefault();
|
|
613
949
|
},
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
950
|
+
_shouldTrigger: function shouldTrigger(keyName, $e) {
|
|
951
|
+
var trigger;
|
|
952
|
+
switch (keyName) {
|
|
953
|
+
case "tab":
|
|
954
|
+
trigger = !withModifier($e);
|
|
955
|
+
break;
|
|
956
|
+
|
|
957
|
+
default:
|
|
958
|
+
trigger = true;
|
|
959
|
+
}
|
|
960
|
+
return trigger;
|
|
961
|
+
},
|
|
962
|
+
_checkInputValue: function checkInputValue() {
|
|
963
|
+
var inputValue, areEquivalent, hasDifferentWhitespace;
|
|
964
|
+
inputValue = this.getInputValue();
|
|
965
|
+
areEquivalent = areQueriesEquivalent(inputValue, this.query);
|
|
966
|
+
hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false;
|
|
967
|
+
if (!areEquivalent) {
|
|
968
|
+
this.trigger("queryChanged", this.query = inputValue);
|
|
969
|
+
} else if (hasDifferentWhitespace) {
|
|
970
|
+
this.trigger("whitespaceChanged", this.query);
|
|
971
|
+
}
|
|
618
972
|
},
|
|
619
|
-
focus: function() {
|
|
973
|
+
focus: function focus() {
|
|
620
974
|
this.$input.focus();
|
|
621
975
|
},
|
|
622
|
-
blur: function() {
|
|
976
|
+
blur: function blur() {
|
|
623
977
|
this.$input.blur();
|
|
624
978
|
},
|
|
625
|
-
getQuery: function() {
|
|
979
|
+
getQuery: function getQuery() {
|
|
626
980
|
return this.query;
|
|
627
981
|
},
|
|
628
|
-
setQuery: function(query) {
|
|
982
|
+
setQuery: function setQuery(query) {
|
|
629
983
|
this.query = query;
|
|
630
984
|
},
|
|
631
|
-
getInputValue: function() {
|
|
985
|
+
getInputValue: function getInputValue() {
|
|
632
986
|
return this.$input.val();
|
|
633
987
|
},
|
|
634
|
-
setInputValue: function(value, silent) {
|
|
988
|
+
setInputValue: function setInputValue(value, silent) {
|
|
635
989
|
this.$input.val(value);
|
|
636
|
-
!silent && this.
|
|
990
|
+
!silent && this._checkInputValue();
|
|
637
991
|
},
|
|
638
|
-
getHintValue: function() {
|
|
992
|
+
getHintValue: function getHintValue() {
|
|
639
993
|
return this.$hint.val();
|
|
640
994
|
},
|
|
641
|
-
setHintValue: function(value) {
|
|
995
|
+
setHintValue: function setHintValue(value) {
|
|
642
996
|
this.$hint.val(value);
|
|
643
997
|
},
|
|
644
|
-
|
|
998
|
+
resetInputValue: function resetInputValue() {
|
|
999
|
+
this.$input.val(this.query);
|
|
1000
|
+
},
|
|
1001
|
+
clearHint: function clearHint() {
|
|
1002
|
+
this.$hint.val("");
|
|
1003
|
+
},
|
|
1004
|
+
getLanguageDirection: function getLanguageDirection() {
|
|
645
1005
|
return (this.$input.css("direction") || "ltr").toLowerCase();
|
|
646
1006
|
},
|
|
647
|
-
|
|
1007
|
+
hasOverflow: function hasOverflow() {
|
|
1008
|
+
var constraint = this.$input.width() - 2;
|
|
648
1009
|
this.$overflowHelper.text(this.getInputValue());
|
|
649
|
-
return this.$overflowHelper.width()
|
|
1010
|
+
return this.$overflowHelper.width() >= constraint;
|
|
650
1011
|
},
|
|
651
1012
|
isCursorAtEnd: function() {
|
|
652
|
-
var valueLength
|
|
653
|
-
|
|
1013
|
+
var valueLength, selectionStart, range;
|
|
1014
|
+
valueLength = this.$input.val().length;
|
|
1015
|
+
selectionStart = this.$input[0].selectionStart;
|
|
1016
|
+
if (_.isNumber(selectionStart)) {
|
|
654
1017
|
return selectionStart === valueLength;
|
|
655
1018
|
} else if (document.selection) {
|
|
656
1019
|
range = document.selection.createRange();
|
|
@@ -658,13 +1021,17 @@
|
|
|
658
1021
|
return valueLength === range.text.length;
|
|
659
1022
|
}
|
|
660
1023
|
return true;
|
|
1024
|
+
},
|
|
1025
|
+
destroy: function destroy() {
|
|
1026
|
+
this.$hint.off(".tt");
|
|
1027
|
+
this.$input.off(".tt");
|
|
1028
|
+
this.$hint = this.$input = this.$overflowHelper = null;
|
|
661
1029
|
}
|
|
662
1030
|
});
|
|
663
|
-
return
|
|
1031
|
+
return Input;
|
|
664
1032
|
function buildOverflowHelper($input) {
|
|
665
|
-
return $("
|
|
1033
|
+
return $('<pre aria-hidden="true"></pre>').css({
|
|
666
1034
|
position: "absolute",
|
|
667
|
-
left: "-9999px",
|
|
668
1035
|
visibility: "hidden",
|
|
669
1036
|
whiteSpace: "nowrap",
|
|
670
1037
|
fontFamily: $input.css("font-family"),
|
|
@@ -679,461 +1046,613 @@
|
|
|
679
1046
|
textTransform: $input.css("text-transform")
|
|
680
1047
|
}).insertAfter($input);
|
|
681
1048
|
}
|
|
682
|
-
function
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1049
|
+
function areQueriesEquivalent(a, b) {
|
|
1050
|
+
return Input.normalizeQuery(a) === Input.normalizeQuery(b);
|
|
1051
|
+
}
|
|
1052
|
+
function withModifier($e) {
|
|
1053
|
+
return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
|
|
686
1054
|
}
|
|
687
1055
|
}();
|
|
688
|
-
var
|
|
689
|
-
var
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
suggestion: {
|
|
696
|
-
whiteSpace: "nowrap",
|
|
697
|
-
cursor: "pointer"
|
|
698
|
-
},
|
|
699
|
-
suggestionChild: {
|
|
700
|
-
whiteSpace: "normal"
|
|
1056
|
+
var Dataset = function() {
|
|
1057
|
+
var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum";
|
|
1058
|
+
function Dataset(o) {
|
|
1059
|
+
o = o || {};
|
|
1060
|
+
o.templates = o.templates || {};
|
|
1061
|
+
if (!o.source) {
|
|
1062
|
+
$.error("missing source");
|
|
701
1063
|
}
|
|
1064
|
+
if (o.name && !isValidName(o.name)) {
|
|
1065
|
+
$.error("invalid dataset name: " + o.name);
|
|
1066
|
+
}
|
|
1067
|
+
this.query = null;
|
|
1068
|
+
this.highlight = !!o.highlight;
|
|
1069
|
+
this.name = o.name || _.getUniqueId();
|
|
1070
|
+
this.source = o.source;
|
|
1071
|
+
this.displayFn = getDisplayFn(o.display || o.displayKey);
|
|
1072
|
+
this.templates = getTemplates(o.templates, this.displayFn);
|
|
1073
|
+
this.$el = $(html.dataset.replace("%CLASS%", this.name));
|
|
1074
|
+
}
|
|
1075
|
+
Dataset.extractDatasetName = function extractDatasetName(el) {
|
|
1076
|
+
return $(el).data(datasetKey);
|
|
1077
|
+
};
|
|
1078
|
+
Dataset.extractValue = function extractDatum(el) {
|
|
1079
|
+
return $(el).data(valueKey);
|
|
702
1080
|
};
|
|
703
|
-
function
|
|
704
|
-
|
|
1081
|
+
Dataset.extractDatum = function extractDatum(el) {
|
|
1082
|
+
return $(el).data(datumKey);
|
|
1083
|
+
};
|
|
1084
|
+
_.mixin(Dataset.prototype, EventEmitter, {
|
|
1085
|
+
_render: function render(query, suggestions) {
|
|
1086
|
+
if (!this.$el) {
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
var that = this, hasSuggestions;
|
|
1090
|
+
this.$el.empty();
|
|
1091
|
+
hasSuggestions = suggestions && suggestions.length;
|
|
1092
|
+
if (!hasSuggestions && this.templates.empty) {
|
|
1093
|
+
this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null);
|
|
1094
|
+
} else if (hasSuggestions) {
|
|
1095
|
+
this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null);
|
|
1096
|
+
}
|
|
1097
|
+
this.trigger("rendered");
|
|
1098
|
+
function getEmptyHtml() {
|
|
1099
|
+
return that.templates.empty({
|
|
1100
|
+
query: query,
|
|
1101
|
+
isEmpty: true
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
function getSuggestionsHtml() {
|
|
1105
|
+
var $suggestions, nodes;
|
|
1106
|
+
$suggestions = $(html.suggestions).css(css.suggestions);
|
|
1107
|
+
nodes = _.map(suggestions, getSuggestionNode);
|
|
1108
|
+
$suggestions.append.apply($suggestions, nodes);
|
|
1109
|
+
that.highlight && highlight({
|
|
1110
|
+
node: $suggestions[0],
|
|
1111
|
+
pattern: query
|
|
1112
|
+
});
|
|
1113
|
+
return $suggestions;
|
|
1114
|
+
function getSuggestionNode(suggestion) {
|
|
1115
|
+
var $el, innerHtml, outerHtml;
|
|
1116
|
+
innerHtml = that.templates.suggestion(suggestion);
|
|
1117
|
+
outerHtml = html.suggestion.replace("%BODY%", innerHtml);
|
|
1118
|
+
$el = $(outerHtml).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion);
|
|
1119
|
+
$el.children().each(function() {
|
|
1120
|
+
$(this).css(css.suggestionChild);
|
|
1121
|
+
});
|
|
1122
|
+
return $el;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function getHeaderHtml() {
|
|
1126
|
+
return that.templates.header({
|
|
1127
|
+
query: query,
|
|
1128
|
+
isEmpty: !hasSuggestions
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
function getFooterHtml() {
|
|
1132
|
+
return that.templates.footer({
|
|
1133
|
+
query: query,
|
|
1134
|
+
isEmpty: !hasSuggestions
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
getRoot: function getRoot() {
|
|
1139
|
+
return this.$el;
|
|
1140
|
+
},
|
|
1141
|
+
update: function update(query) {
|
|
1142
|
+
var that = this;
|
|
1143
|
+
this.query = query;
|
|
1144
|
+
this.source(query, renderIfQueryIsSame);
|
|
1145
|
+
function renderIfQueryIsSame(suggestions) {
|
|
1146
|
+
query === that.query && that._render(query, suggestions);
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
clear: function clear() {
|
|
1150
|
+
this._render(this.query || "");
|
|
1151
|
+
},
|
|
1152
|
+
isEmpty: function isEmpty() {
|
|
1153
|
+
return this.$el.is(":empty");
|
|
1154
|
+
},
|
|
1155
|
+
destroy: function destroy() {
|
|
1156
|
+
this.$el = null;
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
return Dataset;
|
|
1160
|
+
function getDisplayFn(display) {
|
|
1161
|
+
display = display || "value";
|
|
1162
|
+
return _.isFunction(display) ? display : displayFn;
|
|
1163
|
+
function displayFn(obj) {
|
|
1164
|
+
return obj[display];
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function getTemplates(templates, displayFn) {
|
|
1168
|
+
return {
|
|
1169
|
+
empty: templates.empty && _.templatify(templates.empty),
|
|
1170
|
+
header: templates.header && _.templatify(templates.header),
|
|
1171
|
+
footer: templates.footer && _.templatify(templates.footer),
|
|
1172
|
+
suggestion: templates.suggestion || suggestionTemplate
|
|
1173
|
+
};
|
|
1174
|
+
function suggestionTemplate(context) {
|
|
1175
|
+
return "<p>" + displayFn(context) + "</p>";
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
function isValidName(str) {
|
|
1179
|
+
return /^[_a-zA-Z0-9-]+$/.test(str);
|
|
1180
|
+
}
|
|
1181
|
+
}();
|
|
1182
|
+
var Dropdown = function() {
|
|
1183
|
+
function Dropdown(o) {
|
|
1184
|
+
var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave;
|
|
1185
|
+
o = o || {};
|
|
1186
|
+
if (!o.menu) {
|
|
1187
|
+
$.error("menu is required");
|
|
1188
|
+
}
|
|
705
1189
|
this.isOpen = false;
|
|
706
1190
|
this.isEmpty = true;
|
|
707
|
-
this.
|
|
708
|
-
|
|
1191
|
+
this.datasets = _.map(o.datasets, initializeDataset);
|
|
1192
|
+
onSuggestionClick = _.bind(this._onSuggestionClick, this);
|
|
1193
|
+
onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this);
|
|
1194
|
+
onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this);
|
|
1195
|
+
this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave);
|
|
1196
|
+
_.each(this.datasets, function(dataset) {
|
|
1197
|
+
that.$menu.append(dataset.getRoot());
|
|
1198
|
+
dataset.onSync("rendered", that._onRendered, that);
|
|
1199
|
+
});
|
|
709
1200
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
this.
|
|
713
|
-
},
|
|
714
|
-
|
|
715
|
-
this.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
this.
|
|
720
|
-
|
|
1201
|
+
_.mixin(Dropdown.prototype, EventEmitter, {
|
|
1202
|
+
_onSuggestionClick: function onSuggestionClick($e) {
|
|
1203
|
+
this.trigger("suggestionClicked", $($e.currentTarget));
|
|
1204
|
+
},
|
|
1205
|
+
_onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {
|
|
1206
|
+
this._removeCursor();
|
|
1207
|
+
this._setCursor($($e.currentTarget), true);
|
|
1208
|
+
},
|
|
1209
|
+
_onSuggestionMouseLeave: function onSuggestionMouseLeave($e) {
|
|
1210
|
+
this._removeCursor();
|
|
1211
|
+
},
|
|
1212
|
+
_onRendered: function onRendered() {
|
|
1213
|
+
this.isEmpty = _.every(this.datasets, isDatasetEmpty);
|
|
1214
|
+
this.isEmpty ? this._hide() : this.isOpen && this._show();
|
|
1215
|
+
this.trigger("datasetRendered");
|
|
1216
|
+
function isDatasetEmpty(dataset) {
|
|
1217
|
+
return dataset.isEmpty();
|
|
1218
|
+
}
|
|
721
1219
|
},
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
this.trigger("suggestionSelected", extractSuggestion($suggestion));
|
|
1220
|
+
_hide: function() {
|
|
1221
|
+
this.$menu.hide();
|
|
725
1222
|
},
|
|
726
1223
|
_show: function() {
|
|
727
1224
|
this.$menu.css("display", "block");
|
|
728
1225
|
},
|
|
729
|
-
|
|
730
|
-
this.$menu.
|
|
1226
|
+
_getSuggestions: function getSuggestions() {
|
|
1227
|
+
return this.$menu.find(".tt-suggestion");
|
|
1228
|
+
},
|
|
1229
|
+
_getCursor: function getCursor() {
|
|
1230
|
+
return this.$menu.find(".tt-cursor").first();
|
|
1231
|
+
},
|
|
1232
|
+
_setCursor: function setCursor($el, silent) {
|
|
1233
|
+
$el.first().addClass("tt-cursor");
|
|
1234
|
+
!silent && this.trigger("cursorMoved");
|
|
1235
|
+
},
|
|
1236
|
+
_removeCursor: function removeCursor() {
|
|
1237
|
+
this._getCursor().removeClass("tt-cursor");
|
|
731
1238
|
},
|
|
732
|
-
_moveCursor: function(increment) {
|
|
733
|
-
var $suggestions, $
|
|
734
|
-
if (!this.
|
|
1239
|
+
_moveCursor: function moveCursor(increment) {
|
|
1240
|
+
var $suggestions, $oldCursor, newCursorIndex, $newCursor;
|
|
1241
|
+
if (!this.isOpen) {
|
|
735
1242
|
return;
|
|
736
1243
|
}
|
|
1244
|
+
$oldCursor = this._getCursor();
|
|
737
1245
|
$suggestions = this._getSuggestions();
|
|
738
|
-
|
|
739
|
-
$
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
if (nextIndex === -1) {
|
|
1246
|
+
this._removeCursor();
|
|
1247
|
+
newCursorIndex = $suggestions.index($oldCursor) + increment;
|
|
1248
|
+
newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1;
|
|
1249
|
+
if (newCursorIndex === -1) {
|
|
743
1250
|
this.trigger("cursorRemoved");
|
|
744
1251
|
return;
|
|
745
|
-
} else if (
|
|
746
|
-
|
|
1252
|
+
} else if (newCursorIndex < -1) {
|
|
1253
|
+
newCursorIndex = $suggestions.length - 1;
|
|
747
1254
|
}
|
|
748
|
-
$
|
|
749
|
-
this.
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1255
|
+
this._setCursor($newCursor = $suggestions.eq(newCursorIndex));
|
|
1256
|
+
this._ensureVisible($newCursor);
|
|
1257
|
+
},
|
|
1258
|
+
_ensureVisible: function ensureVisible($el) {
|
|
1259
|
+
var elTop, elBottom, menuScrollTop, menuHeight;
|
|
1260
|
+
elTop = $el.position().top;
|
|
1261
|
+
elBottom = elTop + $el.outerHeight(true);
|
|
1262
|
+
menuScrollTop = this.$menu.scrollTop();
|
|
1263
|
+
menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10);
|
|
757
1264
|
if (elTop < 0) {
|
|
758
1265
|
this.$menu.scrollTop(menuScrollTop + elTop);
|
|
759
1266
|
} else if (menuHeight < elBottom) {
|
|
760
1267
|
this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
|
|
761
1268
|
}
|
|
762
1269
|
},
|
|
763
|
-
|
|
764
|
-
this.$menu.off(".tt");
|
|
765
|
-
this.$menu = null;
|
|
766
|
-
},
|
|
767
|
-
isVisible: function() {
|
|
768
|
-
return this.isOpen && !this.isEmpty;
|
|
769
|
-
},
|
|
770
|
-
closeUnlessMouseIsOverDropdown: function() {
|
|
771
|
-
if (!this.isMouseOverDropdown) {
|
|
772
|
-
this.close();
|
|
773
|
-
}
|
|
774
|
-
},
|
|
775
|
-
close: function() {
|
|
1270
|
+
close: function close() {
|
|
776
1271
|
if (this.isOpen) {
|
|
777
1272
|
this.isOpen = false;
|
|
778
|
-
this.
|
|
1273
|
+
this._removeCursor();
|
|
779
1274
|
this._hide();
|
|
780
|
-
this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor");
|
|
781
1275
|
this.trigger("closed");
|
|
782
1276
|
}
|
|
783
1277
|
},
|
|
784
|
-
open: function() {
|
|
1278
|
+
open: function open() {
|
|
785
1279
|
if (!this.isOpen) {
|
|
786
1280
|
this.isOpen = true;
|
|
787
1281
|
!this.isEmpty && this._show();
|
|
788
1282
|
this.trigger("opened");
|
|
789
1283
|
}
|
|
790
1284
|
},
|
|
791
|
-
setLanguageDirection: function(dir) {
|
|
792
|
-
|
|
793
|
-
left: "0",
|
|
794
|
-
right: "auto"
|
|
795
|
-
}, rtlCss = {
|
|
796
|
-
left: "auto",
|
|
797
|
-
right: " 0"
|
|
798
|
-
};
|
|
799
|
-
dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss);
|
|
1285
|
+
setLanguageDirection: function setLanguageDirection(dir) {
|
|
1286
|
+
this.$menu.css(dir === "ltr" ? css.ltr : css.rtl);
|
|
800
1287
|
},
|
|
801
|
-
moveCursorUp: function() {
|
|
1288
|
+
moveCursorUp: function moveCursorUp() {
|
|
802
1289
|
this._moveCursor(-1);
|
|
803
1290
|
},
|
|
804
|
-
moveCursorDown: function() {
|
|
1291
|
+
moveCursorDown: function moveCursorDown() {
|
|
805
1292
|
this._moveCursor(+1);
|
|
806
1293
|
},
|
|
807
|
-
|
|
808
|
-
var
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
renderSuggestions: function(dataset, suggestions) {
|
|
816
|
-
var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '<div class="tt-suggestion">%body</div>', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el;
|
|
817
|
-
if ($dataset.length === 0) {
|
|
818
|
-
$suggestionsList = $(html.suggestionsList).css(css.suggestionsList);
|
|
819
|
-
$dataset = $("<div></div>").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu);
|
|
820
|
-
}
|
|
821
|
-
if (suggestions.length > 0) {
|
|
822
|
-
this.isEmpty = false;
|
|
823
|
-
this.isOpen && this._show();
|
|
824
|
-
elBuilder = document.createElement("div");
|
|
825
|
-
fragment = document.createDocumentFragment();
|
|
826
|
-
utils.each(suggestions, function(i, suggestion) {
|
|
827
|
-
suggestion.dataset = dataset.name;
|
|
828
|
-
compiledHtml = dataset.template(suggestion.datum);
|
|
829
|
-
elBuilder.innerHTML = wrapper.replace("%body", compiledHtml);
|
|
830
|
-
$el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion);
|
|
831
|
-
$el.children().each(function() {
|
|
832
|
-
$(this).css(css.suggestionChild);
|
|
833
|
-
});
|
|
834
|
-
fragment.appendChild($el[0]);
|
|
835
|
-
});
|
|
836
|
-
$dataset.show().find(".tt-suggestions").html(fragment);
|
|
837
|
-
} else {
|
|
838
|
-
this.clearSuggestions(dataset.name);
|
|
1294
|
+
getDatumForSuggestion: function getDatumForSuggestion($el) {
|
|
1295
|
+
var datum = null;
|
|
1296
|
+
if ($el.length) {
|
|
1297
|
+
datum = {
|
|
1298
|
+
raw: Dataset.extractDatum($el),
|
|
1299
|
+
value: Dataset.extractValue($el),
|
|
1300
|
+
datasetName: Dataset.extractDatasetName($el)
|
|
1301
|
+
};
|
|
839
1302
|
}
|
|
840
|
-
|
|
1303
|
+
return datum;
|
|
841
1304
|
},
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1305
|
+
getDatumForCursor: function getDatumForCursor() {
|
|
1306
|
+
return this.getDatumForSuggestion(this._getCursor().first());
|
|
1307
|
+
},
|
|
1308
|
+
getDatumForTopSuggestion: function getDatumForTopSuggestion() {
|
|
1309
|
+
return this.getDatumForSuggestion(this._getSuggestions().first());
|
|
1310
|
+
},
|
|
1311
|
+
update: function update(query) {
|
|
1312
|
+
_.each(this.datasets, updateDataset);
|
|
1313
|
+
function updateDataset(dataset) {
|
|
1314
|
+
dataset.update(query);
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
empty: function empty() {
|
|
1318
|
+
_.each(this.datasets, clearDataset);
|
|
1319
|
+
this.isEmpty = true;
|
|
1320
|
+
function clearDataset(dataset) {
|
|
1321
|
+
dataset.clear();
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
isVisible: function isVisible() {
|
|
1325
|
+
return this.isOpen && !this.isEmpty;
|
|
1326
|
+
},
|
|
1327
|
+
destroy: function destroy() {
|
|
1328
|
+
this.$menu.off(".tt");
|
|
1329
|
+
this.$menu = null;
|
|
1330
|
+
_.each(this.datasets, destroyDataset);
|
|
1331
|
+
function destroyDataset(dataset) {
|
|
1332
|
+
dataset.destroy();
|
|
849
1333
|
}
|
|
850
1334
|
}
|
|
851
1335
|
});
|
|
852
|
-
return
|
|
853
|
-
function
|
|
854
|
-
return
|
|
1336
|
+
return Dropdown;
|
|
1337
|
+
function initializeDataset(oDataset) {
|
|
1338
|
+
return new Dataset(oDataset);
|
|
855
1339
|
}
|
|
856
1340
|
}();
|
|
857
|
-
var
|
|
858
|
-
var
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
position: "relative",
|
|
865
|
-
display: "inline-block"
|
|
866
|
-
},
|
|
867
|
-
hint: {
|
|
868
|
-
position: "absolute",
|
|
869
|
-
top: "0",
|
|
870
|
-
left: "0",
|
|
871
|
-
borderColor: "transparent",
|
|
872
|
-
boxShadow: "none"
|
|
873
|
-
},
|
|
874
|
-
query: {
|
|
875
|
-
position: "relative",
|
|
876
|
-
verticalAlign: "top",
|
|
877
|
-
backgroundColor: "transparent"
|
|
878
|
-
},
|
|
879
|
-
dropdown: {
|
|
880
|
-
position: "absolute",
|
|
881
|
-
top: "100%",
|
|
882
|
-
left: "0",
|
|
883
|
-
zIndex: "100",
|
|
884
|
-
display: "none"
|
|
1341
|
+
var Typeahead = function() {
|
|
1342
|
+
var attrsKey = "ttAttrs";
|
|
1343
|
+
function Typeahead(o) {
|
|
1344
|
+
var $menu, $input, $hint, datasets;
|
|
1345
|
+
o = o || {};
|
|
1346
|
+
if (!o.input) {
|
|
1347
|
+
$.error("missing input");
|
|
885
1348
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
if (utils.isMsie() && utils.isMsie() <= 7) {
|
|
893
|
-
utils.mixin(css.wrapper, {
|
|
894
|
-
display: "inline",
|
|
895
|
-
zoom: "1"
|
|
896
|
-
});
|
|
897
|
-
utils.mixin(css.query, {
|
|
898
|
-
marginTop: "-1px"
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
function TypeaheadView(o) {
|
|
902
|
-
var $menu, $input, $hint;
|
|
903
|
-
utils.bindAll(this);
|
|
904
|
-
this.$node = buildDomStructure(o.input);
|
|
905
|
-
this.datasets = o.datasets;
|
|
906
|
-
this.dir = null;
|
|
907
|
-
this.eventBus = o.eventBus;
|
|
1349
|
+
this.autoselect = !!o.autoselect;
|
|
1350
|
+
this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
|
|
1351
|
+
this.$node = buildDomStructure(o.input, o.withHint);
|
|
908
1352
|
$menu = this.$node.find(".tt-dropdown-menu");
|
|
909
|
-
$input = this.$node.find(".tt-
|
|
1353
|
+
$input = this.$node.find(".tt-input");
|
|
910
1354
|
$hint = this.$node.find(".tt-hint");
|
|
911
|
-
this.
|
|
912
|
-
|
|
913
|
-
})
|
|
914
|
-
this.
|
|
1355
|
+
this.eventBus = o.eventBus || new EventBus({
|
|
1356
|
+
el: $input
|
|
1357
|
+
});
|
|
1358
|
+
this.dropdown = new Dropdown({
|
|
1359
|
+
menu: $menu,
|
|
1360
|
+
datasets: o.datasets
|
|
1361
|
+
}).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this);
|
|
1362
|
+
this.input = new Input({
|
|
915
1363
|
input: $input,
|
|
916
1364
|
hint: $hint
|
|
917
|
-
}).
|
|
1365
|
+
}).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this);
|
|
1366
|
+
$menu.on("mousedown.tt", function($e) {
|
|
1367
|
+
if (_.isMsie() && _.isMsie() < 9) {
|
|
1368
|
+
$input[0].onbeforedeactivate = function() {
|
|
1369
|
+
window.event.returnValue = false;
|
|
1370
|
+
$input[0].onbeforedeactivate = null;
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
$e.preventDefault();
|
|
1374
|
+
});
|
|
918
1375
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
var
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
hint = this.inputView.getHintValue();
|
|
925
|
-
inputValue = this.inputView.getInputValue();
|
|
926
|
-
preventDefault = hint && hint !== inputValue;
|
|
927
|
-
break;
|
|
928
|
-
|
|
929
|
-
case "upKeyed":
|
|
930
|
-
case "downKeyed":
|
|
931
|
-
preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey;
|
|
932
|
-
break;
|
|
1376
|
+
_.mixin(Typeahead.prototype, {
|
|
1377
|
+
_onSuggestionClicked: function onSuggestionClicked(type, $el) {
|
|
1378
|
+
var datum;
|
|
1379
|
+
if (datum = this.dropdown.getDatumForSuggestion($el)) {
|
|
1380
|
+
this._select(datum);
|
|
933
1381
|
}
|
|
934
|
-
preventDefault && $e.preventDefault();
|
|
935
1382
|
},
|
|
936
|
-
|
|
937
|
-
var
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1383
|
+
_onCursorMoved: function onCursorMoved() {
|
|
1384
|
+
var datum = this.dropdown.getDatumForCursor();
|
|
1385
|
+
this.input.clearHint();
|
|
1386
|
+
this.input.setInputValue(datum.value, true);
|
|
1387
|
+
this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName);
|
|
1388
|
+
},
|
|
1389
|
+
_onCursorRemoved: function onCursorRemoved() {
|
|
1390
|
+
this.input.resetInputValue();
|
|
1391
|
+
this._updateHint();
|
|
1392
|
+
},
|
|
1393
|
+
_onDatasetRendered: function onDatasetRendered() {
|
|
1394
|
+
this._updateHint();
|
|
1395
|
+
},
|
|
1396
|
+
_onOpened: function onOpened() {
|
|
1397
|
+
this._updateHint();
|
|
1398
|
+
this.eventBus.trigger("opened");
|
|
1399
|
+
},
|
|
1400
|
+
_onClosed: function onClosed() {
|
|
1401
|
+
this.input.clearHint();
|
|
1402
|
+
this.eventBus.trigger("closed");
|
|
1403
|
+
},
|
|
1404
|
+
_onFocused: function onFocused() {
|
|
1405
|
+
this.dropdown.empty();
|
|
1406
|
+
this.dropdown.open();
|
|
1407
|
+
},
|
|
1408
|
+
_onBlurred: function onBlurred() {
|
|
1409
|
+
this.dropdown.close();
|
|
1410
|
+
},
|
|
1411
|
+
_onEnterKeyed: function onEnterKeyed(type, $e) {
|
|
1412
|
+
var cursorDatum, topSuggestionDatum;
|
|
1413
|
+
cursorDatum = this.dropdown.getDatumForCursor();
|
|
1414
|
+
topSuggestionDatum = this.dropdown.getDatumForTopSuggestion();
|
|
1415
|
+
if (cursorDatum) {
|
|
1416
|
+
this._select(cursorDatum);
|
|
1417
|
+
$e.preventDefault();
|
|
1418
|
+
} else if (this.autoselect && topSuggestionDatum) {
|
|
1419
|
+
this._select(topSuggestionDatum);
|
|
1420
|
+
$e.preventDefault();
|
|
942
1421
|
}
|
|
943
1422
|
},
|
|
944
|
-
|
|
945
|
-
var
|
|
946
|
-
if (
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
match = beginsWithQuery.exec(hint);
|
|
952
|
-
this.inputView.setHintValue(inputValue + (match ? match[1] : ""));
|
|
1423
|
+
_onTabKeyed: function onTabKeyed(type, $e) {
|
|
1424
|
+
var datum;
|
|
1425
|
+
if (datum = this.dropdown.getDatumForCursor()) {
|
|
1426
|
+
this._select(datum);
|
|
1427
|
+
$e.preventDefault();
|
|
1428
|
+
} else {
|
|
1429
|
+
this._autocomplete();
|
|
953
1430
|
}
|
|
954
1431
|
},
|
|
955
|
-
|
|
956
|
-
this.
|
|
1432
|
+
_onEscKeyed: function onEscKeyed() {
|
|
1433
|
+
this.dropdown.close();
|
|
1434
|
+
this.input.resetInputValue();
|
|
957
1435
|
},
|
|
958
|
-
|
|
959
|
-
this.
|
|
1436
|
+
_onUpKeyed: function onUpKeyed() {
|
|
1437
|
+
var query = this.input.getQuery();
|
|
1438
|
+
if (!this.dropdown.isOpen && query.length >= this.minLength) {
|
|
1439
|
+
this.dropdown.update(query);
|
|
1440
|
+
}
|
|
1441
|
+
this.dropdown.open();
|
|
1442
|
+
this.dropdown.moveCursorUp();
|
|
960
1443
|
},
|
|
961
|
-
|
|
962
|
-
this.
|
|
1444
|
+
_onDownKeyed: function onDownKeyed() {
|
|
1445
|
+
var query = this.input.getQuery();
|
|
1446
|
+
if (!this.dropdown.isOpen && query.length >= this.minLength) {
|
|
1447
|
+
this.dropdown.update(query);
|
|
1448
|
+
}
|
|
1449
|
+
this.dropdown.open();
|
|
1450
|
+
this.dropdown.moveCursorDown();
|
|
963
1451
|
},
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
this.inputView.setInputValue(suggestion.value, true);
|
|
1452
|
+
_onLeftKeyed: function onLeftKeyed() {
|
|
1453
|
+
this.dir === "rtl" && this._autocomplete();
|
|
967
1454
|
},
|
|
968
|
-
|
|
969
|
-
this.
|
|
1455
|
+
_onRightKeyed: function onRightKeyed() {
|
|
1456
|
+
this.dir === "ltr" && this._autocomplete();
|
|
970
1457
|
},
|
|
971
|
-
|
|
972
|
-
this.
|
|
1458
|
+
_onQueryChanged: function onQueryChanged(e, query) {
|
|
1459
|
+
this.input.clearHint();
|
|
1460
|
+
this.dropdown.empty();
|
|
1461
|
+
query.length >= this.minLength && this.dropdown.update(query);
|
|
1462
|
+
this.dropdown.open();
|
|
1463
|
+
this._setLanguageDirection();
|
|
973
1464
|
},
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"]();
|
|
978
|
-
}
|
|
1465
|
+
_onWhitespaceChanged: function onWhitespaceChanged() {
|
|
1466
|
+
this._updateHint();
|
|
1467
|
+
this.dropdown.open();
|
|
979
1468
|
},
|
|
980
|
-
|
|
981
|
-
var
|
|
982
|
-
if (
|
|
983
|
-
this.
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset);
|
|
1469
|
+
_setLanguageDirection: function setLanguageDirection() {
|
|
1470
|
+
var dir;
|
|
1471
|
+
if (this.dir !== (dir = this.input.getLanguageDirection())) {
|
|
1472
|
+
this.dir = dir;
|
|
1473
|
+
this.$node.css("direction", dir);
|
|
1474
|
+
this.dropdown.setLanguageDirection(dir);
|
|
987
1475
|
}
|
|
988
1476
|
},
|
|
989
|
-
|
|
990
|
-
var
|
|
991
|
-
|
|
992
|
-
|
|
1477
|
+
_updateHint: function updateHint() {
|
|
1478
|
+
var datum, inputValue, query, escapedQuery, frontMatchRegEx, match;
|
|
1479
|
+
datum = this.dropdown.getDatumForTopSuggestion();
|
|
1480
|
+
if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {
|
|
1481
|
+
inputValue = this.input.getInputValue();
|
|
1482
|
+
query = Input.normalizeQuery(inputValue);
|
|
1483
|
+
escapedQuery = _.escapeRegExChars(query);
|
|
1484
|
+
frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i");
|
|
1485
|
+
match = frontMatchRegEx.exec(datum.value);
|
|
1486
|
+
this.input.setHintValue(inputValue + (match ? match[1] : ""));
|
|
993
1487
|
}
|
|
994
|
-
utils.each(this.datasets, function(i, dataset) {
|
|
995
|
-
dataset.getSuggestions(query, function(suggestions) {
|
|
996
|
-
if (query === that.inputView.getQuery()) {
|
|
997
|
-
that.dropdownView.renderSuggestions(dataset, suggestions);
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
});
|
|
1001
1488
|
},
|
|
1002
|
-
_autocomplete: function(
|
|
1003
|
-
var
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
query = this.inputView.getQuery();
|
|
1012
|
-
hint = this.inputView.getHintValue();
|
|
1013
|
-
if (hint !== "" && query !== hint) {
|
|
1014
|
-
suggestion = this.dropdownView.getFirstSuggestion();
|
|
1015
|
-
this.inputView.setInputValue(suggestion.value);
|
|
1016
|
-
this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset);
|
|
1489
|
+
_autocomplete: function autocomplete() {
|
|
1490
|
+
var hint, query, datum;
|
|
1491
|
+
hint = this.input.getHintValue();
|
|
1492
|
+
query = this.input.getQuery();
|
|
1493
|
+
if (hint && query !== hint && this.input.isCursorAtEnd()) {
|
|
1494
|
+
datum = this.dropdown.getDatumForTopSuggestion();
|
|
1495
|
+
datum && this.input.setInputValue(datum.value);
|
|
1496
|
+
this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName);
|
|
1017
1497
|
}
|
|
1018
1498
|
},
|
|
1019
|
-
|
|
1020
|
-
this.
|
|
1499
|
+
_select: function select(datum) {
|
|
1500
|
+
this.input.clearHint();
|
|
1501
|
+
this.input.setQuery(datum.value);
|
|
1502
|
+
this.input.setInputValue(datum.value, true);
|
|
1503
|
+
this._setLanguageDirection();
|
|
1504
|
+
this.eventBus.trigger("selected", datum.raw, datum.datasetName);
|
|
1505
|
+
this.dropdown.close();
|
|
1506
|
+
_.defer(_.bind(this.dropdown.empty, this.dropdown));
|
|
1507
|
+
},
|
|
1508
|
+
open: function open() {
|
|
1509
|
+
this.dropdown.open();
|
|
1510
|
+
},
|
|
1511
|
+
close: function close() {
|
|
1512
|
+
this.dropdown.close();
|
|
1021
1513
|
},
|
|
1022
|
-
|
|
1023
|
-
this.
|
|
1024
|
-
|
|
1514
|
+
getQuery: function getQuery() {
|
|
1515
|
+
return this.input.getQuery();
|
|
1516
|
+
},
|
|
1517
|
+
setQuery: function setQuery(val) {
|
|
1518
|
+
this.input.setInputValue(val);
|
|
1519
|
+
},
|
|
1520
|
+
destroy: function destroy() {
|
|
1521
|
+
this.input.destroy();
|
|
1522
|
+
this.dropdown.destroy();
|
|
1025
1523
|
destroyDomStructure(this.$node);
|
|
1026
1524
|
this.$node = null;
|
|
1027
|
-
},
|
|
1028
|
-
setQuery: function(query) {
|
|
1029
|
-
this.inputView.setQuery(query);
|
|
1030
|
-
this.inputView.setInputValue(query);
|
|
1031
|
-
this._clearHint();
|
|
1032
|
-
this._clearSuggestions();
|
|
1033
|
-
this._getSuggestions();
|
|
1034
1525
|
}
|
|
1035
1526
|
});
|
|
1036
|
-
return
|
|
1037
|
-
function buildDomStructure(input) {
|
|
1038
|
-
var $
|
|
1039
|
-
$
|
|
1040
|
-
$
|
|
1041
|
-
$
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
backgroundOrigin: $input.css("background-origin"),
|
|
1047
|
-
backgroundPosition: $input.css("background-position"),
|
|
1048
|
-
backgroundRepeat: $input.css("background-repeat"),
|
|
1049
|
-
backgroundSize: $input.css("background-size")
|
|
1527
|
+
return Typeahead;
|
|
1528
|
+
function buildDomStructure(input, withHint) {
|
|
1529
|
+
var $input, $wrapper, $dropdown, $hint;
|
|
1530
|
+
$input = $(input);
|
|
1531
|
+
$wrapper = $(html.wrapper).css(css.wrapper);
|
|
1532
|
+
$dropdown = $(html.dropdown).css(css.dropdown);
|
|
1533
|
+
$hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));
|
|
1534
|
+
$hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({
|
|
1535
|
+
autocomplete: "off",
|
|
1536
|
+
spellcheck: "false"
|
|
1050
1537
|
});
|
|
1051
|
-
$input.data(
|
|
1538
|
+
$input.data(attrsKey, {
|
|
1052
1539
|
dir: $input.attr("dir"),
|
|
1053
1540
|
autocomplete: $input.attr("autocomplete"),
|
|
1054
1541
|
spellcheck: $input.attr("spellcheck"),
|
|
1055
1542
|
style: $input.attr("style")
|
|
1056
1543
|
});
|
|
1057
|
-
$input.addClass("tt-
|
|
1544
|
+
$input.addClass("tt-input").attr({
|
|
1058
1545
|
autocomplete: "off",
|
|
1059
1546
|
spellcheck: false
|
|
1060
|
-
}).css(css.
|
|
1547
|
+
}).css(withHint ? css.input : css.inputWithNoHint);
|
|
1061
1548
|
try {
|
|
1062
1549
|
!$input.attr("dir") && $input.attr("dir", "auto");
|
|
1063
1550
|
} catch (e) {}
|
|
1064
|
-
return $input.wrap($wrapper).parent().prepend($hint).append($dropdown);
|
|
1551
|
+
return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown);
|
|
1552
|
+
}
|
|
1553
|
+
function getBackgroundStyles($el) {
|
|
1554
|
+
return {
|
|
1555
|
+
backgroundAttachment: $el.css("background-attachment"),
|
|
1556
|
+
backgroundClip: $el.css("background-clip"),
|
|
1557
|
+
backgroundColor: $el.css("background-color"),
|
|
1558
|
+
backgroundImage: $el.css("background-image"),
|
|
1559
|
+
backgroundOrigin: $el.css("background-origin"),
|
|
1560
|
+
backgroundPosition: $el.css("background-position"),
|
|
1561
|
+
backgroundRepeat: $el.css("background-repeat"),
|
|
1562
|
+
backgroundSize: $el.css("background-size")
|
|
1563
|
+
};
|
|
1065
1564
|
}
|
|
1066
1565
|
function destroyDomStructure($node) {
|
|
1067
|
-
var $input = $node.find(".tt-
|
|
1068
|
-
|
|
1069
|
-
|
|
1566
|
+
var $input = $node.find(".tt-input");
|
|
1567
|
+
_.each($input.data(attrsKey), function(val, key) {
|
|
1568
|
+
_.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
|
|
1070
1569
|
});
|
|
1071
|
-
$input.detach().removeData(
|
|
1570
|
+
$input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node);
|
|
1072
1571
|
$node.remove();
|
|
1073
1572
|
}
|
|
1074
1573
|
}();
|
|
1075
1574
|
(function() {
|
|
1076
|
-
var
|
|
1575
|
+
var old, typeaheadKey, methods;
|
|
1576
|
+
old = $.fn.typeahead;
|
|
1577
|
+
typeaheadKey = "ttTypeahead";
|
|
1077
1578
|
methods = {
|
|
1078
|
-
initialize: function(
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
if (o.name) {
|
|
1087
|
-
cache[o.name] = dataset;
|
|
1088
|
-
}
|
|
1089
|
-
return dataset;
|
|
1090
|
-
});
|
|
1091
|
-
return this.each(initialize);
|
|
1092
|
-
function initialize() {
|
|
1093
|
-
var $input = $(this), deferreds, eventBus = new EventBus({
|
|
1094
|
-
el: $input
|
|
1095
|
-
});
|
|
1096
|
-
deferreds = utils.map(datasets, function(dataset) {
|
|
1097
|
-
return dataset.initialize();
|
|
1579
|
+
initialize: function initialize(o, datasets) {
|
|
1580
|
+
datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
|
|
1581
|
+
o = o || {};
|
|
1582
|
+
return this.each(attach);
|
|
1583
|
+
function attach() {
|
|
1584
|
+
var $input = $(this), eventBus, typeahead;
|
|
1585
|
+
_.each(datasets, function(d) {
|
|
1586
|
+
d.highlight = !!o.highlight;
|
|
1098
1587
|
});
|
|
1099
|
-
|
|
1588
|
+
typeahead = new Typeahead({
|
|
1100
1589
|
input: $input,
|
|
1101
1590
|
eventBus: eventBus = new EventBus({
|
|
1102
1591
|
el: $input
|
|
1103
1592
|
}),
|
|
1593
|
+
withHint: _.isUndefined(o.hint) ? true : !!o.hint,
|
|
1594
|
+
minLength: o.minLength,
|
|
1595
|
+
autoselect: o.autoselect,
|
|
1104
1596
|
datasets: datasets
|
|
1105
|
-
}));
|
|
1106
|
-
$.when.apply($, deferreds).always(function() {
|
|
1107
|
-
utils.defer(function() {
|
|
1108
|
-
eventBus.trigger("initialized");
|
|
1109
|
-
});
|
|
1110
1597
|
});
|
|
1598
|
+
$input.data(typeaheadKey, typeahead);
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
open: function open() {
|
|
1602
|
+
return this.each(openTypeahead);
|
|
1603
|
+
function openTypeahead() {
|
|
1604
|
+
var $input = $(this), typeahead;
|
|
1605
|
+
if (typeahead = $input.data(typeaheadKey)) {
|
|
1606
|
+
typeahead.open();
|
|
1607
|
+
}
|
|
1111
1608
|
}
|
|
1112
1609
|
},
|
|
1113
|
-
|
|
1114
|
-
return this.each(
|
|
1115
|
-
function
|
|
1116
|
-
var $
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1119
|
-
$this.removeData(viewKey);
|
|
1610
|
+
close: function close() {
|
|
1611
|
+
return this.each(closeTypeahead);
|
|
1612
|
+
function closeTypeahead() {
|
|
1613
|
+
var $input = $(this), typeahead;
|
|
1614
|
+
if (typeahead = $input.data(typeaheadKey)) {
|
|
1615
|
+
typeahead.close();
|
|
1120
1616
|
}
|
|
1121
1617
|
}
|
|
1122
1618
|
},
|
|
1123
|
-
|
|
1124
|
-
return this.each(setQuery);
|
|
1619
|
+
val: function val(newVal) {
|
|
1620
|
+
return !arguments.length ? getQuery(this.first()) : this.each(setQuery);
|
|
1125
1621
|
function setQuery() {
|
|
1126
|
-
var
|
|
1127
|
-
|
|
1622
|
+
var $input = $(this), typeahead;
|
|
1623
|
+
if (typeahead = $input.data(typeaheadKey)) {
|
|
1624
|
+
typeahead.setQuery(newVal);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function getQuery($input) {
|
|
1628
|
+
var typeahead, query;
|
|
1629
|
+
if (typeahead = $input.data(typeaheadKey)) {
|
|
1630
|
+
query = typeahead.getQuery();
|
|
1631
|
+
}
|
|
1632
|
+
return query;
|
|
1633
|
+
}
|
|
1634
|
+
},
|
|
1635
|
+
destroy: function destroy() {
|
|
1636
|
+
return this.each(unattach);
|
|
1637
|
+
function unattach() {
|
|
1638
|
+
var $input = $(this), typeahead;
|
|
1639
|
+
if (typeahead = $input.data(typeaheadKey)) {
|
|
1640
|
+
typeahead.destroy();
|
|
1641
|
+
$input.removeData(typeaheadKey);
|
|
1642
|
+
}
|
|
1128
1643
|
}
|
|
1129
1644
|
}
|
|
1130
1645
|
};
|
|
1131
|
-
|
|
1646
|
+
$.fn.typeahead = function(method) {
|
|
1132
1647
|
if (methods[method]) {
|
|
1133
1648
|
return methods[method].apply(this, [].slice.call(arguments, 1));
|
|
1134
1649
|
} else {
|
|
1135
1650
|
return methods.initialize.apply(this, arguments);
|
|
1136
1651
|
}
|
|
1137
1652
|
};
|
|
1653
|
+
$.fn.typeahead.noConflict = function noConflict() {
|
|
1654
|
+
$.fn.typeahead = old;
|
|
1655
|
+
return this;
|
|
1656
|
+
};
|
|
1138
1657
|
})();
|
|
1139
1658
|
})(window.jQuery);
|