~horux-dev/horux-webcli/thfo

« back to all changes in this revision

Viewing changes to assets/503782b/jquery.autocomplete.js

  • Committer: Thierry Forchelet
  • Date: 2011-02-25 13:30:15 UTC
  • Revision ID: thierry.forchelet@letux.ch-20110225133015-zxyj9w7sqv8ly971
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * jQuery Autocomplete plugin 1.1
 
3
 *
 
4
 * Modified for Yii Framework:
 
5
 * - Renamed "autocomplete" to "legacyautocomplete".
 
6
 * - Fixed IE8 problems (mario.ffranco).
 
7
 *
 
8
 * Copyright (c) 2009 Jörn Zaefferer
 
9
 *
 
10
 * Dual licensed under the MIT and GPL licenses:
 
11
 *   http://www.opensource.org/licenses/mit-license.php
 
12
 *   http://www.gnu.org/licenses/gpl.html
 
13
 *
 
14
 * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
 
15
 */
 
16
 
 
17
;(function($) {
 
18
        
 
19
$.fn.extend({
 
20
        legacyautocomplete: function(urlOrData, options) {
 
21
                var isUrl = typeof urlOrData == "string";
 
22
                options = $.extend({}, $.Autocompleter.defaults, {
 
23
                        url: isUrl ? urlOrData : null,
 
24
                        data: isUrl ? null : urlOrData,
 
25
                        delay: isUrl ? $.Autocompleter.defaults.delay : 10,
 
26
                        max: options && !options.scroll ? 10 : 150
 
27
                }, options);
 
28
                
 
29
                // if highlight is set to false, replace it with a do-nothing function
 
30
                options.highlight = options.highlight || function(value) { return value; };
 
31
                
 
32
                // if the formatMatch option is not specified, then use formatItem for backwards compatibility
 
33
                options.formatMatch = options.formatMatch || options.formatItem;
 
34
                
 
35
                return this.each(function() {
 
36
                        new $.Autocompleter(this, options);
 
37
                });
 
38
        },
 
39
        result: function(handler) {
 
40
                return this.bind("result", handler);
 
41
        },
 
42
        search: function(handler) {
 
43
                return this.trigger("search", [handler]);
 
44
        },
 
45
        flushCache: function() {
 
46
                return this.trigger("flushCache");
 
47
        },
 
48
        setOptions: function(options){
 
49
                return this.trigger("setOptions", [options]);
 
50
        },
 
51
        unautocomplete: function() {
 
52
                return this.trigger("unautocomplete");
 
53
        }
 
54
});
 
55
 
 
56
$.Autocompleter = function(input, options) {
 
57
 
 
58
        var KEY = {
 
59
                UP: 38,
 
60
                DOWN: 40,
 
61
                DEL: 46,
 
62
                TAB: 9,
 
63
                RETURN: 13,
 
64
                ESC: 27,
 
65
                COMMA: 188,
 
66
                PAGEUP: 33,
 
67
                PAGEDOWN: 34,
 
68
                BACKSPACE: 8
 
69
        };
 
70
 
 
71
        // Create $ object for input element
 
72
        var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
 
73
 
 
74
        var timeout;
 
75
        var previousValue = "";
 
76
        var cache = $.Autocompleter.Cache(options);
 
77
        var hasFocus = 0;
 
78
        var lastKeyPressCode;
 
79
        var config = {
 
80
                mouseDownOnSelect: false
 
81
        };
 
82
        var select = $.Autocompleter.Select(options, input, selectCurrent, config);
 
83
        
 
84
        var blockSubmit;
 
85
        
 
86
        // prevent form submit in opera when selecting with return key
 
87
        $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
 
88
                if (blockSubmit) {
 
89
                        blockSubmit = false;
 
90
                        return false;
 
91
                }
 
92
        });
 
93
        
 
94
        // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
 
95
        $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
 
96
                // a keypress means the input has focus
 
97
                // avoids issue where input had focus before the autocomplete was applied
 
98
                hasFocus = 1;
 
99
                // track last key pressed
 
100
                lastKeyPressCode = event.keyCode;
 
101
                switch(event.keyCode) {
 
102
                
 
103
                        case KEY.UP:
 
104
                                event.preventDefault();
 
105
                                if ( select.visible() ) {
 
106
                                        select.prev();
 
107
                                } else {
 
108
                                        onChange(0, true);
 
109
                                }
 
110
                                break;
 
111
                                
 
112
                        case KEY.DOWN:
 
113
                                event.preventDefault();
 
114
                                if ( select.visible() ) {
 
115
                                        select.next();
 
116
                                } else {
 
117
                                        onChange(0, true);
 
118
                                }
 
119
                                break;
 
120
                                
 
121
                        case KEY.PAGEUP:
 
122
                                event.preventDefault();
 
123
                                if ( select.visible() ) {
 
124
                                        select.pageUp();
 
125
                                } else {
 
126
                                        onChange(0, true);
 
127
                                }
 
128
                                break;
 
129
                                
 
130
                        case KEY.PAGEDOWN:
 
131
                                event.preventDefault();
 
132
                                if ( select.visible() ) {
 
133
                                        select.pageDown();
 
134
                                } else {
 
135
                                        onChange(0, true);
 
136
                                }
 
137
                                break;
 
138
                        
 
139
                        // matches also semicolon
 
140
                        case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
 
141
                        case KEY.TAB:
 
142
                        case KEY.RETURN:
 
143
                                if( selectCurrent() ) {
 
144
                                        // stop default to prevent a form submit, Opera needs special handling
 
145
                                        event.preventDefault();
 
146
                                        blockSubmit = true;
 
147
                                        return false;
 
148
                                }
 
149
                                break;
 
150
                                
 
151
                        case KEY.ESC:
 
152
                                select.hide();
 
153
                                break;
 
154
                                
 
155
                        default:
 
156
                                clearTimeout(timeout);
 
157
                                timeout = setTimeout(onChange, options.delay);
 
158
                                break;
 
159
                }
 
160
        }).focus(function(){
 
161
                // track whether the field has focus, we shouldn't process any
 
162
                // results if the field no longer has focus
 
163
                hasFocus++;
 
164
        }).blur(function() {
 
165
                hasFocus = 0;
 
166
                if (!config.mouseDownOnSelect) {
 
167
                        hideResults();
 
168
                }
 
169
        }).click(function() {
 
170
                // show select when clicking in a focused field
 
171
                if ( hasFocus++ > 1 && !select.visible() ) {
 
172
                        onChange(0, true);
 
173
                }
 
174
        }).bind("search", function() {
 
175
                // TODO why not just specifying both arguments?
 
176
                var fn = (arguments.length > 1) ? arguments[1] : null;
 
177
                function findValueCallback(q, data) {
 
178
                        var result;
 
179
                        if( data && data.length ) {
 
180
                                for (var i=0; i < data.length; i++) {
 
181
                                        if( data[i].result.toLowerCase() == q.toLowerCase() ) {
 
182
                                                result = data[i];
 
183
                                                break;
 
184
                                        }
 
185
                                }
 
186
                        }
 
187
                        if( typeof fn == "function" ) fn(result);
 
188
                        else $input.trigger("result", result && [result.data, result.value]);
 
189
                }
 
190
                $.each(trimWords($input.val()), function(i, value) {
 
191
                        request(value, findValueCallback, findValueCallback);
 
192
                });
 
193
        }).bind("flushCache", function() {
 
194
                cache.flush();
 
195
        }).bind("setOptions", function() {
 
196
                $.extend(options, arguments[1]);
 
197
                // if we've updated the data, repopulate
 
198
                if ( "data" in arguments[1] )
 
199
                        cache.populate();
 
200
        }).bind("unautocomplete", function() {
 
201
                select.unbind();
 
202
                $input.unbind();
 
203
                $(input.form).unbind(".autocomplete");
 
204
        });
 
