scratch – Blame information for rev 134
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
134 | office | 1 | /*! |
2 | * typeahead.js 0.10.1 |
||
3 | * https://github.com/twitter/typeahead.js |
||
4 | * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT |
||
5 | */ |
||
6 | |||
7 | (function($) { |
||
8 | var _ = { |
||
9 | isMsie: function() { |
||
10 | return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; |
||
11 | }, |
||
12 | isBlankString: function(str) { |
||
13 | return !str || /^\s*$/.test(str); |
||
14 | }, |
||
15 | escapeRegExChars: function(str) { |
||
16 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); |
||
17 | }, |
||
18 | isString: function(obj) { |
||
19 | return typeof obj === "string"; |
||
20 | }, |
||
21 | isNumber: function(obj) { |
||
22 | return typeof obj === "number"; |
||
23 | }, |
||
24 | isArray: $.isArray, |
||
25 | isFunction: $.isFunction, |
||
26 | isObject: $.isPlainObject, |
||
27 | isUndefined: function(obj) { |
||
28 | return typeof obj === "undefined"; |
||
29 | }, |
||
30 | bind: $.proxy, |
||
31 | each: function(collection, cb) { |
||
32 | $.each(collection, reverseArgs); |
||
33 | function reverseArgs(index, value) { |
||
34 | return cb(value, index); |
||
35 | } |
||
36 | }, |
||
37 | map: $.map, |
||
38 | filter: $.grep, |
||
39 | every: function(obj, test) { |
||
40 | var result = true; |
||
41 | if (!obj) { |
||
42 | return result; |
||
43 | } |
||
44 | $.each(obj, function(key, val) { |
||
45 | if (!(result = test.call(null, val, key, obj))) { |
||
46 | return false; |
||
47 | } |
||
48 | }); |
||
49 | return !!result; |
||
50 | }, |
||
51 | some: function(obj, test) { |
||
52 | var result = false; |
||
53 | if (!obj) { |
||
54 | return result; |
||
55 | } |
||
56 | $.each(obj, function(key, val) { |
||
57 | if (result = test.call(null, val, key, obj)) { |
||
58 | return false; |
||
59 | } |
||
60 | }); |
||
61 | return !!result; |
||
62 | }, |
||
63 | mixin: $.extend, |
||
64 | getUniqueId: function() { |
||
65 | var counter = 0; |
||
66 | return function() { |
||
67 | return counter++; |
||
68 | }; |
||
69 | }(), |
||
70 | templatify: function templatify(obj) { |
||
71 | return $.isFunction(obj) ? obj : template; |
||
72 | function template() { |
||
73 | return String(obj); |
||
74 | } |
||
75 | }, |
||
76 | defer: function(fn) { |
||
77 | setTimeout(fn, 0); |
||
78 | }, |
||
79 | debounce: function(func, wait, immediate) { |
||
80 | var timeout, result; |
||
81 | return function() { |
||
82 | var context = this, args = arguments, later, callNow; |
||
83 | later = function() { |
||
84 | timeout = null; |
||
85 | if (!immediate) { |
||
86 | result = func.apply(context, args); |
||
87 | } |
||
88 | }; |
||
89 | callNow = immediate && !timeout; |
||
90 | clearTimeout(timeout); |
||
91 | timeout = setTimeout(later, wait); |
||
92 | if (callNow) { |
||
93 | result = func.apply(context, args); |
||
94 | } |
||
95 | return result; |
||
96 | }; |
||
97 | }, |
||
98 | throttle: function(func, wait) { |
||
99 | var context, args, timeout, result, previous, later; |
||
100 | previous = 0; |
||
101 | later = function() { |
||
102 | previous = new Date(); |
||
103 | timeout = null; |
||
104 | result = func.apply(context, args); |
||
105 | }; |
||
106 | return function() { |
||
107 | var now = new Date(), remaining = wait - (now - previous); |
||
108 | context = this; |
||
109 | args = arguments; |
||
110 | if (remaining <= 0) { |
||
111 | clearTimeout(timeout); |
||
112 | timeout = null; |
||
113 | previous = now; |
||
114 | result = func.apply(context, args); |
||
115 | } else if (!timeout) { |
||
116 | timeout = setTimeout(later, remaining); |
||
117 | } |
||
118 | return result; |
||
119 | }; |
||
120 | }, |
||
121 | noop: function() {} |
||
122 | }; |
||
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]; |
||
137 | } |
||
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++; |
||
146 | } |
||
147 | }, |
||
148 | get: function get(key) { |
||
149 | var node = this.hash[key]; |
||
150 | if (node) { |
||
151 | this.list.moveToFront(node); |
||
152 | return node.val; |
||
153 | } |
||
154 | } |
||
155 | }); |
||
156 | function List() { |
||
157 | this.head = this.tail = null; |
||
158 | } |
||
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); |
||
175 | } |
||
176 | }); |
||
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); |
||
184 | var PersistentStorage = function() { |
||
185 | var ls, methods; |
||
186 | try { |
||
187 | ls = window.localStorage; |
||
188 | ls.setItem("~~~", "!"); |
||
189 | ls.removeItem("~~~"); |
||
190 | } catch (err) { |
||
191 | ls = null; |
||
192 | } |
||
193 | function PersistentStorage(namespace) { |
||
194 | this.prefix = [ "__", namespace, "__" ].join(""); |
||
195 | this.ttlKey = "__ttl__"; |
||
196 | this.keyMatcher = new RegExp("^" + this.prefix); |
||
197 | } |
||
198 | if (ls && window.JSON) { |
||
199 | methods = { |
||
200 | _prefix: function(key) { |
||
201 | return this.prefix + key; |
||
202 | }, |
||
203 | _ttlKey: function(key) { |
||
204 | return this._prefix(key) + this.ttlKey; |
||
205 | }, |
||
206 | get: function(key) { |
||
207 | if (this.isExpired(key)) { |
||
208 | this.remove(key); |
||
209 | } |
||
210 | return decode(ls.getItem(this._prefix(key))); |
||
211 | }, |
||
212 | set: function(key, val, ttl) { |
||
213 | if (_.isNumber(ttl)) { |
||
214 | ls.setItem(this._ttlKey(key), encode(now() + ttl)); |
||
215 | } else { |
||
216 | ls.removeItem(this._ttlKey(key)); |
||
217 | } |
||
218 | return ls.setItem(this._prefix(key), encode(val)); |
||
219 | }, |
||
220 | remove: function(key) { |
||
221 | ls.removeItem(this._ttlKey(key)); |
||
222 | ls.removeItem(this._prefix(key)); |
||
223 | return this; |
||
224 | }, |
||
225 | clear: function() { |
||
226 | var i, key, keys = [], len = ls.length; |
||
227 | for (i = 0; i < len; i++) { |
||
228 | if ((key = ls.key(i)).match(this.keyMatcher)) { |
||
229 | keys.push(key.replace(this.keyMatcher, "")); |
||
230 | } |
||
231 | } |
||
232 | for (i = keys.length; i--; ) { |
||
233 | this.remove(keys[i]); |
||
234 | } |
||
235 | return this; |
||
236 | }, |
||
237 | isExpired: function(key) { |
||
238 | var ttl = decode(ls.getItem(this._ttlKey(key))); |
||
239 | return _.isNumber(ttl) && now() > ttl ? true : false; |
||
240 | } |
||
241 | }; |
||
242 | } else { |
||
243 | methods = { |
||
244 | get: _.noop, |
||
245 | set: _.noop, |
||
246 | remove: _.noop, |
||
247 | clear: _.noop, |
||
248 | isExpired: _.noop |
||
249 | }; |
||
250 | } |
||
251 | _.mixin(PersistentStorage.prototype, methods); |
||
252 | return PersistentStorage; |
||
253 | function now() { |
||
254 | return new Date().getTime(); |
||
255 | } |
||
256 | function encode(val) { |
||
257 | return JSON.stringify(_.isUndefined(val) ? null : val); |
||
258 | } |
||
259 | function decode(val) { |
||
260 | return JSON.parse(val); |
||
261 | } |
||
262 | }(); |
||
263 | var Transport = function() { |
||
264 | var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); |
||
265 | function Transport(o) { |
||
266 | o = o || {}; |
||
267 | this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; |
||
268 | this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; |
||
269 | } |
||
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); |
||
284 | } else { |
||
285 | this.onDeckRequestArgs = [].slice.call(arguments, 0); |
||
286 | } |
||
287 | function done(resp) { |
||
288 | cb && cb(resp); |
||
289 | requestCache.set(url, resp); |
||
290 | } |
||
291 | function always() { |
||
292 | pendingRequestsCount--; |
||
293 | delete pendingRequests[url]; |
||
294 | if (that.onDeckRequestArgs) { |
||
295 | that._get.apply(that, that.onDeckRequestArgs); |
||
296 | that.onDeckRequestArgs = null; |
||
297 | } |
||
298 | } |
||
299 | }, |
||
300 | get: function(url, o, cb) { |
||
301 | var that = this, resp; |
||
302 | if (_.isFunction(o)) { |
||
303 | cb = o; |
||
304 | o = {}; |
||
305 | } |
||
306 | if (resp = requestCache.get(url)) { |
||
307 | _.defer(function() { |
||
308 | cb && cb(resp); |
||
309 | }); |
||
310 | } else { |
||
311 | this._get(url, o, cb); |
||
312 | } |
||
313 | return !!resp; |
||
314 | } |
||
315 | }); |
||
316 | return Transport; |
||
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 | }; |
||
333 | } |
||
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(); |
||
345 | } |
||
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 | } |
||
446 | } |
||
447 | }(); |
||
448 | var oParser = function() { |
||
449 | return { |
||
450 | local: getLocal, |
||
451 | prefetch: getPrefetch, |
||
452 | remote: getRemote |
||
453 | }; |
||
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 | }; |
||
516 | } |
||
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) { |
||
528 | $.error("one of local, prefetch, or remote is required"); |
||
529 | } |
||
530 | this.limit = o.limit || 5; |
||
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; |
||
542 | } |
||
543 | Bloodhound.tokenizers = { |
||
544 | whitespace: function whitespaceTokenizer(s) { |
||
545 | return s.split(/\s+/); |
||
546 | }, |
||
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); |
||
556 | deferred = $.Deferred().resolve(); |
||
557 | } else { |
||
558 | deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); |
||
559 | } |
||
560 | return deferred; |
||
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); |
||
566 | } |
||
567 | }, |
||
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); |
||
577 | } |
||
578 | }, |
||
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 | } |
||
585 | }, |
||
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); |
||
592 | } |
||
593 | isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; |
||
594 | return stored.data && !isExpired ? stored.data : null; |
||
595 | }, |
||
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); |
||
600 | this.transport = this.remote ? new Transport(this.remote) : null; |
||
601 | this.initialize = function initialize() { |
||
602 | return deferred.promise(); |
||
603 | }; |
||
604 | return deferred.promise(); |
||
605 | function addLocalToIndex() { |
||
606 | that.add(that.local); |
||
607 | } |
||
608 | }, |
||
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); |
||
618 | } |
||
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); |
||
626 | }); |
||
627 | !isDuplicate && matchesWithBackfill.push(remoteMatch); |
||
628 | return matchesWithBackfill.length < that.limit; |
||
629 | }); |
||
630 | cb && cb(that.sorter(matchesWithBackfill)); |
||
631 | } |
||
632 | }, |
||
633 | ttAdapter: function ttAdapter() { |
||
634 | return _.bind(this.get, this); |
||
635 | } |
||
636 | }); |
||
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 | }; |
||
805 | } else { |
||
806 | nextTickFn = function nextTickSetTimeout(fn) { |
||
807 | setTimeout(function() { |
||
808 | fn(); |
||
809 | }, 0); |
||
810 | }; |
||
811 | } |
||
812 | return nextTickFn; |
||
813 | } |
||
814 | function bindContext(fn, context) { |
||
815 | return fn.bind ? fn.bind(context) : function() { |
||
816 | fn.apply(context, [].slice.call(arguments, 0)); |
||
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); |
||
892 | this.$hint = $(o.hint); |
||
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); |
||
899 | } else { |
||
900 | this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { |
||
901 | if (specialKeyCodeMap[$e.which || $e.keyCode]) { |
||
902 | return; |
||
903 | } |
||
904 | _.defer(_.bind(that._onInput, that, $e)); |
||
905 | }); |
||
906 | } |
||
907 | this.query = this.$input.val(); |
||
908 | this.$overflowHelper = buildOverflowHelper(this.$input); |
||
909 | } |
||
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) { |
||
919 | this.trigger("focused"); |
||
920 | }, |
||
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 | } |
||
927 | }, |
||
928 | _onInput: function onInput($e) { |
||
929 | this._checkInputValue(); |
||
930 | }, |
||
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; |
||
947 | } |
||
948 | preventDefault && $e.preventDefault(); |
||
949 | }, |
||
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 | } |
||
972 | }, |
||
973 | focus: function focus() { |
||
974 | this.$input.focus(); |
||
975 | }, |
||
976 | blur: function blur() { |
||
977 | this.$input.blur(); |
||
978 | }, |
||
979 | getQuery: function getQuery() { |
||
980 | return this.query; |
||
981 | }, |
||
982 | setQuery: function setQuery(query) { |
||
983 | this.query = query; |
||
984 | }, |
||
985 | getInputValue: function getInputValue() { |
||
986 | return this.$input.val(); |
||
987 | }, |
||
988 | setInputValue: function setInputValue(value, silent) { |
||
989 | this.$input.val(value); |
||
990 | !silent && this._checkInputValue(); |
||
991 | }, |
||
992 | getHintValue: function getHintValue() { |
||
993 | return this.$hint.val(); |
||
994 | }, |
||
995 | setHintValue: function setHintValue(value) { |
||
996 | this.$hint.val(value); |
||
997 | }, |
||
998 | resetInputValue: function resetInputValue() { |
||
999 | this.$input.val(this.query); |
||
1000 | }, |
||
1001 | clearHint: function clearHint() { |
||
1002 | this.$hint.val(""); |
||
1003 | }, |
||
1004 | getLanguageDirection: function getLanguageDirection() { |
||
1005 | return (this.$input.css("direction") || "ltr").toLowerCase(); |
||
1006 | }, |
||
1007 | hasOverflow: function hasOverflow() { |
||
1008 | var constraint = this.$input.width() - 2; |
||
1009 | this.$overflowHelper.text(this.getInputValue()); |
||
1010 | return this.$overflowHelper.width() >= constraint; |
||
1011 | }, |
||
1012 | isCursorAtEnd: function() { |
||
1013 | var valueLength, selectionStart, range; |
||
1014 | valueLength = this.$input.val().length; |
||
1015 | selectionStart = this.$input[0].selectionStart; |
||
1016 | if (_.isNumber(selectionStart)) { |
||
1017 | return selectionStart === valueLength; |
||
1018 | } else if (document.selection) { |
||
1019 | range = document.selection.createRange(); |
||
1020 | range.moveStart("character", -valueLength); |
||
1021 | return valueLength === range.text.length; |
||
1022 | } |
||
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; |
||
1029 | } |
||
1030 | }); |
||
1031 | return Input; |
||
1032 | function buildOverflowHelper($input) { |
||
1033 | return $('<pre aria-hidden="true"></pre>').css({ |
||
1034 | position: "absolute", |
||
1035 | visibility: "hidden", |
||
1036 | whiteSpace: "nowrap", |
||
1037 | fontFamily: $input.css("font-family"), |
||
1038 | fontSize: $input.css("font-size"), |
||
1039 | fontStyle: $input.css("font-style"), |
||
1040 | fontVariant: $input.css("font-variant"), |
||
1041 | fontWeight: $input.css("font-weight"), |
||
1042 | wordSpacing: $input.css("word-spacing"), |
||
1043 | letterSpacing: $input.css("letter-spacing"), |
||
1044 | textIndent: $input.css("text-indent"), |
||
1045 | textRendering: $input.css("text-rendering"), |
||
1046 | textTransform: $input.css("text-transform") |
||
1047 | }).insertAfter($input); |
||
1048 | } |
||
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; |
||
1054 | } |
||
1055 | }(); |
||
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"); |
||
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); |
||
1080 | }; |
||
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 | } |
||
1189 | this.isOpen = false; |
||
1190 | this.isEmpty = true; |
||
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 | }); |
||
1200 | } |
||
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 | } |
||
1219 | }, |
||
1220 | _hide: function() { |
||
1221 | this.$menu.hide(); |
||
1222 | }, |
||
1223 | _show: function() { |
||
1224 | this.$menu.css("display", "block"); |
||
1225 | }, |
||
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"); |
||
1238 | }, |
||
1239 | _moveCursor: function moveCursor(increment) { |
||
1240 | var $suggestions, $oldCursor, newCursorIndex, $newCursor; |
||
1241 | if (!this.isOpen) { |
||
1242 | return; |
||
1243 | } |
||
1244 | $oldCursor = this._getCursor(); |
||
1245 | $suggestions = this._getSuggestions(); |
||
1246 | this._removeCursor(); |
||
1247 | newCursorIndex = $suggestions.index($oldCursor) + increment; |
||
1248 | newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; |
||
1249 | if (newCursorIndex === -1) { |
||
1250 | this.trigger("cursorRemoved"); |
||
1251 | return; |
||
1252 | } else if (newCursorIndex < -1) { |
||
1253 | newCursorIndex = $suggestions.length - 1; |
||
1254 | } |
||
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); |
||
1264 | if (elTop < 0) { |
||
1265 | this.$menu.scrollTop(menuScrollTop + elTop); |
||
1266 | } else if (menuHeight < elBottom) { |
||
1267 | this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); |
||
1268 | } |
||
1269 | }, |
||
1270 | close: function close() { |
||
1271 | if (this.isOpen) { |
||
1272 | this.isOpen = false; |
||
1273 | this._removeCursor(); |
||
1274 | this._hide(); |
||
1275 | this.trigger("closed"); |
||
1276 | } |
||
1277 | }, |
||
1278 | open: function open() { |
||
1279 | if (!this.isOpen) { |
||
1280 | this.isOpen = true; |
||
1281 | !this.isEmpty && this._show(); |
||
1282 | this.trigger("opened"); |
||
1283 | } |
||
1284 | }, |
||
1285 | setLanguageDirection: function setLanguageDirection(dir) { |
||
1286 | this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); |
||
1287 | }, |
||
1288 | moveCursorUp: function moveCursorUp() { |
||
1289 | this._moveCursor(-1); |
||
1290 | }, |
||
1291 | moveCursorDown: function moveCursorDown() { |
||
1292 | this._moveCursor(+1); |
||
1293 | }, |
||
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 | }; |
||
1302 | } |
||
1303 | return datum; |
||
1304 | }, |
||
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(); |
||
1333 | } |
||
1334 | } |
||
1335 | }); |
||
1336 | return Dropdown; |
||
1337 | function initializeDataset(oDataset) { |
||
1338 | return new Dataset(oDataset); |
||
1339 | } |
||
1340 | }(); |
||
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"); |
||
1348 | } |
||
1349 | this.autoselect = !!o.autoselect; |
||
1350 | this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; |
||
1351 | this.$node = buildDomStructure(o.input, o.withHint); |
||
1352 | $menu = this.$node.find(".tt-dropdown-menu"); |
||
1353 | $input = this.$node.find(".tt-input"); |
||
1354 | $hint = this.$node.find(".tt-hint"); |
||
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({ |
||
1363 | input: $input, |
||
1364 | hint: $hint |
||
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 | }); |
||
1375 | } |
||
1376 | _.mixin(Typeahead.prototype, { |
||
1377 | _onSuggestionClicked: function onSuggestionClicked(type, $el) { |
||
1378 | var datum; |
||
1379 | if (datum = this.dropdown.getDatumForSuggestion($el)) { |
||
1380 | this._select(datum); |
||
1381 | } |
||
1382 | }, |
||
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(); |
||
1421 | } |
||
1422 | }, |
||
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(); |
||
1430 | } |
||
1431 | }, |
||
1432 | _onEscKeyed: function onEscKeyed() { |
||
1433 | this.dropdown.close(); |
||
1434 | this.input.resetInputValue(); |
||
1435 | }, |
||
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(); |
||
1443 | }, |
||
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(); |
||
1451 | }, |
||
1452 | _onLeftKeyed: function onLeftKeyed() { |
||
1453 | this.dir === "rtl" && this._autocomplete(); |
||
1454 | }, |
||
1455 | _onRightKeyed: function onRightKeyed() { |
||
1456 | this.dir === "ltr" && this._autocomplete(); |
||
1457 | }, |
||
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(); |
||
1464 | }, |
||
1465 | _onWhitespaceChanged: function onWhitespaceChanged() { |
||
1466 | this._updateHint(); |
||
1467 | this.dropdown.open(); |
||
1468 | }, |
||
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); |
||
1475 | } |
||
1476 | }, |
||
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] : "")); |
||
1487 | } |
||
1488 | }, |
||
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); |
||
1497 | } |
||
1498 | }, |
||
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(); |
||
1513 | }, |
||
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(); |
||
1523 | destroyDomStructure(this.$node); |
||
1524 | this.$node = null; |
||
1525 | } |
||
1526 | }); |
||
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" |
||
1537 | }); |
||
1538 | $input.data(attrsKey, { |
||
1539 | dir: $input.attr("dir"), |
||
1540 | autocomplete: $input.attr("autocomplete"), |
||
1541 | spellcheck: $input.attr("spellcheck"), |
||
1542 | style: $input.attr("style") |
||
1543 | }); |
||
1544 | $input.addClass("tt-input").attr({ |
||
1545 | autocomplete: "off", |
||
1546 | spellcheck: false |
||
1547 | }).css(withHint ? css.input : css.inputWithNoHint); |
||
1548 | try { |
||
1549 | !$input.attr("dir") && $input.attr("dir", "auto"); |
||
1550 | } catch (e) {} |
||
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 | }; |
||
1564 | } |
||
1565 | function destroyDomStructure($node) { |
||
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); |
||
1569 | }); |
||
1570 | $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); |
||
1571 | $node.remove(); |
||
1572 | } |
||
1573 | }(); |
||
1574 | (function() { |
||
1575 | var old, typeaheadKey, methods; |
||
1576 | old = $.fn.typeahead; |
||
1577 | typeaheadKey = "ttTypeahead"; |
||
1578 | methods = { |
||
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; |
||
1587 | }); |
||
1588 | typeahead = new Typeahead({ |
||
1589 | input: $input, |
||
1590 | eventBus: eventBus = new EventBus({ |
||
1591 | el: $input |
||
1592 | }), |
||
1593 | withHint: _.isUndefined(o.hint) ? true : !!o.hint, |
||
1594 | minLength: o.minLength, |
||
1595 | autoselect: o.autoselect, |
||
1596 | datasets: datasets |
||
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 | } |
||
1608 | } |
||
1609 | }, |
||
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(); |
||
1616 | } |
||
1617 | } |
||
1618 | }, |
||
1619 | val: function val(newVal) { |
||
1620 | return !arguments.length ? getQuery(this.first()) : this.each(setQuery); |
||
1621 | function setQuery() { |
||
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 | } |
||
1643 | } |
||
1644 | } |
||
1645 | }; |
||
1646 | $.fn.typeahead = function(method) { |
||
1647 | if (methods[method]) { |
||
1648 | return methods[method].apply(this, [].slice.call(arguments, 1)); |
||
1649 | } else { |
||
1650 | return methods.initialize.apply(this, arguments); |
||
1651 | } |
||
1652 | }; |
||
1653 | $.fn.typeahead.noConflict = function noConflict() { |
||
1654 | $.fn.typeahead = old; |
||
1655 | return this; |
||
1656 | }; |
||
1657 | })(); |
||
1658 | })(window.jQuery); |