Projet

Général

Profil

Paste
Télécharger (15,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / jquery_update / replace / ui / ui / jquery.ui.autocomplete.js @ 503b3f7b

1
/*!
2
 * jQuery UI Autocomplete 1.10.2
3
 * http://jqueryui.com
4
 *
5
 * Copyright 2013 jQuery Foundation and other contributors
6
 * Released under the MIT license.
7
 * http://jquery.org/license
8
 *
9
 * http://api.jqueryui.com/autocomplete/
10
 *
11
 * Depends:
12
 *        jquery.ui.core.js
13
 *        jquery.ui.widget.js
14
 *        jquery.ui.position.js
15
 *        jquery.ui.menu.js
16
 */
17
(function( $, undefined ) {
18

    
19
// used to prevent race conditions with remote data sources
20
var requestIndex = 0;
21

    
22
$.widget( "ui.autocomplete", {
23
        version: "1.10.2",
24
        defaultElement: "<input>",
25
        options: {
26
                appendTo: null,
27
                autoFocus: false,
28
                delay: 300,
29
                minLength: 1,
30
                position: {
31
                        my: "left top",
32
                        at: "left bottom",
33
                        collision: "none"
34
                },
35
                source: null,
36

    
37
                // callbacks
38
                change: null,
39
                close: null,
40
                focus: null,
41
                open: null,
42
                response: null,
43
                search: null,
44
                select: null
45
        },
46

    
47
        pending: 0,
48

    
49
        _create: function() {
50
                // Some browsers only repeat keydown events, not keypress events,
51
                // so we use the suppressKeyPress flag to determine if we've already
52
                // handled the keydown event. #7269
53
                // Unfortunately the code for & in keypress is the same as the up arrow,
54
                // so we use the suppressKeyPressRepeat flag to avoid handling keypress
55
                // events when we know the keydown event was used to modify the
56
                // search term. #7799
57
                var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
58
                        nodeName = this.element[0].nodeName.toLowerCase(),
59
                        isTextarea = nodeName === "textarea",
60
                        isInput = nodeName === "input";
61

    
62
                this.isMultiLine =
63
                        // Textareas are always multi-line
64
                        isTextarea ? true :
65
                        // Inputs are always single-line, even if inside a contentEditable element
66
                        // IE also treats inputs as contentEditable
67
                        isInput ? false :
68
                        // All other element types are determined by whether or not they're contentEditable
69
                        this.element.prop( "isContentEditable" );
70

    
71
                this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
72
                this.isNewMenu = true;
73

    
74
                this.element
75
                        .addClass( "ui-autocomplete-input" )
76
                        .attr( "autocomplete", "off" );
77

    
78
                this._on( this.element, {
79
                        keydown: function( event ) {
80
                                /*jshint maxcomplexity:15*/
81
                                if ( this.element.prop( "readOnly" ) ) {
82
                                        suppressKeyPress = true;
83
                                        suppressInput = true;
84
                                        suppressKeyPressRepeat = true;
85
                                        return;
86
                                }
87

    
88
                                suppressKeyPress = false;
89
                                suppressInput = false;
90
                                suppressKeyPressRepeat = false;
91
                                var keyCode = $.ui.keyCode;
92
                                switch( event.keyCode ) {
93
                                case keyCode.PAGE_UP:
94
                                        suppressKeyPress = true;
95
                                        this._move( "previousPage", event );
96
                                        break;
97
                                case keyCode.PAGE_DOWN:
98
                                        suppressKeyPress = true;
99
                                        this._move( "nextPage", event );
100
                                        break;
101
                                case keyCode.UP:
102
                                        suppressKeyPress = true;
103
                                        this._keyEvent( "previous", event );
104
                                        break;
105
                                case keyCode.DOWN:
106
                                        suppressKeyPress = true;
107
                                        this._keyEvent( "next", event );
108
                                        break;
109
                                case keyCode.ENTER:
110
                                case keyCode.NUMPAD_ENTER:
111
                                        // when menu is open and has focus
112
                                        if ( this.menu.active ) {
113
                                                // #6055 - Opera still allows the keypress to occur
114
                                                // which causes forms to submit
115
                                                suppressKeyPress = true;
116
                                                event.preventDefault();
117
                                                this.menu.select( event );
118
                                        }
119
                                        break;
120
                                case keyCode.TAB:
121
                                        if ( this.menu.active ) {
122
                                                this.menu.select( event );
123
                                        }
124
                                        break;
125
                                case keyCode.ESCAPE:
126
                                        if ( this.menu.element.is( ":visible" ) ) {
127
                                                this._value( this.term );
128
                                                this.close( event );
129
                                                // Different browsers have different default behavior for escape
130
                                                // Single press can mean undo or clear
131
                                                // Double press in IE means clear the whole form
132
                                                event.preventDefault();
133
                                        }
134
                                        break;
135
                                default:
136
                                        suppressKeyPressRepeat = true;
137
                                        // search timeout should be triggered before the input value is changed
138
                                        this._searchTimeout( event );
139
                                        break;
140
                                }
141
                        },
142
                        keypress: function( event ) {
143
                                if ( suppressKeyPress ) {
144
                                        suppressKeyPress = false;
145
                                        event.preventDefault();
146
                                        return;
147
                                }
148
                                if ( suppressKeyPressRepeat ) {
149
                                        return;
150
                                }
151

    
152
                                // replicate some key handlers to allow them to repeat in Firefox and Opera
153
                                var keyCode = $.ui.keyCode;
154
                                switch( event.keyCode ) {
155
                                case keyCode.PAGE_UP:
156
                                        this._move( "previousPage", event );
157
                                        break;
158
                                case keyCode.PAGE_DOWN:
159
                                        this._move( "nextPage", event );
160
                                        break;
161
                                case keyCode.UP:
162
                                        this._keyEvent( "previous", event );
163
                                        break;
164
                                case keyCode.DOWN:
165
                                        this._keyEvent( "next", event );
166
                                        break;
167
                                }
168
                        },
169
                        input: function( event ) {
170
                                if ( suppressInput ) {
171
                                        suppressInput = false;
172
                                        event.preventDefault();
173
                                        return;
174
                                }
175
                                this._searchTimeout( event );
176
                        },
177
                        focus: function() {
178
                                this.selectedItem = null;
179
                                this.previous = this._value();
180
                        },
181
                        blur: function( event ) {
182
                                if ( this.cancelBlur ) {
183
                                        delete this.cancelBlur;
184
                                        return;
185
                                }
186

    
187
                                clearTimeout( this.searching );
188
                                this.close( event );
189
                                this._change( event );
190
                        }
191
                });
192

    
193
                this._initSource();
194
                this.menu = $( "<ul>" )
195
                        .addClass( "ui-autocomplete ui-front" )
196
                        .appendTo( this._appendTo() )
197
                        .menu({
198
                                // custom key handling for now
199
                                input: $(),
200
                                // disable ARIA support, the live region takes care of that
201
                                role: null
202
                        })
203
                        .hide()
204
                        .data( "ui-menu" );
205

    
206
                this._on( this.menu.element, {
207
                        mousedown: function( event ) {
208
                                // prevent moving focus out of the text field
209
                                event.preventDefault();
210

    
211
                                // IE doesn't prevent moving focus even with event.preventDefault()
212
                                // so we set a flag to know when we should ignore the blur event
213
                                this.cancelBlur = true;
214
                                this._delay(function() {
215
                                        delete this.cancelBlur;
216
                                });
217

    
218
                                // clicking on the scrollbar causes focus to shift to the body
219
                                // but we can't detect a mouseup or a click immediately afterward
220
                                // so we have to track the next mousedown and close the menu if
221
                                // the user clicks somewhere outside of the autocomplete
222
                                var menuElement = this.menu.element[ 0 ];
223
                                if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
224
                                        this._delay(function() {
225
                                                var that = this;
226
                                                this.document.one( "mousedown", function( event ) {
227
                                                        if ( event.target !== that.element[ 0 ] &&
228
                                                                        event.target !== menuElement &&
229
                                                                        !$.contains( menuElement, event.target ) ) {
230
                                                                that.close();
231
                                                        }
232
                                                });
233
                                        });
234
                                }
235
                        },
236
                        menufocus: function( event, ui ) {
237
                                // support: Firefox
238
                                // Prevent accidental activation of menu items in Firefox (#7024 #9118)
239
                                if ( this.isNewMenu ) {
240
                                        this.isNewMenu = false;
241
                                        if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
242
                                                this.menu.blur();
243

    
244
                                                this.document.one( "mousemove", function() {
245
                                                        $( event.target ).trigger( event.originalEvent );
246
                                                });
247

    
248
                                                return;
249
                                        }
250
                                }
251

    
252
                                var item = ui.item.data( "ui-autocomplete-item" );
253
                                if ( false !== this._trigger( "focus", event, { item: item } ) ) {
254
                                        // use value to match what will end up in the input, if it was a key event
255
                                        if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
256
                                                this._value( item.value );
257
                                        }
258
                                } else {
259
                                        // Normally the input is populated with the item's value as the
260
                                        // menu is navigated, causing screen readers to notice a change and
261
                                        // announce the item. Since the focus event was canceled, this doesn't
262
                                        // happen, so we update the live region so that screen readers can
263
                                        // still notice the change and announce it.
264
                                        this.liveRegion.text( item.value );
265
                                }
266
                        },
267
                        menuselect: function( event, ui ) {
268
                                var item = ui.item.data( "ui-autocomplete-item" ),
269
                                        previous = this.previous;
270

    
271
                                // only trigger when focus was lost (click on menu)
272
                                if ( this.element[0] !== this.document[0].activeElement ) {
273
                                        this.element.focus();
274
                                        this.previous = previous;
275
                                        // #6109 - IE triggers two focus events and the second
276
                                        // is asynchronous, so we need to reset the previous
277
                                        // term synchronously and asynchronously :-(
278
                                        this._delay(function() {
279
                                                this.previous = previous;
280
                                                this.selectedItem = item;
281
                                        });
282
                                }
283

    
284
                                if ( false !== this._trigger( "select", event, { item: item } ) ) {
285
                                        this._value( item.value );
286
                                }
287
                                // reset the term after the select event
288
                                // this allows custom select handling to work properly
289
                                this.term = this._value();
290

    
291
                                this.close( event );
292
                                this.selectedItem = item;
293
                        }
294
                });
295

    
296
                this.liveRegion = $( "<span>", {
297
                                role: "status",
298
                                "aria-live": "polite"
299
                        })
300
                        .addClass( "ui-helper-hidden-accessible" )
301
                        .insertAfter( this.element );
302

    
303
                // turning off autocomplete prevents the browser from remembering the
304
                // value when navigating through history, so we re-enable autocomplete
305
                // if the page is unloaded before the widget is destroyed. #7790
306
                this._on( this.window, {
307
                        beforeunload: function() {
308
                                this.element.removeAttr( "autocomplete" );
309
                        }
310
                });
311
        },
312

    
313
        _destroy: function() {
314
                clearTimeout( this.searching );
315
                this.element
316
                        .removeClass( "ui-autocomplete-input" )
317
                        .removeAttr( "autocomplete" );
318
                this.menu.element.remove();
319
                this.liveRegion.remove();
320
        },
321

    
322
        _setOption: function( key, value ) {
323
                this._super( key, value );
324
                if ( key === "source" ) {
325
                        this._initSource();
326
                }
327
                if ( key === "appendTo" ) {
328
                        this.menu.element.appendTo( this._appendTo() );
329
                }
330
                if ( key === "disabled" && value && this.xhr ) {
331
                        this.xhr.abort();
332
                }
333
        },
334

    
335
        _appendTo: function() {
336
                var element = this.options.appendTo;
337

    
338
                if ( element ) {
339
                        element = element.jquery || element.nodeType ?
340
                                $( element ) :
341
                                this.document.find( element ).eq( 0 );
342
                }
343

    
344
                if ( !element ) {
345
                        element = this.element.closest( ".ui-front" );
346
                }
347

    
348
                if ( !element.length ) {
349
                        element = this.document[0].body;
350
                }
351

    
352
                return element;
353
        },
354

    
355
        _initSource: function() {
356
                var array, url,
357
                        that = this;
358
                if ( $.isArray(this.options.source) ) {
359
                        array = this.options.source;
360
                        this.source = function( request, response ) {
361
                                response( $.ui.autocomplete.filter( array, request.term ) );
362
                        };
363
                } else if ( typeof this.options.source === "string" ) {
364
                        url = this.options.source;
365
                        this.source = function( request, response ) {
366
                                if ( that.xhr ) {
367
                                        that.xhr.abort();
368
                                }
369
                                that.xhr = $.ajax({
370
                                        url: url,
371
                                        data: request,
372
                                        dataType: "json",
373
                                        success: function( data ) {
374
                                                response( data );
375
                                        },
376
                                        error: function() {
377
                                                response( [] );
378
                                        }
379
                                });
380
                        };
381
                } else {
382
                        this.source = this.options.source;
383
                }
384
        },
385

    
386
        _searchTimeout: function( event ) {
387
                clearTimeout( this.searching );
388
                this.searching = this._delay(function() {
389
                        // only search if the value has changed
390
                        if ( this.term !== this._value() ) {
391
                                this.selectedItem = null;
392
                                this.search( null, event );
393
                        }
394
                }, this.options.delay );
395
        },
396

    
397
        search: function( value, event ) {
398
                value = value != null ? value : this._value();
399

    
400
                // always save the actual value, not the one passed as an argument
401
                this.term = this._value();
402

    
403
                if ( value.length < this.options.minLength ) {
404
                        return this.close( event );
405
                }
406

    
407
                if ( this._trigger( "search", event ) === false ) {
408
                        return;
409
                }
410

    
411
                return this._search( value );
412
        },
413

    
414
        _search: function( value ) {
415
                this.pending++;
416
                this.element.addClass( "ui-autocomplete-loading" );
417
                this.cancelSearch = false;
418

    
419
                this.source( { term: value }, this._response() );
420
        },
421

    
422
        _response: function() {
423
                var that = this,
424
                        index = ++requestIndex;
425

    
426
                return function( content ) {
427
                        if ( index === requestIndex ) {
428
                                that.__response( content );
429
                        }
430

    
431
                        that.pending--;
432
                        if ( !that.pending ) {
433
                                that.element.removeClass( "ui-autocomplete-loading" );
434
                        }
435
                };
436
        },
437

    
438
        __response: function( content ) {
439
                if ( content ) {
440
                        content = this._normalize( content );
441
                }
442
                this._trigger( "response", null, { content: content } );
443
                if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
444
                        this._suggest( content );
445
                        this._trigger( "open" );
446
                } else {
447
                        // use ._close() instead of .close() so we don't cancel future searches
448
                        this._close();
449
                }
450
        },
451

    
452
        close: function( event ) {
453
                this.cancelSearch = true;
454
                this._close( event );
455
        },
456

    
457
        _close: function( event ) {
458
                if ( this.menu.element.is( ":visible" ) ) {
459
                        this.menu.element.hide();
460
                        this.menu.blur();
461
                        this.isNewMenu = true;
462
                        this._trigger( "close", event );
463
                }
464
        },
465

    
466
        _change: function( event ) {
467
                if ( this.previous !== this._value() ) {
468
                        this._trigger( "change", event, { item: this.selectedItem } );
469
                }
470
        },
471

    
472
        _normalize: function( items ) {
473
                // assume all items have the right format when the first item is complete
474
                if ( items.length && items[0].label && items[0].value ) {
475
                        return items;
476
                }
477
                return $.map( items, function( item ) {
478
                        if ( typeof item === "string" ) {
479
                                return {
480
                                        label: item,
481
                                        value: item
482
                                };
483
                        }
484
                        return $.extend({
485
                                label: item.label || item.value,
486
                                value: item.value || item.label
487
                        }, item );
488
                });
489
        },
490

    
491
        _suggest: function( items ) {
492
                var ul = this.menu.element.empty();
493
                this._renderMenu( ul, items );
494
                this.isNewMenu = true;
495
                this.menu.refresh();
496

    
497
                // size and position menu
498
                ul.show();
499
                this._resizeMenu();
500
                ul.position( $.extend({
501
                        of: this.element
502
                }, this.options.position ));
503

    
504
                if ( this.options.autoFocus ) {
505
                        this.menu.next();
506
                }
507
        },
508

    
509
        _resizeMenu: function() {
510
                var ul = this.menu.element;
511
                ul.outerWidth( Math.max(
512
                        // Firefox wraps long text (possibly a rounding bug)
513
                        // so we add 1px to avoid the wrapping (#7513)
514
                        ul.width( "" ).outerWidth() + 1,
515
                        this.element.outerWidth()
516
                ) );
517
        },
518

    
519
        _renderMenu: function( ul, items ) {
520
                var that = this;
521
                $.each( items, function( index, item ) {
522
                        that._renderItemData( ul, item );
523
                });
524
        },
525

    
526
        _renderItemData: function( ul, item ) {
527
                return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
528
        },
529

    
530
        _renderItem: function( ul, item ) {
531
                return $( "<li>" )
532
                        .append( $( "<a>" ).text( item.label ) )
533
                        .appendTo( ul );
534
        },
535

    
536
        _move: function( direction, event ) {
537
                if ( !this.menu.element.is( ":visible" ) ) {
538
                        this.search( null, event );
539
                        return;
540
                }
541
                if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
542
                                this.menu.isLastItem() && /^next/.test( direction ) ) {
543
                        this._value( this.term );
544
                        this.menu.blur();
545
                        return;
546
                }
547
                this.menu[ direction ]( event );
548
        },