205
        
 
206
        
 
207
        function selectCurrent() {
 
208
                var selected = select.selected();
 
209
                if( !selected )
 
210
                        return false;
 
211
                
 
212
                var v = selected.result;
 
213
                previousValue = v;
 
214
                
 
215
                if ( options.multiple ) {
 
216
                        var words = trimWords($input.val());
 
217
                        if ( words.length > 1 ) {
 
218
                                var seperator = options.multipleSeparator.length;
 
219
                                var cursorAt = $(input).selection().start;
 
220
                                var wordAt, progress = 0;
 
221
                                $.each(words, function(i, word) {
 
222
                                        progress += word.length;
 
223
                                        if (cursorAt <= progress) {
 
224
                                                wordAt = i;
 
225
                        // Following return caused IE8 to set cursor to the start of the line.
 
226
                                                // return false;
 
227
                                        }
 
228
                                        progress += seperator;
 
229
                                });
 
230
                                words[wordAt] = v;
 
231
                                // TODO this should set the cursor to the right position, but it gets overriden somewhere
 
232
                                //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
 
233
                                v = words.join( options.multipleSeparator );
 
234
                        }
 
235
                        v += options.multipleSeparator;
 
236
                }
 
237
                
 
238
                $input.val(v);
 
239
                hideResultsNow();
 
240
                $input.trigger("result", [selected.data, selected.value]);
 
241
                return true;
 
242
        }
 
