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));
 |