| /*! |
| * jQuery UI Autocomplete 1.8.23 |
| * |
| * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) |
| * Dual licensed under the MIT or GPL Version 2 licenses. |
| * http://jquery.org/license |
| * |
| * http://docs.jquery.com/UI/Autocomplete |
| * |
| * Depends: |
| * jquery.ui.core.js |
| * jquery.ui.widget.js |
| * jquery.ui.position.js |
| */ |
| (function( $, undefined ) { |
| |
| // used to prevent race conditions with remote data sources |
| var requestIndex = 0; |
| |
| $.widget( "ui.autocomplete", { |
| options: { |
| appendTo: "body", |
| autoFocus: false, |
| delay: 300, |
| minLength: 1, |
| position: { |
| my: "left top", |
| at: "left bottom", |
| collision: "none" |
| }, |
| source: null |
| }, |
| |
| pending: 0, |
| |
| _create: function() { |
| var self = this, |
| doc = this.element[ 0 ].ownerDocument, |
| suppressKeyPress; |
| this.isMultiLine = this.element.is( "textarea" ); |
| |
| this.element |
| .addClass( "ui-autocomplete-input" ) |
| .attr( "autocomplete", "off" ) |
| // TODO verify these actually work as intended |
| .attr({ |
| role: "textbox", |
| "aria-autocomplete": "list", |
| "aria-haspopup": "true" |
| }) |
| .bind( "keydown.autocomplete", function( event ) { |
| if ( self.options.disabled || self.element.propAttr( "readOnly" ) ) { |
| return; |
| } |
| |
| suppressKeyPress = false; |
| var keyCode = $.ui.keyCode; |
| switch( event.keyCode ) { |
| case keyCode.PAGE_UP: |
| self._move( "previousPage", event ); |
| break; |
| case keyCode.PAGE_DOWN: |
| self._move( "nextPage", event ); |
| break; |
| case keyCode.UP: |
| self._keyEvent( "previous", event ); |
| break; |
| case keyCode.DOWN: |
| self._keyEvent( "next", event ); |
| break; |
| case keyCode.ENTER: |
| case keyCode.NUMPAD_ENTER: |
| // when menu is open and has focus |
| if ( self.menu.active ) { |
| // #6055 - Opera still allows the keypress to occur |
| // which causes forms to submit |
| suppressKeyPress = true; |
| event.preventDefault(); |
| } |
| //passthrough - ENTER and TAB both select the current element |
| case keyCode.TAB: |
| if ( !self.menu.active ) { |
| return; |
| } |
| self.menu.select( event ); |
| break; |
| case keyCode.ESCAPE: |
| self.element.val( self.term ); |
| self.close( event ); |
| break; |
| default: |
| // keypress is triggered before the input value is changed |
| clearTimeout( self.searching ); |
| self.searching = setTimeout(function() { |
| // only search if the value has changed |
| if ( self.term != self.element.val() ) { |
| self.selectedItem = null; |
| self.search( null, event ); |
| } |
| }, self.options.delay ); |
| break; |
| } |
| }) |
| .bind( "keypress.autocomplete", function( event ) { |
| if ( suppressKeyPress ) { |
| suppressKeyPress = false; |
| event.preventDefault(); |
| } |
| }) |
| .bind( "focus.autocomplete", function() { |
| if ( self.options.disabled ) { |
| return; |
| } |
| |
| self.selectedItem = null; |
| self.previous = self.element.val(); |
| }) |
| .bind( "blur.autocomplete", function( event ) { |
| if ( self.options.disabled ) { |
| return; |
| } |
| |
| clearTimeout( self.searching ); |
| // clicks on the menu (or a button to trigger a search) will cause a blur event |
| self.closing = setTimeout(function() { |
| self.close( event ); |
| self._change( event ); |
| }, 150 ); |
| }); |
| this._initSource(); |
| this.menu = $( "<ul></ul>" ) |
| .addClass( "ui-autocomplete" ) |
| .appendTo( $( this.options.appendTo || "body", doc )[0] ) |
| // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown) |
| .mousedown(function( event ) { |
| // clicking on the scrollbar causes focus to shift to the body |
| // but we can't detect a mouseup or a click immediately afterward |
| // so we have to track the next mousedown and close the menu if |
| // the user clicks somewhere outside of the autocomplete |
| var menuElement = self.menu.element[ 0 ]; |
| if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { |
| setTimeout(function() { |
| $( document ).one( 'mousedown', function( event ) { |
| if ( event.target !== self.element[ 0 ] && |
| event.target !== menuElement && |
| !$.ui.contains( menuElement, event.target ) ) { |
| self.close(); |
| } |
| }); |
| }, 1 ); |
| } |
| |
| // use another timeout to make sure the blur-event-handler on the input was already triggered |
| setTimeout(function() { |
| clearTimeout( self.closing ); |
| }, 13); |
| }) |
| .menu({ |
| focus: function( event, ui ) { |
| var item = ui.item.data( "item.autocomplete" ); |
| if ( false !== self._trigger( "focus", event, { item: item } ) ) { |
| // use value to match what will end up in the input, if it was a key event |
| if ( /^key/.test(event.originalEvent.type) ) { |
| self.element.val( item.value ); |
| } |
| } |
| }, |
| selected: function( event, ui ) { |
| var item = ui.item.data( "item.autocomplete" ), |
| previous = self.previous; |
| |
| // only trigger when focus was lost (click on menu) |
| if ( self.element[0] !== doc.activeElement ) { |
| self.element.focus(); |
| self.previous = previous; |
| // #6109 - IE triggers two focus events and the second |
| // is asynchronous, so we need to reset the previous |
| // term synchronously and asynchronously :-( |
| setTimeout(function() { |
| self.previous = previous; |
| self.selectedItem = item; |
| }, 1); |
| } |
| |
| if ( false !== self._trigger( "select", event, { item: item } ) ) { |
| self.element.val( item.value ); |
| } |
| // reset the term after the select event |
| // this allows custom select handling to work properly |
| self.term = self.element.val(); |
| |
| self.close( event ); |
| self.selectedItem = item; |
| }, |
| blur: function( event, ui ) { |
| // don't set the value of the text field if it's already correct |
| // this prevents moving the cursor unnecessarily |
| if ( self.menu.element.is(":visible") && |
| ( self.element.val() !== self.term ) ) { |
| self.element.val( self.term ); |
| } |
| } |
| }) |
| .zIndex( this.element.zIndex() + 1 ) |
| // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781 |
| .css({ top: 0, left: 0 }) |
| .hide() |
| .data( "menu" ); |
| if ( $.fn.bgiframe ) { |
| this.menu.element.bgiframe(); |
| } |
| // turning off autocomplete prevents the browser from remembering the |
| // value when navigating through history, so we re-enable autocomplete |
| // if the page is unloaded before the widget is destroyed. #7790 |
| self.beforeunloadHandler = function() { |
| self.element.removeAttr( "autocomplete" ); |
| }; |
| $( window ).bind( "beforeunload", self.beforeunloadHandler ); |
| }, |
| |
| destroy: function() { |
| this.element |
| .removeClass( "ui-autocomplete-input" ) |
| .removeAttr( "autocomplete" ) |
| .removeAttr( "role" ) |
| .removeAttr( "aria-autocomplete" ) |
| .removeAttr( "aria-haspopup" ); |
| this.menu.element.remove(); |
| $( window ).unbind( "beforeunload", this.beforeunloadHandler ); |
| $.Widget.prototype.destroy.call( this ); |
| }, |
| |
| _setOption: function( key, value ) { |
| $.Widget.prototype._setOption.apply( this, arguments ); |
| if ( key === "source" ) { |
| this._initSource(); |
| } |
| if ( key === "appendTo" ) { |
| this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] ) |
| } |
| if ( key === "disabled" && value && this.xhr ) { |
| this.xhr.abort(); |
| } |
| }, |
| |
| _initSource: function() { |
| var self = this, |
| array, |
| url; |
| if ( $.isArray(this.options.source) ) { |
| array = this.options.source; |
| this.source = function( request, response ) { |
| response( $.ui.autocomplete.filter(array, request.term) ); |
| }; |
| } else if ( typeof this.options.source === "string" ) { |
| url = this.options.source; |
| this.source = function( request, response ) { |
| if ( self.xhr ) { |
| self.xhr.abort(); |
| } |
| self.xhr = $.ajax({ |
| url: url, |
| data: request, |
| dataType: "json", |
| success: function( data, status ) { |
| response( data ); |
| }, |
| error: function() { |
| response( [] ); |
| } |
| }); |
| }; |
| } else { |
| this.source = this.options.source; |
| } |
| }, |
| |
| search: function( value, event ) { |
| value = value != null ? value : this.element.val(); |
| |
| // always save the actual value, not the one passed as an argument |
| this.term = this.element.val(); |
| |
| if ( value.length < this.options.minLength ) { |
| return this.close( event ); |
| } |
| |
| clearTimeout( this.closing ); |
| if ( this._trigger( "search", event ) === false ) { |
| return; |
| } |
| |
| return this._search( value ); |
| }, |
| |
| _search: function( value ) { |
| this.pending++; |
| this.element.addClass( "ui-autocomplete-loading" ); |
| |
| this.source( { term: value }, this._response() ); |
| }, |
| |
| _response: function() { |
| var that = this, |
| index = ++requestIndex; |
| |
| return function( content ) { |
| if ( index === requestIndex ) { |
| that.__response( content ); |
| } |
| |
| that.pending--; |
| if ( !that.pending ) { |
| that.element.removeClass( "ui-autocomplete-loading" ); |
| } |
| }; |
| }, |
| |
| __response: function( content ) { |
| if ( !this.options.disabled && content && content.length ) { |
| content = this._normalize( content ); |
| this._suggest( content ); |
| this._trigger( "open" ); |
| } else { |
| this.close(); |
| } |
| }, |
| |
| close: function( event ) { |
| clearTimeout( this.closing ); |
| if ( this.menu.element.is(":visible") ) { |
| this.menu.element.hide(); |
| this.menu.deactivate(); |
| this._trigger( "close", event ); |
| } |
| }, |
| |
| _change: function( event ) { |
| if ( this.previous !== this.element.val() ) { |
| this._trigger( "change", event, { item: this.selectedItem } ); |
| } |
| }, |
| |
| _normalize: function( items ) { |
| // assume all items have the right format when the first item is complete |
| if ( items.length && items[0].label && items[0].value ) { |
| return items; |
| } |
| return $.map( items, function(item) { |
| if ( typeof item === "string" ) { |
| return { |
| label: item, |
| value: item |
| }; |
| } |
| return $.extend({ |
| label: item.label || item.value, |
| value: item.value || item.label |
| }, item ); |
| }); |
| }, |
| |
| _suggest: function( items ) { |
| var ul = this.menu.element |
| .empty() |
| .zIndex( this.element.zIndex() + 1 ); |
| this._renderMenu( ul, items ); |
| // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate |
| this.menu.deactivate(); |
| this.menu.refresh(); |
| |
| // size and position menu |
| ul.show(); |
| this._resizeMenu(); |
| ul.position( $.extend({ |
| of: this.element |
| }, this.options.position )); |
| |
| if ( this.options.autoFocus ) { |
| this.menu.next( new $.Event("mouseover") ); |
| } |
| }, |
| |
| _resizeMenu: function() { |
| var ul = this.menu.element; |
| ul.outerWidth( Math.max( |
| // Firefox wraps long text (possibly a rounding bug) |
| // so we add 1px to avoid the wrapping (#7513) |
| ul.width( "" ).outerWidth() + 1, |
| this.element.outerWidth() |
| ) ); |
| }, |
| |
| _renderMenu: function( ul, items ) { |
| var self = this; |
| $.each( items, function( index, item ) { |
| self._renderItem( ul, item ); |
| }); |
| }, |
| |
| _renderItem: function( ul, item) { |
| return $( "<li></li>" ) |
| .data( "item.autocomplete", item ) |
| .append( $( "<a></a>" ).text( item.label ) ) |
| .appendTo( ul ); |
| }, |
| |
| _move: function( direction, event ) { |
| if ( !this.menu.element.is(":visible") ) { |
| this.search( null, event ); |
| return; |
| } |
| if ( this.menu.first() && /^previous/.test(direction) || |
| this.menu.last() && /^next/.test(direction) ) { |
| this.element.val( this.term ); |
| this.menu.deactivate(); |
| return; |
| } |
| this.menu[ direction ]( event ); |
| }, |
| |
| widget: function() { |
| return this.menu.element; |
| }, |
| _keyEvent: function( keyEvent, event ) { |
| if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { |
| this._move( keyEvent, event ); |
| |
| // prevents moving cursor to beginning/end of the text field in some browsers |
| event.preventDefault(); |
| } |
| } |
| }); |
| |
| $.extend( $.ui.autocomplete, { |
| escapeRegex: function( value ) { |
| return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); |
| }, |
| filter: function(array, term) { |
| var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); |
| return $.grep( array, function(value) { |
| return matcher.test( value.label || value.value || value ); |
| }); |
| } |
| }); |
| |
| }( jQuery )); |
| |
| /* |
| * jQuery UI Menu (not officially released) |
| * |
| * This widget isn't yet finished and the API is subject to change. We plan to finish |
| * it for the next release. You're welcome to give it a try anyway and give us feedback, |
| * as long as you're okay with migrating your code later on. We can help with that, too. |
| * |
| * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) |
| * Dual licensed under the MIT or GPL Version 2 licenses. |
| * http://jquery.org/license |
| * |
| * http://docs.jquery.com/UI/Menu |
| * |
| * Depends: |
| * jquery.ui.core.js |
| * jquery.ui.widget.js |
| */ |
| (function($) { |
| |
| $.widget("ui.menu", { |
| _create: function() { |
| var self = this; |
| this.element |
| .addClass("ui-menu ui-widget ui-widget-content ui-corner-all") |
| .attr({ |
| role: "listbox", |
| "aria-activedescendant": "ui-active-menuitem" |
| }) |
| .click(function( event ) { |
| if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) { |
| return; |
| } |
| // temporary |
| event.preventDefault(); |
| self.select( event ); |
| }); |
| this.refresh(); |
| }, |
| |
| refresh: function() { |
| var self = this; |
| |
| // don't refresh list items that are already adapted |
| var items = this.element.children("li:not(.ui-menu-item):has(a)") |
| .addClass("ui-menu-item") |
| .attr("role", "menuitem"); |
| |
| items.children("a") |
| .addClass("ui-corner-all") |
| .attr("tabindex", -1) |
| // mouseenter doesn't work with event delegation |
| .mouseenter(function( event ) { |
| self.activate( event, $(this).parent() ); |
| }) |
| .mouseleave(function() { |
| self.deactivate(); |
| }); |
| }, |
| |
| activate: function( event, item ) { |
| this.deactivate(); |
| if (this.hasScroll()) { |
| var offset = item.offset().top - this.element.offset().top, |
| scroll = this.element.scrollTop(), |
| elementHeight = this.element.height(); |
| if (offset < 0) { |
| this.element.scrollTop( scroll + offset); |
| } else if (offset >= elementHeight) { |
| this.element.scrollTop( scroll + offset - elementHeight + item.height()); |
| } |
| } |
| this.active = item.eq(0) |
| .children("a") |
| .addClass("ui-state-hover") |
| .attr("id", "ui-active-menuitem") |
| .end(); |
| this._trigger("focus", event, { item: item }); |
| }, |
| |
| deactivate: function() { |
| if (!this.active) { return; } |
| |
| this.active.children("a") |
| .removeClass("ui-state-hover") |
| .removeAttr("id"); |
| this._trigger("blur"); |
| this.active = null; |
| }, |
| |
| next: function(event) { |
| this.move("next", ".ui-menu-item:first", event); |
| }, |
| |
| previous: function(event) { |
| this.move("prev", ".ui-menu-item:last", event); |
| }, |
| |
| first: function() { |
| return this.active && !this.active.prevAll(".ui-menu-item").length; |
| }, |
| |
| last: function() { |
| return this.active && !this.active.nextAll(".ui-menu-item").length; |
| }, |
| |
| move: function(direction, edge, event) { |
| if (!this.active) { |
| this.activate(event, this.element.children(edge)); |
| return; |
| } |
| var next = this.active[direction + "All"](".ui-menu-item").eq(0); |
| if (next.length) { |
| this.activate(event, next); |
| } else { |
| this.activate(event, this.element.children(edge)); |
| } |
| }, |
| |
| // TODO merge with previousPage |
| nextPage: function(event) { |
| if (this.hasScroll()) { |
| // TODO merge with no-scroll-else |
| if (!this.active || this.last()) { |
| this.activate(event, this.element.children(".ui-menu-item:first")); |
| return; |
| } |
| var base = this.active.offset().top, |
| height = this.element.height(), |
| result = this.element.children(".ui-menu-item").filter(function() { |
| var close = $(this).offset().top - base - height + $(this).height(); |
| // TODO improve approximation |
| return close < 10 && close > -10; |
| }); |
| |
| // TODO try to catch this earlier when scrollTop indicates the last page anyway |
| if (!result.length) { |
| result = this.element.children(".ui-menu-item:last"); |
| } |
| this.activate(event, result); |
| } else { |
| this.activate(event, this.element.children(".ui-menu-item") |
| .filter(!this.active || this.last() ? ":first" : ":last")); |
| } |
| }, |
| |
| // TODO merge with nextPage |
| previousPage: function(event) { |
| if (this.hasScroll()) { |
| // TODO merge with no-scroll-else |
| if (!this.active || this.first()) { |
| this.activate(event, this.element.children(".ui-menu-item:last")); |
| return; |
| } |
| |
| var base = this.active.offset().top, |
| height = this.element.height(), |
| result = this.element.children(".ui-menu-item").filter(function() { |
| var close = $(this).offset().top - base + height - $(this).height(); |
| // TODO improve approximation |
| return close < 10 && close > -10; |
| }); |
| |
| // TODO try to catch this earlier when scrollTop indicates the last page anyway |
| if (!result.length) { |
| result = this.element.children(".ui-menu-item:first"); |
| } |
| this.activate(event, result); |
| } else { |
| this.activate(event, this.element.children(".ui-menu-item") |
| .filter(!this.active || this.first() ? ":last" : ":first")); |
| } |
| }, |
| |
| hasScroll: function() { |
| return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight"); |
| }, |
| |
| select: function( event ) { |
| this._trigger("selected", event, { item: this.active }); |
| } |
| }); |
| |
| }(jQuery)); |