Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14218 anikendra 1
//  ----------------------------------------------------------------------------
2
//
3
//  bootstrap-typeahead.js  
4
//
5
//  Twitter Bootstrap Typeahead Plugin
6
//  v1.2.2
7
//  https://github.com/tcrosen/twitter-bootstrap-typeahead
8
//
9
//
10
//  Author
11
//  ----------
12
//  Terry Rosen
13
//  tcrosen@gmail.com | @rerrify | github.com/tcrosen/
14
//
15
//
16
//  Description
17
//  ----------
18
//  Custom implementation of Twitter's Bootstrap Typeahead Plugin
19
//  http://twitter.github.com/bootstrap/javascript.html#typeahead
20
//
21
//
22
//  Requirements
23
//  ----------
24
//  jQuery 1.7+
25
//  Twitter Bootstrap 2.0+
26
//
27
//  ----------------------------------------------------------------------------
28
 
29
!
30
function ($) {
31
 
32
    "use strict";
33
 
34
    //------------------------------------------------------------------
35
    //
36
    //  Constructor
37
    //
38
    var Typeahead = function (element, options) {
39
        this.$element = $(element);
40
        this.options = $.extend(true, {}, $.fn.typeahead.defaults, options);
41
        this.$menu = $(this.options.menu).appendTo('body');
42
        this.shown = false;
43
 
44
        // Method overrides    
45
        this.eventSupported = this.options.eventSupported || this.eventSupported;
46
        this.grepper = this.options.grepper || this.grepper;
47
        this.highlighter = this.options.highlighter || this.highlighter;
48
        this.lookup = this.options.lookup || this.lookup;
49
        this.matcher = this.options.matcher || this.matcher;
50
        this.render = this.options.render || this.render;
51
        this.select = this.options.select || this.select;
52
        this.sorter = this.options.sorter || this.sorter;
53
        this.source = this.options.source || this.source;        
54
 
55
        if (!this.source.length) {
56
            var ajax = this.options.ajax;
57
 
58
            if (typeof ajax === 'string') {
59
                this.ajax = $.extend({}, $.fn.typeahead.defaults.ajax, { url: ajax });
60
            } else {
61
                this.ajax = $.extend({}, $.fn.typeahead.defaults.ajax, ajax);
62
            }
63
 
64
            if (!this.ajax.url) {
65
                this.ajax = null;
66
            }
67
        }
68
 
69
        this.listen();        
70
    }
71
 
72
    Typeahead.prototype = {
73
 
74
        constructor: Typeahead,
75
 
76
        //=============================================================================================================
77
        //
78
        //  Utils
79
        //
80
        //=============================================================================================================
81
 
82
        //------------------------------------------------------------------
83
        //
84
        //  Check if an event is supported by the browser eg. 'keypress'
85
        //  * This was included to handle the "exhaustive deprecation" of jQuery.browser in jQuery 1.8
86
        //
87
        eventSupported: function(eventName) {         
88
            var isSupported = (eventName in this.$element);
89
 
90
            if (!isSupported) {
91
              this.$element.setAttribute(eventName, 'return;');
92
              isSupported = typeof this.$element[eventName] === 'function';
93
            }
94
 
95
            return isSupported;
96
        },
97
 
98
        //=============================================================================================================
99
        //
100
        //  AJAX
101
        //
102
        //=============================================================================================================
103
 
104
        //------------------------------------------------------------------
105
        //
106
        //  Handle AJAX source 
107
        //
108
        ajaxer: function () { 
109
            var that = this,
110
                query = that.$element.val();
111
 
112
            if (query === that.query) {
113
                return that;
114
            }
115
 
116
            // Query changed
117
            that.query = query;
118
 
119
            // Cancel last timer if set
120
            if (that.ajax.timerId) {
121
                clearTimeout(that.ajax.timerId);
122
                that.ajax.timerId = null;
123
            }
124
 
125
            if (!query || query.length < that.ajax.triggerLength) {
126
                // Cancel the ajax callback if in progress
127
                if (that.ajax.xhr) {
128
                    that.ajax.xhr.abort();
129
                    that.ajax.xhr = null;
130
                    that.ajaxToggleLoadClass(false);
131
                }
132
 
133
                return that.shown ? that.hide() : that;
134
            }
135
 
136
            // Query is good to send, set a timer
137
            that.ajax.timerId = setTimeout(function() {
138
                $.proxy(that.ajaxExecute(query), that)
139
            }, that.ajax.timeout);
140
 
141
            return that;
142
        },
143
 
144
        //------------------------------------------------------------------
145
        //
146
        //  Execute an AJAX request
147
        //
148
        ajaxExecute: function(query) {
149
            this.ajaxToggleLoadClass(true);
150
 
151
            // Cancel last call if already in progress
152
            if (this.ajax.xhr) this.ajax.xhr.abort();
153
 
154
            var params = this.ajax.preDispatch ? this.ajax.preDispatch(query) : { query : query };
155
            var jAjax = (this.ajax.method === "post") ? $.post : $.get;
156
            this.ajax.xhr = jAjax(this.ajax.url, params, $.proxy(this.ajaxLookup, this));
157
            this.ajax.timerId = null;
158
        },
159
 
160
        //------------------------------------------------------------------
161
        //
162
        //  Perform a lookup in the AJAX results
163
        //
164
        ajaxLookup: function (data) { 
165
            var items;
166
 
167
            this.ajaxToggleLoadClass(false);
168
 
169
            if (!this.ajax.xhr) return;
170
 
171
            if (this.ajax.preProcess) {
172
                data = this.ajax.preProcess(data);
173
            }
174
 
175
            // Save for selection retreival
176
            this.ajax.data = data;
177
 
178
            items = this.grepper(this.ajax.data);
179
 
180
            if (!items || !items.length) {
181
                return this.shown ? this.hide() : this;
182
            }
183
 
184
            this.ajax.xhr = null;
185
 
186
            return this.render(items.slice(0, this.options.items)).show();
187
        },
188
 
189
        //------------------------------------------------------------------
190
        //
191
        //  Toggle the loading class
192
        //
193
        ajaxToggleLoadClass: function (enable) {
194
            if (!this.ajax.loadingClass) return;
195
            this.$element.toggleClass(this.ajax.loadingClass, enable);
196
        },
197
 
198
        //=============================================================================================================
199
        //
200
        //  Data manipulation
201
        //
202
        //=============================================================================================================
203
 
204
        //------------------------------------------------------------------
205
        //
206
        //  Search source
207
        //
208
        lookup: function (event) {
209
            var that = this,
210
                items;
211
 
212
            if (that.ajax) {
213
                that.ajaxer();
214
            }
215
            else {
216
                that.query = that.$element.val();
217
 
218
                if (!that.query) {
219
                    return that.shown ? that.hide() : that;
220
                }
221
 
222
                items = that.grepper(that.source);
223
 
224
                if (!items || !items.length) {
225
                    return that.shown ? that.hide() : that;
226
                }
227
 
228
                return that.render(items.slice(0, that.options.items)).show();
229
            }
230
        },
231
 
232
        //------------------------------------------------------------------
233
        //
234
        //  Filters relevent results 
235
        //
236
        grepper: function(data) {
237
            var that = this,
238
                items;
239
 
240
            if (data && data.length && !data[0].hasOwnProperty(that.options.display)) {                
241
                return null;
242
            } 
243
 
244
            items = $.grep(data, function (item) {
245
                return that.matcher(item[that.options.display], item);
246
            });
247
 
248
            return this.sorter(items);                
249
        },
250
 
251
        //------------------------------------------------------------------
252
        //
253
        //  Looks for a match in the source
254
        //
255
        matcher: function (item) {
256
            return ~item.toLowerCase().indexOf(this.query.toLowerCase());
257
        },
258
 
259
        //------------------------------------------------------------------
260
        //
261
        //  Sorts the results
262
        //
263
        sorter: function (items) {
264
            var that = this,
265
                beginswith = [],
266
                caseSensitive = [],
267
                caseInsensitive = [],
268
                item;
269
 
270
            while (item = items.shift()) {
271
                if (!item[that.options.display].toLowerCase().indexOf(this.query.toLowerCase())) {
272
                    beginswith.push(item);
273
                }
274
                else if (~item[that.options.display].indexOf(this.query)) {
275
                    caseSensitive.push(item);
276
                }
277
                else {
278
                    caseInsensitive.push(item);
279
                }
280
            }
281
 
282
            return beginswith.concat(caseSensitive, caseInsensitive);
283
        },       
284
 
285
        //=============================================================================================================
286
        //
287
        //  DOM manipulation
288
        //
289
        //=============================================================================================================
290
 
291
        //------------------------------------------------------------------
292
        //
293
        //  Shows the results list
294
        //
295
        show: function () {
296
            var pos = $.extend({}, this.$element.offset(), {
297
                height: this.$element[0].offsetHeight
298
            });
299
 
300
            this.$menu.css({
301
                top: pos.top + pos.height,
302
                left: pos.left
303
            });
304
 
305
            this.$menu.show();
306
            this.shown = true;
307
 
308
            return this;
309
        },
310
 
311
        //------------------------------------------------------------------
312
        //
313
        //  Hides the results list
314
        //
315
        hide: function () {
316
            this.$menu.hide();
317
            this.shown = false;
318
            return this;
319
        },
320
 
321
        //------------------------------------------------------------------
322
        //
323
        //  Highlights the match(es) within the results
324
        //
325
        highlighter: function (item) {
326
            var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
327
            return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
328
                return '<strong>' + match + '</strong>';
329
            });
330
        },