243
        
 
244
        function onChange(crap, skipPrevCheck) {
 
245
                if( lastKeyPressCode == KEY.DEL ) {
 
246
                        select.hide();
 
247
                        return;
 
248
                }
 
249
                
 
250
                var currentValue = $input.val();
 
251
                
 
252
                if ( !skipPrevCheck && currentValue == previousValue )
 
253
                        return;
 
254
                
 
255
                previousValue = currentValue;
 
256
                
 
257
                currentValue = lastWord(currentValue);
 
258
                if ( currentValue.length >= options.minChars) {
 
259
                        $input.addClass(options.loadingClass);
 
260
                        if (!options.matchCase)
 
261
                                currentValue = currentValue.toLowerCase();
 
262
                        request(currentValue, receiveData, hideResultsNow);
 
263
                } else {
 
264
                        stopLoading();
 
265
                        select.hide();
 
266
                }
 
267
        };
 
268
        
 
269
        function trimWords(value) {
 
270
                if (!value)
 
271
                        return [""];
 
272
                if (!options.multiple)
 
273
                        return [$.trim(value)];
 
274
                return $.map(value.split(options.multipleSeparator), function(word) {
 
275
                        return $.trim(value).length ? $.trim(word) : null;
 
276
                });
 
277
        }
 
278
        
 
279
        function lastWord(value) {
 
280
                if ( !options.multiple )
 
281
                        return value;
 
282
                var words = trimWords(value);
 
283
                if (words.length == 1) 
 
284
                        return words[0];
 
285
                var cursorAt = $(input).selection().start;
 
286
                if (cursorAt == value.length) {
 
287
                        words = trimWords(value)
 
288
                } else {
 
289
                        words = trimWords(value.replace(value.substring(cursorAt), ""));
 
290
                }
 
291
                return words[words.length - 1];
 
292
        }
 
293
        
 
294
        // fills in the input box w/the first match (assumed to be the best match)
 
295
        // q: the term entered
 
296
        // sValue: the first matching result
 
297
        function autoFill(q, sValue){
 
298
                // autofill in the complete box w/the first match as long as the user hasn't entered in more data
 
299
                // if the last user key pressed was backspace, don't autofill
 
300
                if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
 
301
                        // fill in the value (keep the case the user has typed)
 
302
                        $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
 
303
                        // select the portion of the value not typed by the user (so the next character will erase)
 
304
                        $(input).selection(previousValue.length, previousValue.length + sValue.length);
 
305
                }
 
306
        };
 
307
 
 
308
        function hideResults() {
 
309
                clearTimeout(timeout);
 
310
                timeout = setTimeout(hideResultsNow, 200);
 
311
        };
 
312
 
 
313
        function hideResultsNow() {
 
314
                var wasVisible = select.visible();
 
315
                select.hide();
 
316
                clearTimeout(timeout);
 
317
                stopLoading();
 
318
                if (options.mustMatch) {
 
319
                        // call search and run callback
 
320
                        $input.search(
 
321
                                function (result){
 
322
                                        // if no value found, clear the input box
 
323
                                        if( !result ) {
 
324
                                                if (options.multiple) {
 
325
                                                        var words = trimWords($input.val()).slice(0, -1);
 
326
                                                        $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
 
327
                                                }
 
328
                                                else {
 
329
                                                        $input.val( "" );
 
330
                                                        $input.trigger("result", null);
 
331
                                                }
 
332
                                        }
 
333
                                }
 
334
                        );
 
335
                }
 
336
        };
 
