forked from GNUsocial/gnu-social
		
	
		
			
	
	
		
			608 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			608 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|   | /* | ||
|  |  * jQuery UI Autocomplete 1.8.10 | ||
|  |  * | ||
|  |  * Copyright 2011, 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", | ||
|  | 		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.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.attr( "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._move( "previous", event ); | ||
|  | 					// prevent moving cursor to beginning of text field in some browsers
 | ||
|  | 					event.preventDefault(); | ||
|  | 					break; | ||
|  | 				case keyCode.DOWN: | ||
|  | 					self._move( "next", event ); | ||
|  | 					// prevent moving cursor to end of text field in some browsers
 | ||
|  | 					event.preventDefault(); | ||
|  | 					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.response = function() { | ||
|  | 			return self._response.apply( self, arguments ); | ||
|  | 		}; | ||
|  | 		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(); | ||
|  | 		} | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	destroy: function() { | ||
|  | 		this.element | ||
|  | 			.removeClass( "ui-autocomplete-input" ) | ||
|  | 			.removeAttr( "autocomplete" ) | ||
|  | 			.removeAttr( "role" ) | ||
|  | 			.removeAttr( "aria-autocomplete" ) | ||
|  | 			.removeAttr( "aria-haspopup" ); | ||
|  | 		this.menu.element.remove(); | ||
|  | 		$.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", | ||
|  | 					autocompleteRequest: ++requestIndex, | ||
|  | 					success: function( data, status ) { | ||
|  | 						if ( this.autocompleteRequest === requestIndex ) { | ||
|  | 							response( data ); | ||
|  | 						} | ||
|  | 					}, | ||
|  | 					error: function() { | ||
|  | 						if ( this.autocompleteRequest === requestIndex ) { | ||
|  | 							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( content ) { | ||
|  | 		if ( !this.options.disabled && content && content.length ) { | ||
|  | 			content = this._normalize( content ); | ||
|  | 			this._suggest( content ); | ||
|  | 			this._trigger( "open" ); | ||
|  | 		} else { | ||
|  | 			this.close(); | ||
|  | 		} | ||
|  | 		this.pending--; | ||
|  | 		if ( !this.pending ) { | ||
|  | 			this.element.removeClass( "ui-autocomplete-loading" ); | ||
|  | 		} | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	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 )); | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	_resizeMenu: function() { | ||
|  | 		var ul = this.menu.element; | ||
|  | 		ul.outerWidth( Math.max( | ||
|  | 			ul.width( "" ).outerWidth(), | ||
|  | 			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; | ||
|  | 	} | ||
|  | }); | ||
|  | 
 | ||
|  | $.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.attr("scrollTop"), | ||
|  | 				elementHeight = this.element.height(); | ||
|  | 			if (offset < 0) { | ||
|  | 				this.element.attr("scrollTop", scroll + offset); | ||
|  | 			} else if (offset >= elementHeight) { | ||
|  | 				this.element.attr("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.attr("scrollHeight"); | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	select: function( event ) { | ||
|  | 		this._trigger("selected", event, { item: this.active }); | ||
|  | 	} | ||
|  | }); | ||
|  | 
 | ||
|  | }(jQuery)); |