331
 
332
        //------------------------------------------------------------------
333
        //
334
        //  Renders the results list
335
        //
336
        render: function (items) {
337
            var that = this;
338
 
339
            items = $(items).map(function (i, item) {
340
                i = $(that.options.item).attr('data-value', item[that.options.val]);
341
                i.find('a').html(that.highlighter(item[that.options.display], item));
342
                return i[0];
343
            });
344
 
345
            items.first().addClass('active');
346
            this.$menu.html(items);
347
            return this;
348
        },
349
 
350
        //------------------------------------------------------------------
351
        //
352
        //  Item is selected
353
        //
354
        select: function () {
355
            var $selectedItem = this.$menu.find('.active');
356
            this.$element.val($selectedItem.text()).change();
357
            this.options.itemSelected($selectedItem, $selectedItem.attr('data-value'), $selectedItem.text());
358
            return this.hide();
359
        },
360
 
361
        //------------------------------------------------------------------
362
        //
363
        //  Selects the next result
364
        //
365
        next: function (event) {
366
            var active = this.$menu.find('.active').removeClass('active');
367
            var next = active.next();
368
 
369
            if (!next.length) {
370
                next = $(this.$menu.find('li')[0]);
371
            }
372
 
373
            next.addClass('active');
374
        },
375
 