337
 
 
338
        function receiveData(q, data) {
 
339
                if ( data && data.length && hasFocus ) {
 
340
                        stopLoading();
 
341
                        select.display(data, q);
 
342
                        autoFill(q, data[0].value);
 
343
                        select.show();
 
344
                } else {
 
345
                        hideResultsNow();
 
346
                }
 
347
        };
 
348
 
 
349
        function request(term, success, failure) {
 
350
                if (!options.matchCase)
 
351
                        term = term.toLowerCase();
 
352
                var data = cache.load(term);
 
353
                // recieve the cached data
 
354
                if (data && data.length) {
 
355
                        success(term, data);
 
356
                // if an AJAX url has been supplied, try loading the data now
 
357
                } else if( (typeof options.url == "string") && (options.url.length > 0) ){
 
358
                        
 
359
                        var extraParams = {
 
360
                                timestamp: +new Date()
 
361
                        };
 
362
                        $.each(options.extraParams, function(key, param) {
 
363
                                extraParams[key] = typeof param == "function" ? param() : param;
 
364
                        });
 
365
                        
 
366
                        $.ajax({
 
367
                                // try to leverage ajaxQueue plugin to abort previous requests
 
368
                                mode: "abort",
 
369
                                // limit abortion to this input
 
370
                                port: "autocomplete" + input.name,
 
371
                                dataType: options.dataType,
 
372
                                url: options.url,
 
373
                                data: $.extend({
 
374
                                        q: lastWord(term),
 
375
                                        limit: options.max
 
376
                                }, extraParams),
 
377
                                success: function(data) {
 
378
                                        var parsed = options.parse && options.parse(data) || parse(data);
 
379
                                        cache.add(term, parsed);
 
380
                                        success(term, parsed);
 
381
                                }
 
382
                        });
 
383
                } else {
 
384
                        // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
 
385
                        select.emptyList();
 
386
                        failure(term);
 
387
                }
 
388
        };
 
389
        
 
390
        function parse(data) {
 
391
                var parsed = [];
 
392
                var rows = data.split("\n");
 
393
                for (var i=0; i < rows.length; i++) {
 
394
                        var row = $.trim(rows[i]);
 
395
                        if (row) {
 
396
                                row = row.split("|");
 
397
                                parsed[parsed.length] = {
 
398
                                        data: row,
 
399
                                        value: row[0],
 
400
                                        result: options.formatResult && options.formatResult(row, row[0]) || row[0]
 
401
                                };
 
402
                        }
 
403
                }
 
404
                return parsed;
 
405
        };
 
406
 
 
407
        function stopLoading() {
 
408
                $input.removeClass(options.loadingClass);
 
409
        };
 
410
 
 
411
};
 
412
 
 
413
$.Autocompleter.defaults = {
 
414
        inputClass: "ac_input",
 
415
        resultsClass: "ac_results",
 
416
        loadingClass: "ac_loading",
 
417
        minChars: 1,
 
418
        delay: 400,
 
419
        matchCase: false,
 
420
        matchSubset: true,
 
421
        matchContains: false,
 
422
        cacheLength: 10,
 
423
        max: 100,
 
424
        mustMatch: false,
 
425
        extraParams: {},
 
426
        selectFirst: true,
 
427
        formatItem: function(row) { return row[0]; },
 
428
        formatMatch: null,
 
429
        autoFill: false,
 
430
        width: 0,
 
431
        multiple: false,
 
432
        multipleSeparator: ", ",
 
433
        highlight: function(value, term) {
 
434
                return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
 
435
        },
 
436
    scroll: true,
 
437
    scrollHeight: 180
 
438
};
 
