]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - js/ui/jquery.ui.autocomplete.js
Arg. Gotta remember to take out console.log() debug statements before committing :D
[quix0rs-gnu-social.git] / js / ui / jquery.ui.autocomplete.js
1 /*
2  * jQuery UI Autocomplete 1.8.10
3  *
4  * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
5  * Dual licensed under the MIT or GPL Version 2 licenses.
6  * http://jquery.org/license
7  *
8  * http://docs.jquery.com/UI/Autocomplete
9  *
10  * Depends:
11  *      jquery.ui.core.js
12  *      jquery.ui.widget.js
13  *      jquery.ui.position.js
14  */
15 (function( $, undefined ) {
16
17 // used to prevent race conditions with remote data sources
18 var requestIndex = 0;
19
20 $.widget( "ui.autocomplete", {
21         options: {
22                 appendTo: "body",
23                 delay: 300,
24                 minLength: 1,
25                 position: {
26                         my: "left top",
27                         at: "left bottom",
28                         collision: "none"
29                 },
30                 source: null
31         },
32
33         pending: 0,
34
35         _create: function() {
36                 var self = this,
37                         doc = this.element[ 0 ].ownerDocument,
38                         suppressKeyPress;
39
40                 this.element
41                         .addClass( "ui-autocomplete-input" )
42                         .attr( "autocomplete", "off" )
43                         // TODO verify these actually work as intended
44                         .attr({
45                                 role: "textbox",
46                                 "aria-autocomplete": "list",
47                                 "aria-haspopup": "true"
48                         })
49                         .bind( "keydown.autocomplete", function( event ) {
50                                 if ( self.options.disabled || self.element.attr( "readonly" ) ) {
51                                         return;
52                                 }
53
54                                 suppressKeyPress = false;
55                                 var keyCode = $.ui.keyCode;
56                                 switch( event.keyCode ) {
57                                 case keyCode.PAGE_UP:
58                                         self._move( "previousPage", event );
59                                         break;
60                                 case keyCode.PAGE_DOWN:
61                                         self._move( "nextPage", event );
62                                         break;
63                                 case keyCode.UP:
64                                         self._move( "previous", event );
65                                         // prevent moving cursor to beginning of text field in some browsers
66                                         event.preventDefault();
67                                         break;
68                                 case keyCode.DOWN:
69                                         self._move( "next", event );
70                                         // prevent moving cursor to end of text field in some browsers
71                                         event.preventDefault();
72                                         break;
73                                 case keyCode.ENTER:
74                                 case keyCode.NUMPAD_ENTER:
75                                         // when menu is open and has focus
76                                         if ( self.menu.active ) {
77                                                 // #6055 - Opera still allows the keypress to occur
78                                                 // which causes forms to submit
79                                                 suppressKeyPress = true;
80                                                 event.preventDefault();
81                                         }
82                                         //passthrough - ENTER and TAB both select the current element
83                                 case keyCode.TAB:
84                                         if ( !self.menu.active ) {
85                                                 return;
86                                         }
87                                         self.menu.select( event );
88                                         break;
89                                 case keyCode.ESCAPE:
90                                         self.element.val( self.term );
91                                         self.close( event );
92                                         break;
93                                 default:
94                                         // keypress is triggered before the input value is changed
95                                         clearTimeout( self.searching );
96                                         self.searching = setTimeout(function() {
97                                                 // only search if the value has changed
98                                                 if ( self.term != self.element.val() ) {
99                                                         self.selectedItem = null;
100                                                         self.search( null, event );
101                                                 }
102                                         }, self.options.delay );
103                                         break;
104                                 }
105                         })
106                         .bind( "keypress.autocomplete", function( event ) {
107                                 if ( suppressKeyPress ) {
108                                         suppressKeyPress = false;
109                                         event.preventDefault();
110                                 }
111                         })
112                         .bind( "focus.autocomplete", function() {
113                                 if ( self.options.disabled ) {
114                                         return;
115                                 }
116
117                                 self.selectedItem = null;
118                                 self.previous = self.element.val();
119                         })
120                         .bind( "blur.autocomplete", function( event ) {
121                                 if ( self.options.disabled ) {
122                                         return;
123                                 }
124
125                                 clearTimeout( self.searching );
126                                 // clicks on the menu (or a button to trigger a search) will cause a blur event
127                                 self.closing = setTimeout(function() {
128                                         self.close( event );
129                                         self._change( event );
130                                 }, 150 );
131                         });
132                 this._initSource();
133                 this.response = function() {
134                         return self._response.apply( self, arguments );
135                 };
136                 this.menu = $( "<ul></ul>" )
137                         .addClass( "ui-autocomplete" )
138                         .appendTo( $( this.options.appendTo || "body", doc )[0] )
139                         // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
140                         .mousedown(function( event ) {
141                                 // clicking on the scrollbar causes focus to shift to the body
142                                 // but we can't detect a mouseup or a click immediately afterward
143                                 // so we have to track the next mousedown and close the menu if
144                                 // the user clicks somewhere outside of the autocomplete
145                                 var menuElement = self.menu.element[ 0 ];
146                                 if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
147                                         setTimeout(function() {
148                                                 $( document ).one( 'mousedown', function( event ) {
149                                                         if ( event.target !== self.element[ 0 ] &&
150                                                                 event.target !== menuElement &&
151                                                                 !$.ui.contains( menuElement, event.target ) ) {
152                                                                 self.close();
153                                                         }
154                                                 });
155                                         }, 1 );
156                                 }
157
158                                 // use another timeout to make sure the blur-event-handler on the input was already triggered
159                                 setTimeout(function() {
160                                         clearTimeout( self.closing );
161                                 }, 13);
162                         })
163                         .menu({
164                                 focus: function( event, ui ) {
165                                         var item = ui.item.data( "item.autocomplete" );
166                                         if ( false !== self._trigger( "focus", event, { item: item } ) ) {
167                                                 // use value to match what will end up in the input, if it was a key event
168                                                 if ( /^key/.test(event.originalEvent.type) ) {
169                                                         self.element.val( item.value );
170                                                 }
171                                         }
172                                 },
173                                 selected: function( event, ui ) {
174                                         var item = ui.item.data( "item.autocomplete" ),
175                                                 previous = self.previous;
176
177                                         // only trigger when focus was lost (click on menu)
178                                         if ( self.element[0] !== doc.activeElement ) {
179                                                 self.element.focus();
180                                                 self.previous = previous;
181                                                 // #6109 - IE triggers two focus events and the second
182                                                 // is asynchronous, so we need to reset the previous
183                                                 // term synchronously and asynchronously :-(
184                                                 setTimeout(function() {
185                                                         self.previous = previous;
186                                                         self.selectedItem = item;
187                                                 }, 1);
188                                         }
189
190                                         if ( false !== self._trigger( "select", event, { item: item } ) ) {
191                                                 self.element.val( item.value );
192                                         }
193                                         // reset the term after the select event
194                                         // this allows custom select handling to work properly
195                                         self.term = self.element.val();
196
197                                         self.close( event );
198                                         self.selectedItem = item;
199                                 },
200                                 blur: function( event, ui ) {
201                                         // don't set the value of the text field if it's already correct
202                                         // this prevents moving the cursor unnecessarily
203                                         if ( self.menu.element.is(":visible") &&
204                                                 ( self.element.val() !== self.term ) ) {
205                                                 self.element.val( self.term );
206                                         }
207                                 }
208                         })
209                         .zIndex( this.element.zIndex() + 1 )
210                         // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
211                         .css({ top: 0, left: 0 })
212                         .hide()
213                         .data( "menu" );
214                 if ( $.fn.bgiframe ) {
215                          this.menu.element.bgiframe();
216                 }
217         },
218
219         destroy: function() {
220                 this.element
221                         .removeClass( "ui-autocomplete-input" )
222                         .removeAttr( "autocomplete" )
223                         .removeAttr( "role" )
224                         .removeAttr( "aria-autocomplete" )
225                         .removeAttr( "aria-haspopup" );
226                 this.menu.element.remove();
227                 $.Widget.prototype.destroy.call( this );
228         },
229
230         _setOption: function( key, value ) {
231                 $.Widget.prototype._setOption.apply( this, arguments );
232                 if ( key === "source" ) {
233                         this._initSource();
234                 }
235                 if ( key === "appendTo" ) {
236                         this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
237                 }
238                 if ( key === "disabled" && value && this.xhr ) {
239                         this.xhr.abort();
240                 }
241         },
242
243         _initSource: function() {
244                 var self = this,
245                         array,
246                         url;
247                 if ( $.isArray(this.options.source) ) {
248                         array = this.options.source;
249                         this.source = function( request, response ) {
250                                 response( $.ui.autocomplete.filter(array, request.term) );
251                         };
252                 } else if ( typeof this.options.source === "string" ) {
253                         url = this.options.source;
254                         this.source = function( request, response ) {
255                                 if ( self.xhr ) {
256                                         self.xhr.abort();
257                                 }
258                                 self.xhr = $.ajax({
259                                         url: url,
260                                         data: request,
261                                         dataType: "json",
262                                         autocompleteRequest: ++requestIndex,
263                                         success: function( data, status ) {
264                                                 if ( this.autocompleteRequest === requestIndex ) {
265                                                         response( data );
266                                                 }
267                                         },
268                                         error: function() {
269                                                 if ( this.autocompleteRequest === requestIndex ) {
270                                                         response( [] );
271                                                 }
272                                         }
273                                 });
274                         };
275                 } else {
276                         this.source = this.options.source;
277                 }
278         },
279
280         search: function( value, event ) {
281                 value = value != null ? value : this.element.val();
282
283                 // always save the actual value, not the one passed as an argument
284                 this.term = this.element.val();
285
286                 if ( value.length < this.options.minLength ) {
287                         return this.close( event );
288                 }
289
290                 clearTimeout( this.closing );
291                 if ( this._trigger( "search", event ) === false ) {
292                         return;
293                 }
294
295                 return this._search( value );
296         },
297
298         _search: function( value ) {
299                 this.pending++;
300                 this.element.addClass( "ui-autocomplete-loading" );
301
302                 this.source( { term: value }, this.response );
303         },
304
305         _response: function( content ) {
306                 if ( !this.options.disabled && content && content.length ) {
307                         content = this._normalize( content );
308                         this._suggest( content );
309                         this._trigger( "open" );
310                 } else {
311                         this.close();
312                 }
313                 this.pending--;
314                 if ( !this.pending ) {
315                         this.element.removeClass( "ui-autocomplete-loading" );
316                 }
317         },
318
319         close: function( event ) {
320                 clearTimeout( this.closing );
321                 if ( this.menu.element.is(":visible") ) {
322                         this.menu.element.hide();
323                         this.menu.deactivate();
324                         this._trigger( "close", event );
325                 }
326         },
327         
328         _change: function( event ) {
329                 if ( this.previous !== this.element.val() ) {
330                         this._trigger( "change", event, { item: this.selectedItem } );
331                 }
332         },
333
334         _normalize: function( items ) {
335                 // assume all items have the right format when the first item is complete
336                 if ( items.length && items[0].label && items[0].value ) {
337                         return items;
338                 }
339                 return $.map( items, function(item) {
340                         if ( typeof item === "string" ) {
341                                 return {
342                                         label: item,
343                                         value: item
344                                 };
345                         }
346                         return $.extend({
347                                 label: item.label || item.value,
348                                 value: item.value || item.label
349                         }, item );
350                 });
351         },
352
353         _suggest: function( items ) {
354                 var ul = this.menu.element
355                         .empty()
356                         .zIndex( this.element.zIndex() + 1 );
357                 this._renderMenu( ul, items );
358                 // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
359                 this.menu.deactivate();
360                 this.menu.refresh();
361
362                 // size and position menu
363                 ul.show();
364                 this._resizeMenu();
365                 ul.position( $.extend({
366                         of: this.element
367                 }, this.options.position ));
368         },
369
370         _resizeMenu: function() {
371                 var ul = this.menu.element;
372                 ul.outerWidth( Math.max(
373                         ul.width( "" ).outerWidth(),
374                         this.element.outerWidth()
375                 ) );
376         },
377
378         _renderMenu: function( ul, items ) {
379                 var self = this;
380                 $.each( items, function( index, item ) {
381                         self._renderItem( ul, item );
382                 });
383         },
384
385         _renderItem: function( ul, item) {
386                 return $( "<li></li>" )
387                         .data( "item.autocomplete", item )
388                         .append( $( "<a></a>" ).text( item.label ) )
389                         .appendTo( ul );
390         },
391
392         _move: function( direction, event ) {
393                 if ( !this.menu.element.is(":visible") ) {
394                         this.search( null, event );
395                         return;
396                 }
397                 if ( this.menu.first() && /^previous/.test(direction) ||
398                                 this.menu.last() && /^next/.test(direction) ) {
399                         this.element.val( this.term );
400                         this.menu.deactivate();
401                         return;
402                 }
403                 this.menu[ direction ]( event );
404         },
405
406         widget: function() {
407                 return this.menu.element;
408         }
409 });
410
411 $.extend( $.ui.autocomplete, {
412         escapeRegex: function( value ) {
413                 return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
414         },
415         filter: function(array, term) {
416                 var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
417                 return $.grep( array, function(value) {
418                         return matcher.test( value.label || value.value || value );
419                 });
420         }
421 });
422
423 }( jQuery ));
424
425 /*
426  * jQuery UI Menu (not officially released)
427  * 
428  * This widget isn't yet finished and the API is subject to change. We plan to finish
429  * it for the next release. You're welcome to give it a try anyway and give us feedback,
430  * as long as you're okay with migrating your code later on. We can help with that, too.
431  *
432  * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
433  * Dual licensed under the MIT or GPL Version 2 licenses.
434  * http://jquery.org/license
435  *
436  * http://docs.jquery.com/UI/Menu
437  *
438  * Depends:
439  *      jquery.ui.core.js
440  *  jquery.ui.widget.js
441  */
442 (function($) {
443
444 $.widget("ui.menu", {
445         _create: function() {
446                 var self = this;
447                 this.element
448                         .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
449                         .attr({
450                                 role: "listbox",
451                                 "aria-activedescendant": "ui-active-menuitem"
452                         })
453                         .click(function( event ) {
454                                 if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
455                                         return;
456                                 }
457                                 // temporary
458                                 event.preventDefault();
459                                 self.select( event );
460                         });
461                 this.refresh();
462         },
463         
464         refresh: function() {
465                 var self = this;
466
467                 // don't refresh list items that are already adapted
468                 var items = this.element.children("li:not(.ui-menu-item):has(a)")
469                         .addClass("ui-menu-item")
470                         .attr("role", "menuitem");
471                 
472                 items.children("a")
473                         .addClass("ui-corner-all")
474                         .attr("tabindex", -1)
475                         // mouseenter doesn't work with event delegation
476                         .mouseenter(function( event ) {
477                                 self.activate( event, $(this).parent() );
478                         })
479                         .mouseleave(function() {
480                                 self.deactivate();
481                         });
482         },
483
484         activate: function( event, item ) {
485                 this.deactivate();
486                 if (this.hasScroll()) {
487                         var offset = item.offset().top - this.element.offset().top,
488                                 scroll = this.element.attr("scrollTop"),
489                                 elementHeight = this.element.height();
490                         if (offset < 0) {
491                                 this.element.attr("scrollTop", scroll + offset);
492                         } else if (offset >= elementHeight) {
493                                 this.element.attr("scrollTop", scroll + offset - elementHeight + item.height());
494                         }
495                 }
496                 this.active = item.eq(0)
497                         .children("a")
498                                 .addClass("ui-state-hover")
499                                 .attr("id", "ui-active-menuitem")
500                         .end();
501                 this._trigger("focus", event, { item: item });
502         },
503
504         deactivate: function() {
505                 if (!this.active) { return; }
506
507                 this.active.children("a")
508                         .removeClass("ui-state-hover")
509                         .removeAttr("id");
510                 this._trigger("blur");
511                 this.active = null;
512         },
513
514         next: function(event) {
515                 this.move("next", ".ui-menu-item:first", event);
516         },
517
518         previous: function(event) {
519                 this.move("prev", ".ui-menu-item:last", event);
520         },
521
522         first: function() {
523                 return this.active && !this.active.prevAll(".ui-menu-item").length;
524         },
525
526         last: function() {
527                 return this.active && !this.active.nextAll(".ui-menu-item").length;
528         },
529
530         move: function(direction, edge, event) {
531                 if (!this.active) {
532                         this.activate(event, this.element.children(edge));
533                         return;
534                 }
535                 var next = this.active[direction + "All"](".ui-menu-item").eq(0);
536                 if (next.length) {
537                         this.activate(event, next);
538                 } else {
539                         this.activate(event, this.element.children(edge));
540                 }
541         },
542
543         // TODO merge with previousPage
544         nextPage: function(event) {
545                 if (this.hasScroll()) {
546                         // TODO merge with no-scroll-else
547                         if (!this.active || this.last()) {
548                                 this.activate(event, this.element.children(".ui-menu-item:first"));
549                                 return;
550                         }
551                         var base = this.active.offset().top,
552                                 height = this.element.height(),
553                                 result = this.element.children(".ui-menu-item").filter(function() {
554                                         var close = $(this).offset().top - base - height + $(this).height();
555                                         // TODO improve approximation
556                                         return close < 10 && close > -10;
557                                 });
558
559                         // TODO try to catch this earlier when scrollTop indicates the last page anyway
560                         if (!result.length) {
561                                 result = this.element.children(".ui-menu-item:last");
562                         }
563                         this.activate(event, result);
564                 } else {
565                         this.activate(event, this.element.children(".ui-menu-item")
566                                 .filter(!this.active || this.last() ? ":first" : ":last"));
567                 }
568         },
569
570         // TODO merge with nextPage
571         previousPage: function(event) {
572                 if (this.hasScroll()) {
573                         // TODO merge with no-scroll-else
574                         if (!this.active || this.first()) {
575                                 this.activate(event, this.element.children(".ui-menu-item:last"));
576                                 return;
577                         }
578
579                         var base = this.active.offset().top,
580                                 height = this.element.height();
581                                 result = this.element.children(".ui-menu-item").filter(function() {
582                                         var close = $(this).offset().top - base + height - $(this).height();
583                                         // TODO improve approximation
584                                         return close < 10 && close > -10;
585                                 });
586
587                         // TODO try to catch this earlier when scrollTop indicates the last page anyway
588                         if (!result.length) {
589                                 result = this.element.children(".ui-menu-item:first");
590                         }
591                         this.activate(event, result);
592                 } else {
593                         this.activate(event, this.element.children(".ui-menu-item")
594                                 .filter(!this.active || this.first() ? ":last" : ":first"));
595                 }
596         },
597
598         hasScroll: function() {
599                 return this.element.height() < this.element.attr("scrollHeight");
600         },
601
602         select: function( event ) {
603                 this._trigger("selected", event, { item: this.active });
604         }
605 });
606
607 }(jQuery));