376
        //------------------------------------------------------------------
377
        //
378
        //  Selects the previous result
379
        //
380
        prev: function (event) {
381
            var active = this.$menu.find('.active').removeClass('active');
382
            var prev = active.prev();
383
 
384
            if (!prev.length) {
385
                prev = this.$menu.find('li').last();
386
            }
387
 
388
            prev.addClass('active');
389
        },
390
 
391
        //=============================================================================================================
392
        //
393
        //  Events
394
        //
395
        //=============================================================================================================
396
 
397
        //------------------------------------------------------------------
398
        //
399
        //  Listens for user events
400
        //
401
        listen: function () {
402
            this.$element.on('blur', $.proxy(this.blur, this))
403
                         .on('keyup', $.proxy(this.keyup, this));
404
 
405
    		if (this.eventSupported('keydown')) {
406
				this.$element.on('keydown', $.proxy(this.keypress, this));
407
			} else {
408
				this.$element.on('keypress', $.proxy(this.keypress, this));
409
			}
410
 
411
            this.$menu.on('click', $.proxy(this.click, this))
412
                      .on('mouseenter', 'li', $.proxy(this.mouseenter, this));
413
        },
414
 
415
        //------------------------------------------------------------------
416
        //
417
        //  Handles a key being raised up
418
        //