549

    
550
        widget: function() {
551
                return this.menu.element;
552
        },
553

    
554
        _value: function() {
555
                return this.valueMethod.apply( this.element, arguments );
556
        },
557

    
558
        _keyEvent: function( keyEvent, event ) {
559
                if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
560
                        this._move( keyEvent, event );
561

    
562
                        // prevents moving cursor to beginning/end of the text field in some browsers
563
                        event.preventDefault();
564
                }
565
        }
566
});
567

    
568
$.extend( $.ui.autocomplete, {
569
        escapeRegex: function( value ) {
570
                return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
571
        },
572
        filter: function(array, term) {
573
                var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
574
                return $.grep( array, function(value) {
575
                        return matcher.test( value.label || value.value || value );
576
                });
577
        }
578
});
579

    
580

    
581
// live region extension, adding a `messages` option
582
// NOTE: This is an experimental API. We are still investigating
583
// a full solution for string manipulation and internationalization.
584
$.widget( "ui.autocomplete", $.ui.autocomplete, {
585
        options: {
586
                messages: {
587
                        noResults: "No search results.",
588
                        results: function( amount ) {
589
                                return amount + ( amount > 1 ? " results are" : " result is" ) +
590
                                        " available, use up and down arrow keys to navigate.";
591
                        }
592
                }
593
        },
594

    
595
        __response: function( content ) {
596
                var message;
597
                this._superApply( arguments );
598
                if ( this.options.disabled || this.cancelSearch ) {
599
                        return;
600
                }
601
                if ( content && content.length ) {
602
                        message = this.options.messages.results( content.length );
603
                } else {
604
                        message = this.options.messages.noResults;
605
                }
606
                this.liveRegion.text( message );
607
        }
608
});
609

    
610
}( jQuery ));