439
 
 
440
$.Autocompleter.Cache = function(options) {
 
441
 
 
442
        var data = {};
 
443
        var length = 0;
 
444
        
 
445
        function matchSubset(s, sub) {
 
446
                if (!options.matchCase) 
 
447
                        s = s.toLowerCase();
 
448
                var i = s.indexOf(sub);
 
449
                if (options.matchContains == "word"){
 
450
                        i = s.toLowerCase().search("\\b" + sub.toLowerCase());
 
451
                }
 
452
                if (i == -1) return false;
 
453
                return i == 0 || options.matchContains;
 
454
        };
 
455
        
 
456
        function add(q, value) {
 
457
                if (length > options.cacheLength){
 
458
                        flush();
 
459
                }
 
460
                if (!data[q]){ 
 
461
                        length++;
 
462
                }
 
463
                data[q] = value;
 
464
        }
 
465
        
 
466
        function populate(){
 
467
                if( !options.data ) return false;
 
468
                // track the matches
 
469
                var stMatchSets = {},
 
470
                        nullData = 0;
 
471
 
 
472
                // no url was specified, we need to adjust the cache length to make sure it fits the local data store
 
473
                if( !options.url ) options.cacheLength = 1;
 
474
                
 
475
                // track all options for minChars = 0
 
476
                stMatchSets[""] = [];
 
477
                
 
478
                // loop through the array and create a lookup structure
 
479
                for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
 
480
                        var rawValue = options.data[i];
 
481
                        // if rawValue is a string, make an array otherwise just reference the array
 
482
                        rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
 
483
                        
 
484
                        var value = options.formatMatch(rawValue, i+1, options.data.length);
 
485
                        if ( value === false )
 
486
                                continue;
 
487
                                
 
488
                        var firstChar = value.charAt(0).toLowerCase();
 
489
                        // if no lookup array for this character exists, look it up now
 
490
                        if( !stMatchSets[firstChar] ) 
 
491
                                stMatchSets[firstChar] = [];
 
492
 
 
493
                        // if the match is a string
 
494
                        var row = {
 
495
                                value: value,
 
496
                                data: rawValue,
 
497
                                result: options.formatResult && options.formatResult(rawValue) || value
 
498
                        };
 
499
                        
 
500
                        // push the current match into the set list
 
501
                        stMatchSets[firstChar].push(row);
 
502
 
 
503
                        // keep track of minChars zero items
 
504
                        if ( nullData++ < options.max ) {
 
505
                                stMatchSets[""].push(row);
 
506
                        }
 
507
                };
 
508
 
 
509
                // add the data items to the cache
 
510
                $.each(stMatchSets, function(i, value) {
 
511
                        // increase the cache size
 
512
                        options.cacheLength++;
 
513
                        // add to the cache
 
514
                        add(i, value);
 
515
                });
 
516
        }
 
517
        
 
518
        // populate any existing data
 
519
        setTimeout(populate, 25);
 
520
        
 
521
        function flush(){
 
522
                data = {};
 
523
                length = 0;
 
524
        }
 
525
        
 
526
        return {
 
527
                flush: flush,
 
528
                add: add,
 
529
                populate: populate,
 
530
                load: function(q) {
 
531
                        if (!options.cacheLength || !length)
 
532
                                return null;
 
533
                        /* 
 
534
                         * if dealing w/local data and matchContains than we must make sure
 
535
                         * to loop through all the data collections looking for matches
 
536
                         */
 
537
                        if( !options.url && options.matchContains ){
 
538
                                // track all matches
 
539
                                var csub = [];
 
540
                                // loop through all the data grids for matches
 
541
                                for( var k in data ){
 
542
                                        // don't search through the stMatchSets[""] (minChars: 0) cache
 
543
                                        // this prevents duplicates
 
544
                                        if( k.length > 0 ){
 
545
                                                var c = data[k];
 
546
                                                $.each(c, function(i, x) {
 
547
                                                        // if we've got a match, add it to the array
 
548
                                                        if (matchSubset(x.value, q)) {
 
549
                                                                csub.push(x);
 
550
                                                        }
 
551
                                                });
 
552
                                        }
 
553
                                }                               
 
554
                                return csub;
 
555
                        } else 
 
556
                        // if the exact item exists, use it
 
557
                        if (data[q]){
 
558
                                return data[q];
 
559
                        } else
 
560
                        if (options.matchSubset) {
 
561
                                for (var i = q.length - 1; i >= options.minChars; i--) {
 
562
                                        var c = data[q.substr(0, i)];
 
563
                                        if (c) {
 
564
                                                var csub = [];
 
565
                                                $.each(c, function(i, x) {
 
566
                                                        if (matchSubset(x.value, q)) {
 
567
                                                                csub[csub.length] = x;
 
568
                                                        }
 
569
                                                });
 
570
                                                return csub;
 
571
                                        }
 
572
                                }
 
573
                        }
 
574
                        return null;
 
575
                }
 
576
        };
 