419
        keyup: function (e) {
420
            e.stopPropagation();
421
            e.preventDefault();
422
 
423
            switch (e.keyCode) {
424
                case 40:
425
                    // down arrow
426
                case 38:
427
                    // up arrow
428
                    break;
429
                case 9:
430
                    // tab
431
                case 13:
432
                    // enter
433
                    if (!this.shown) {
434
                        return;
435
                    }
436
                    this.select();
437
                    break;
438
                case 27:
439
                    // escape
440
                    this.hide();
441
                    break;
442
                default:
443
                    this.lookup();
444
            }
445
        },
446
 
447
        //------------------------------------------------------------------
448
        //
449
        //  Handles a key being pressed
450
        //
451
        keypress: function (e) {
452
            e.stopPropagation();
453
            if (!this.shown) {
454
                return;
455
            }
456
 
457
            switch (e.keyCode) {
458
                case 9:
459
                    // tab
460
                case 13:
461
                    // enter
462
                case 27:
463
                    // escape
464
                    e.preventDefault();
465
                    break;
466
                case 38:
467
                    // up arrow
468
                    e.preventDefault();
469
                    this.prev();
470
                    break;
471
                case 40:
472
                    // down arrow
473
                    e.preventDefault();
474
                    this.next();
475
                    break;
476
            }
477
        },
478
 
479
        //------------------------------------------------------------------
480
        //
481
        //  Handles cursor exiting the textbox
482
        //
483
        blur: function (e) {
484
            var that = this;
485
            e.stopPropagation();
486
            e.preventDefault();
487
            setTimeout(function () {
488
                if (!that.$menu.is(':focus')) {
489
                  that.hide();
490
                }
491
            }, 150)
492
        },
493
 
494
        //------------------------------------------------------------------
495
        //
496
        //  Handles clicking on the results list
497
        //
498
        click: function (e) {
499
            e.stopPropagation();
500
            e.preventDefault();
501
            this.select();
502
        },
503
 
504
        //------------------------------------------------------------------
505
        //
506
        //  Handles the mouse entering the results list
507
        //
508
        mouseenter: function (e) {
509
            this.$menu.find('.active').removeClass('active');
510
            $(e.currentTarget).addClass('active');
511
        }
512
    }
513
 
514
    //------------------------------------------------------------------
515
    //
516
    //  Plugin definition
517
    //
518
    $.fn.typeahead = function (option) {
519
        return this.each(function () {
520
            var $this = $(this),
521
                data = $this.data('typeahead'),
522
                options = typeof option === 'object' && option;
523
 
524
            if (!data) {
525
                $this.data('typeahead', (data = new Typeahead(this, options)));
526
            }
527
 
528
            if (typeof option === 'string') {
529
                data[option]();
530
            }
531
        });
532
    }
533
 
534
    //------------------------------------------------------------------
535
    //
536
    //  Defaults
537
    //
538
    $.fn.typeahead.defaults = {
539
        source: [],
540
        items: 8,
541
        menu: '<ul class="typeahead dropdown-menu"></ul>',
542
        item: '<li><a href="#"></a></li>',
543
        display: 'name',
544
        val: 'id',
545
        itemSelected: function () { },
546
        ajax: {
547
            url: null,
548
            timeout: 300,
549
            method: 'post',
550
            triggerLength: 3,
551
            loadingClass: null,
552
            displayField: null,
553
            preDispatch: null,
554
            preProcess: null
555
        }
556
    }
557
 
558
    $.fn.typeahead.Constructor = Typeahead;
559
 
560
    //------------------------------------------------------------------
561
    //
562
    //  DOM-ready call for the Data API (no-JS implementation)
563
    //    
564
    //  Note: As of Bootstrap v2.0 this feature may be disabled using $('body').off('.data-api')    
565
    //  More info here: https://github.com/twitter/bootstrap/tree/master/js
566
    //
567
    $(function () {
568
        $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
569
            var $this = $(this);
570
 
571
            if ($this.data('typeahead')) {
572
                return;
573
            }
574
 
575
            e.preventDefault();
576
            $this.typeahead($this.data());
577
        })
578
    });
579
 
580
} (window.jQuery);