Subversion Repositories SmartDukaan

Rev

Rev 14218 | Details | Compare with Previous | 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
 
14345 anikendra 345
            // items.first().addClass('active');
14218 anikendra 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');
14345 anikendra 356
            var val = this.$menu.find('.active').attr('data-value');
357
            if (val) {                
358
                this.$element.val($selectedItem.text()).change();
359
                this.options.itemSelected($selectedItem, $selectedItem.attr('data-value'), $selectedItem.text());
360
            }            
361
            $('form.navbar-form').submit();
362
            return this.hide();            
14218 anikendra 363
        },
364
 
365
        //------------------------------------------------------------------
366
        //
367
        //  Selects the next result
368
        //
369
        next: function (event) {
370
            var active = this.$menu.find('.active').removeClass('active');
371
            var next = active.next();
372
 
373
            if (!next.length) {
374
                next = $(this.$menu.find('li')[0]);
375
            }
376
 
377
            next.addClass('active');
378
        },
379
 
380
        //------------------------------------------------------------------
381
        //
382
        //  Selects the previous result
383
        //
384
        prev: function (event) {
385
            var active = this.$menu.find('.active').removeClass('active');
386
            var prev = active.prev();
387
 
388
            if (!prev.length) {
389
                prev = this.$menu.find('li').last();
390
            }
391
 
392
            prev.addClass('active');
393
        },
394
 
395
        //=============================================================================================================
396
        //
397
        //  Events
398
        //
399
        //=============================================================================================================
400
 
401
        //------------------------------------------------------------------
402
        //
403
        //  Listens for user events
404
        //
405
        listen: function () {
406
            this.$element.on('blur', $.proxy(this.blur, this))
407
                         .on('keyup', $.proxy(this.keyup, this));
408
 
409
    		if (this.eventSupported('keydown')) {
410
				this.$element.on('keydown', $.proxy(this.keypress, this));
411
			} else {
412
				this.$element.on('keypress', $.proxy(this.keypress, this));
413
			}
414
 
415
            this.$menu.on('click', $.proxy(this.click, this))
416
                      .on('mouseenter', 'li', $.proxy(this.mouseenter, this));
417
        },
418
 
419
        //------------------------------------------------------------------
420
        //
421
        //  Handles a key being raised up
422
        //
423
        keyup: function (e) {
424
            e.stopPropagation();
425
            e.preventDefault();
426
 
427
            switch (e.keyCode) {
428
                case 40:
429
                    // down arrow
430
                case 38:
431
                    // up arrow
432
                    break;
433
                case 9:
434
                    // tab
435
                case 13:
436
                    // enter
437
                    if (!this.shown) {
438
                        return;
439
                    }
440
                    this.select();
441
                    break;
442
                case 27:
443
                    // escape
444
                    this.hide();
445
                    break;
446
                default:
447
                    this.lookup();
448
            }
449
        },
450
 
451
        //------------------------------------------------------------------
452
        //
453
        //  Handles a key being pressed
454
        //
455
        keypress: function (e) {
456
            e.stopPropagation();
457
            if (!this.shown) {
458
                return;
459
            }
460
 
461
            switch (e.keyCode) {
462
                case 9:
463
                    // tab
464
                case 13:
465
                    // enter
466
                case 27:
467
                    // escape
468
                    e.preventDefault();
469
                    break;
470
                case 38:
471
                    // up arrow
472
                    e.preventDefault();
473
                    this.prev();
474
                    break;
475
                case 40:
476
                    // down arrow
477
                    e.preventDefault();
478
                    this.next();
479
                    break;
480
            }
481
        },
482
 
483
        //------------------------------------------------------------------
484
        //
485
        //  Handles cursor exiting the textbox
486
        //
487
        blur: function (e) {
488
            var that = this;
489
            e.stopPropagation();
490
            e.preventDefault();
491
            setTimeout(function () {
492
                if (!that.$menu.is(':focus')) {
493
                  that.hide();
494
                }
495
            }, 150)
496
        },
497
 
498
        //------------------------------------------------------------------
499
        //
500
        //  Handles clicking on the results list
501
        //
502
        click: function (e) {
503
            e.stopPropagation();
504
            e.preventDefault();
505
            this.select();
506
        },
507
 
508
        //------------------------------------------------------------------
509
        //
510
        //  Handles the mouse entering the results list
511
        //
512
        mouseenter: function (e) {
513
            this.$menu.find('.active').removeClass('active');
514
            $(e.currentTarget).addClass('active');
515
        }
516
    }
517
 
518
    //------------------------------------------------------------------
519
    //
520
    //  Plugin definition
521
    //
522
    $.fn.typeahead = function (option) {
523
        return this.each(function () {
524
            var $this = $(this),
525
                data = $this.data('typeahead'),
526
                options = typeof option === 'object' && option;
527
 
528
            if (!data) {
529
                $this.data('typeahead', (data = new Typeahead(this, options)));
530
            }
531
 
532
            if (typeof option === 'string') {
533
                data[option]();
534
            }
535
        });
536
    }
537
 
538
    //------------------------------------------------------------------
539
    //
540
    //  Defaults
541
    //
542
    $.fn.typeahead.defaults = {
543
        source: [],
544
        items: 8,
545
        menu: '<ul class="typeahead dropdown-menu"></ul>',
546
        item: '<li><a href="#"></a></li>',
547
        display: 'name',
548
        val: 'id',
549
        itemSelected: function () { },
550
        ajax: {
551
            url: null,
552
            timeout: 300,
553
            method: 'post',
554
            triggerLength: 3,
555
            loadingClass: null,
556
            displayField: null,
557
            preDispatch: null,
558
            preProcess: null
559
        }
560
    }
561
 
562
    $.fn.typeahead.Constructor = Typeahead;
563
 
564
    //------------------------------------------------------------------
565
    //
566
    //  DOM-ready call for the Data API (no-JS implementation)
567
    //    
568
    //  Note: As of Bootstrap v2.0 this feature may be disabled using $('body').off('.data-api')    
569
    //  More info here: https://github.com/twitter/bootstrap/tree/master/js
570
    //
571
    $(function () {
572
        $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
573
            var $this = $(this);
574
 
575
            if ($this.data('typeahead')) {
576
                return;
577
            }
578
 
579
            e.preventDefault();
580
            $this.typeahead($this.data());
581
        })
582
    });
583
 
584
} (window.jQuery);