577
};
 
578
 
 
579
$.Autocompleter.Select = function (options, input, select, config) {
 
580
        var CLASSES = {
 
581
                ACTIVE: "ac_over"
 
582
        };
 
583
        
 
584
        var listItems,
 
585
                active = -1,
 
586
                data,
 
587
                term = "",
 
588
                needsInit = true,
 
589
                element,
 
590
                list;
 
591
        
 
592
        // Create results
 
593
        function init() {
 
594
                if (!needsInit)
 
595
                        return;
 
596
                element = $("<div/>")
 
597
                .hide()
 
598
                .addClass(options.resultsClass)
 
599
                .css("position", "absolute")
 
600
                .appendTo(document.body);
 
601
        
 
602
                list = $("<ul/>").appendTo(element).mouseover( function(event) {
 
603
                        if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
 
604
                    active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
 
605
                            $(target(event)).addClass(CLASSES.ACTIVE);            
 
606
                }
 
607
                }).click(function(event) {
 
608
                        $(target(event)).addClass(CLASSES.ACTIVE);
 
609
                        select();
 
610
                        // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
 
611
                        input.focus();
 
612
                        return false;
 
613
                }).mousedown(function() {
 
614
                        config.mouseDownOnSelect = true;
 
615
                }).mouseup(function() {
 
616
                        config.mouseDownOnSelect = false;
 
617
                });
 
618
                
 
619
                if( options.width > 0 )
 
620
                        element.css("width", options.width);
 
621
                        
 
622
                needsInit = false;
 
623
        } 
 
624
        
 
625
        function target(event) {
 
626
                var element = event.target;
 
627
                while(element && element.tagName != "LI")
 
628
                        element = element.parentNode;
 
629
                // more fun with IE, sometimes event.target is empty, just ignore it then
 
630
                if(!element)
 
631
                        return [];
 
632
                return element;
 
633
        }
 
634
 
 
635
        function moveSelect(step) {
 
636
                listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
 
637
                movePosition(step);
 
638
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
 
639
        if(options.scroll) {
 
640
            var offset = 0;
 
641
            listItems.slice(0, active).each(function() {
 
642
                                offset += this.offsetHeight;
 
643
                        });
 
644
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
 
645
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
 
646
            } else if(offset < list.scrollTop()) {
 
647
                list.scrollTop(offset);
 
648
            }
 
649
        }
 
650
        };
 
651
        
 
652
        function movePosition(step) {
 
653
                active += step;
 
654
                if (active < 0) {
 
655
                        active = listItems.size() - 1;
 
656
                } else if (active >= listItems.size()) {
 
657
                        active = 0;
 
658
                }
 
659
        }
 
660
        
 
661
        function limitNumberOfItems(available) {
 
662
                return options.max && options.max < available
 
663
                        ? options.max
 
664
                        : available;
 
665
        }
 
666
        
 
667
        function fillList() {
 
668
                list.empty();
 
669
                var max = limitNumberOfItems(data.length);
 
670
                for (var i=0; i < max; i++) {
 
671
                        if (!data[i])
 
672
                                continue;
 
673
                        var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
 
674
                        if ( formatted === false )
 
675
                                continue;
 
676
                        var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
 
677
                        $.data(li, "ac_data", data[i]);
 
678
                }
 
679
                listItems = list.find("li");
 
680
                if ( options.selectFirst ) {
 
681
                        listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
 
682
                        active = 0;
 
683
                }
 
684
                // apply bgiframe if available
 
685
                if ( $.fn.bgiframe )
 
686
                        list.bgiframe();
 
687
        }
 
688
        
 
689
        return {
 
690
                display: function(d, q) {
 
691
                        init();
 
692
                        data = d;
 
693
                        term = q;
 
694
                        fillList();
 
695
                },
 
696
                next: function() {
 
697
                        moveSelect(1);
 
698
                },
 
699
                prev: function() {
 
700
                        moveSelect(-1);
 
701
                },
 
702
                pageUp: function() {
 
703
                        if (active != 0 && active - 8 < 0) {
 
704
                                moveSelect( -active );
 
705
                        } else {
 
706
                                moveSelect(-8);
 
707
                        }
 
708
                },
 
709
                pageDown: function() {
 
710
                        if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
 
711
                                moveSelect( listItems.size() - 1 - active );
 
712
                        } else {
 
713
                                moveSelect(8);
 
714
                        }
 
715
                },
 
716
                hide: function() {
 
717
                        element && element.hide();
 
718
                        listItems && listItems.removeClass(CLASSES.ACTIVE);
 
719
                        active = -1;
 
720
                },
 
721
                visible : function() {
 
722
                        return element && element.is(":visible");
 
723
                },
 
724
                current: function() {
 
725
                        return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
 
726
                },
 
727
                show: function() {
 
728
                        var offset = $(input).offset();
 
729
                        element.css({
 
730
                                width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
 
731
                                top: offset.top + input.offsetHeight,
 
732
                                left: offset.left
 
733
                        }).show();
 
734
            if(options.scroll) {
 
735
                list.scrollTop(0);
 
736
                list.css({
 
737
                                        maxHeight: options.scrollHeight,
 
738
                                        overflow: 'auto'
 
739
                                });
 
740
                                
 
741
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
 
742
                                        var listHeight = 0;
 
743
                                        listItems.each(function() {
 
744
                                                listHeight += this.offsetHeight;
 
745
                                        });
 
746
                                        var scrollbarsVisible = listHeight > options.scrollHeight;
 
747
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
 
748
                                        if (!scrollbarsVisible) {
 
749
                                                // IE doesn't recalculate width when scrollbar disappears
 
750
                                                listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
 
751
                                        }
 
752
                }
 
753
                
 
754
            }
 
755
                },
 
756
                selected: function() {
 
757
                        var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
 
758
                        return selected && selected.length && $.data(selected[0], "ac_data");
 
759
                },
 
760
                emptyList: function (){
 
761
                        list && list.empty();
 
762
                },
 
763
                unbind: function() {
 
764
                        element && element.remove();
 
765
                }
 
766
        };
 
767
};
 
768
 
 
769
$.fn.selection = function(start, end) {
 
770
        if (start !== undefined) {
 
771
                return this.each(function() {
 
772
                        if( this.createTextRange ){
 
773
                                var selRange = this.createTextRange();
 
774
                                if (end === undefined || start == end) {
 
775
                                        selRange.move("character", start);
 
776
                                        selRange.select();
 
777
                                } else {
 
778
                                        selRange.collapse(true);
 
779
                                        selRange.moveStart("character", start);
 
780
                                        selRange.moveEnd("character", end);
 
781
                                        selRange.select();
 
782
                                }
 
783
                        } else if( this.setSelectionRange ){
 
784
                                this.setSelectionRange(start, end);
 
785
                        } else if( this.selectionStart ){
 
786
                                this.selectionStart = start;
 
787
                                this.selectionEnd = end;
 
788
                        }
 
789
                });
 
790
        }
 
791
        var field = this[0];
 
792
        if ( field.createTextRange ) {
 
793
                var range = document.selection.createRange(),
 
794
                        orig = field.value,
 
795
                        teststring = "<->",
 
796
                        textLength = range.text.length;
 
797
                range.text = teststring;
 
798
                var caretAt = field.value.indexOf(teststring);
 
799
                field.value = orig;
 
800
                this.selection(caretAt, caretAt + textLength);
 
801
                return {
 
802
                        start: caretAt,
 
803
                        end: caretAt + textLength
 
804
                }
 
805
        } else if( field.selectionStart !== undefined ){
 
806
                return {
 
807
                        start: field.selectionStart,
 
808
                        end: field.selectionEnd
 
809
                }
 
810
        }
 
811
};
 
812
 
 
813
})(jQuery);
 
 
b'\\ No newline at end of file'