topical media & game development
mobile-js-jquery-congress-jquery.mobile-1.0.js / js
/*
* jQuery Mobile Framework 1.0
* http://jquerymobile.com
*
* Copyright 2011 (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
*/
/*!
* jQuery UI Widget @VERSION
*
* 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/Widget
*/
(function( .cleanData ) {
var _cleanData = .cleanData = function( elems ) {
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
$( elem ).triggerHandler( "remove" );
}
_cleanData( elems );
};
} else {
var _remove = .fn.remove = function( selector, keepData ) {
return this.each(function() {
if ( !keepData ) {
if ( !selector || .widget = function( name, base, prototype ) {
var namespace = name.split( "." )[ 0 ],
fullName;
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
if ( !prototype ) {
prototype = base;
base = .expr[ ":" ][ fullName ] = function( elem ) {
return !![ namespace ] = [ namespace ][ name ] = function( options, element ) {
// allow instantiation without initializing for simple inheritance
if ( arguments.length ) {
this._createWidget( options, element );
}
};
var basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
// .isPlainObject(val) ) {
// basePrototype[ key ] = .extend( true, {}, basePrototype.options );
.extend( true, basePrototype, {
namespace: namespace,
widgetName: name,
widgetEventPrefix: .widget.bridge( name, .widget.bridge = function( name, object ) {
.extend.apply( null, [ true, options ].concat(args) ) :
options;
// prevent calls to internal methods
if ( isMethodCall && options.charAt( 0 ) === "_" ) {
return returnValue;
}
if ( isMethodCall ) {
this.each(function() {
var instance = .isFunction( instance[options] ) ) {
throw "no such method '" + options + "' for " + name + " widget instance";
}
var methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = .data( this, name, new object( options, this ) );
}
});
}
return returnValue;
};
};
.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
options: {
disabled: false
},
_createWidget: function( options, element ) {
// .data( element, this.widgetName, this );
this.element = $( element );
this.options = .metadata ) {
options = .extend( {}, this.options );
}
if (typeof key === "string" ) {
if ( value === undefined ) {
return this.options[ key ];
}
options = {};
options[ key ] = value;
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var self = this;
.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
data = data || {};
// copy original event properties over to the new event
// this would happen if we could call .Event
// but we don't have a way to force an event to be fixed multiple times
if ( event.originalEvent ) {
for ( var i = .event.props[ --i ];
event[ prop ] = event.originalEvent[ prop ];
}
}
this.element.trigger( event, data );
return !( , undefined ) {
.Widget.prototype._createWidget.apply( this, arguments );
this._trigger( 'init' );
},
_getCreateOptions: function() {
var elem = this.element,
options = {};
, undefined ) {
var html = $( "html" );
/* .mobile.media('screen') //>> tests for screen media type
.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') //>> tests for webkit 2x pixel ratio (iPhone 4)
*/
html.prepend( fakeBody ).prepend( styleBlock );
cache[ query ] = testDiv.css( "position" ) === "absolute";
fakeBody.add( styleBlock ).remove();
}
return cache[ query ];
};
})();
})(jQuery);
/*
* support tests
*/
(function( .mobile.browser = {};
.extend( .mobile.media( "only all" ),
cssPseudoElement: !!propExists( "content" ),
touchOverflow: !!propExists( "overflowScrolling" ),
boxShadow: !!propExists( "boxShadow" ) && !bb,
scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos && !operamini,
dynamicBaseTag: baseTagTest()
});
fakeBody.remove();
// .mobile.ajaxBlacklist =
// BlackBerry browsers, pre-webkit
window.blackberry && !window.WebKitPoint ||
// Opera Mini
operamini ||
// Symbian webkits pre 7.3
nokiaLTE7_3;
// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices
// to render the stylesheets when they're referenced before this script, as we'd recommend doing.
// This simply reappends the CSS in place, which for some reason makes it apply
if ( nokiaLTE7_3 ) {
$(function() {
$( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" );
});
}
// For ruling out shadows via css
if ( !, window, document, undefined ) {
var dataPropertyName = "virtualMouseBindings",
touchTargetPropertyName = "virtualTouchID",
virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
activeDocHandlers = {},
resetTimerID = 0,
startX = 0,
startY = 0,
didScroll = false,
clickBlockList = [],
blockMouseTriggers = false,
blockTouchTriggers = false,
eventCaptureSupported = "addEventListener" in document,
.vmouse = {
moveDistanceThreshold: 10,
clickDistanceThreshold: 10,
resetTimerDuration: 1500
};
function getNativeEvent( event ) {
while ( event && typeof event.originalEvent !== "undefined" ) {
event = event.originalEvent;
}
return event;
}
function createVirtualEvent( event, eventType ) {
var t = event.type,
oe, props, ne, prop, ct, touch, i, j;
event = .event.props;
// copy original event properties over to the new event
// this would happen if we could call .Event
// but we don't have a way to force an event to be fixed multiple times
if ( oe ) {
for ( i = props.length, prop; i; ) {
prop = props[ --i ];
event[ prop ] = oe[ prop ];
}
}
// make sure that if the mouse and click virtual events are generated
// without a .which one is defined
if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){
event.which = 1;
}
if ( t.search(/^touch/) !== -1 ) {
ne = getNativeEvent( oe );
t = ne.touches;
ct = ne.changedTouches;
touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined );
if ( touch ) {
for ( j = 0, len = touchEventProps.length; j < len; j++){
prop = touchEventProps[ j ];
event[ prop ] = touch[ prop ];
}
}
}
return event;
}
function getVirtualBindingFlags( element ) {
var flags = {},
b, k;
while ( element ) {
b = .data( element, dataPropertyName );
if ( b && ( !eventType || b[ eventType ] ) ) {
return element;
}
element = element.parentNode;
}
return null;
}
function enableTouchBindings() {
blockTouchTriggers = false;
}
function disableTouchBindings() {
blockTouchTriggers = true;
}
function enableMouseBindings() {
lastTouchID = 0;
clickBlockList.length = 0;
blockMouseTriggers = false;
// When mouse bindings are enabled, our
// touch bindings are disabled.
disableTouchBindings();
}
function disableMouseBindings() {
// When mouse bindings are disabled, our
// touch bindings are enabled.
enableTouchBindings();
}
function startResetTimer() {
clearResetTimer();
resetTimerID = setTimeout(function(){
resetTimerID = 0;
enableMouseBindings();
}, .data(event.target, touchTargetPropertyName);
if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){
var ve = triggerVirtualEvent( "v" + event.type, event );
if ( ve ) {
if ( ve.isDefaultPrevented() ) {
event.preventDefault();
}
if ( ve.isPropagationStopped() ) {
event.stopPropagation();
}
if ( ve.isImmediatePropagationStopped() ) {
event.stopImmediatePropagation();
}
}
}
}
function handleTouchStart( event ) {
var touches = getNativeEvent( event ).touches,
target, flags;
if ( touches && touches.length === 1 ) {
target = event.target;
flags = getVirtualBindingFlags( target );
if ( flags.hasVirtualBinding ) {
lastTouchID = nextTouchID++;
.vmouse.moveDistanceThreshold;
didScroll = didScroll ||
( Math.abs(t.pageX - startX) > moveThreshold ||
Math.abs(t.pageY - startY) > moveThreshold ),
flags = getVirtualBindingFlags( event.target );
if ( didScroll && !didCancel ) {
triggerVirtualEvent( "vmousecancel", event, flags );
}
triggerVirtualEvent( "vmousemove", event, flags );
startResetTimer();
}
function handleTouchEnd( event ) {
if ( blockTouchTriggers ) {
return;
}
disableTouchBindings();
var flags = getVirtualBindingFlags( event.target ),
t;
triggerVirtualEvent( "vmouseup", event, flags );
if ( !didScroll ) {
var ve = triggerVirtualEvent( "vclick", event, flags );
if ( ve && ve.isDefaultPrevented() ) {
// The target of the mouse events that follow the touchend
// event don't necessarily match the target used during the
// touch. This means we need to rely on coordinates for blocking
// any click that is generated.
t = getNativeEvent( event ).changedTouches[ 0 ];
clickBlockList.push({
touchID: lastTouchID,
x: t.clientX,
y: t.clientY
});
// Prevent any mouse events that follow from triggering
// virtual event notifications.
blockMouseTriggers = true;
}
}
triggerVirtualEvent( "vmouseout", event, flags);
didScroll = false;
startResetTimer();
}
function hasVirtualBindings( ele ) {
var bindings = .data( this, dataPropertyName, {});
}
// If setup is called, we know it is the first binding for this
// eventType, so initialize the count for the eventType to zero.
var bindings = document.bind( realType, mouseEventCallback );
}
// Some browsers, like Opera Mini, won't dispatch mouse/click events
// for elements unless they actually have handlers registered on them.
// To get around this, we register dummy handlers on the elements.
$( this ).bind( realType, dummyMouseHandler );
// For now, if event capture is not supported, we rely on mouse handlers.
if ( eventCaptureSupported ) {
// If this is the first virtual mouse binding for the document,
// register our touchstart handler on the document.
activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
if (activeDocHandlers[ "touchstart" ] === 1) {
document.unbind( realType, mouseEventCallback );
}
if ( eventCaptureSupported ) {
// If this is the last virtual mouse binding in existence,
// remove our document touchstart listener.
--activeDocHandlers[ "touchstart" ];
if ( !activeDocHandlers[ "touchstart" ] ) {
this = $( this ),
bindings = this.unbind( realType, dummyMouseHandler );
// If this is the last virtual mouse binding on the
// element, remove the binding data from the element.
if ( !hasVirtualBindings( this ) ) {
.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
}
// Add a capture click handler to block clicks.
// Note that we require event capture support for this so if the device
// doesn't support it, we punt for now and rely solely on mouse events.
if ( eventCaptureSupported ) {
document.addEventListener( "click", function( e ){
var cnt = clickBlockList.length,
target = e.target,
x, y, ele, i, o, touchID;
if ( cnt ) {
x = e.clientX;
y = e.clientY;
threshold = .data( ele, touchTargetPropertyName ) === o.touchID ) {
// XXX: We may want to consider removing matches from the block list
// instead of waiting for the reset timer to fire.
e.preventDefault();
e.stopPropagation();
return;
}
}
ele = ele.parentNode;
}
}
}, true);
}
})( jQuery, window, document );
/*
* "events" plugin - Handles events
*/
(function( .each( ( "touchstart touchmove touchend orientationchange throttledresize " +
"tap taphold swipe swipeleft swiperight scrollstart scrollstop" ).split( " " ), function( i, name ) {
.attrFn[ name ] = true;
});
var supportTouch = .event.handle.call( obj, event );
event.type = originalType;
}
// also handles scrollstop
this = $( thisObject ),
scrolling,
timer;
function trigger( event, state ) {
scrolling = state;
triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
}
// iPhone triggers scroll after a small delay; use touchmove instead
.event.special.scrollstart.enabled ) {
return;
}
if ( !scrolling ) {
trigger( event, true );
}
clearTimeout( timer );
timer = setTimeout(function() {
trigger( event, false );
}, 50 );
});
}
};
// also handles taphold
this = $( thisObject );
this.unbind( "vclick", clickHandler )
.unbind( "vmouseup", clearTapTimer )
.unbind( "vmousecancel", clearTapHandlers );
}
function clickHandler(event) {
clearTapHandlers();
// ONLY trigger a 'tap' event if the start target is
// the same as the stop target.
if ( origTarget == event.target ) {
triggerCustomEvent( thisObject, "tap", event );
}
}
.Event( "taphold" ) );
}, 750 );
});
}
};
// also handles swipeleft, swiperight
this = $( thisObject );
.event.special.swipe.scrollSupressionThreshold ) {
event.preventDefault();
}
}
this.unbind( touchMoveEvent, moveHandler );
if ( start && stop ) {
if ( stop.time - start.time < .event.special.swipe.horizontalDistanceThreshold &&
Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < , window ) {
// "Cowboy" Ben Alman
var win = $( window ),
special_event,
get_orientation,
last_orientation;
.support.orientation && .support.orientation && .event.special.orientationchange.orientation = get_orientation = function() {
var isPortrait = true, elem = document.documentElement;
// prefer window orientation to the calculation based on screensize as
// the actual screen resize takes place before or after the orientation change event
// has been fired depending on implementation (eg android 2.3 is before, iphone after).
// More testing is required to determine if a more reliable method of determining the new screensize
// is possible when orientationchange is fired. (eg, use media queries + element + opacity)
if ( .event.special.throttledresize = {
setup: function() {
$( this ).bind( "resize", handler );
},
teardown: function(){
$( this ).unbind( "resize", handler );
}
};
var throttle = 250,
handler = function() {
curr = ( new Date() ).getTime();
diff = curr - lastCall;
if ( diff >= throttle ) {
lastCall = curr;
$( this ).trigger( "throttledresize" );
} else {
if ( heldCall ) {
clearTimeout( heldCall );
}
// Promise a held call will still execute
heldCall = setTimeout( handler, throttle - diff );
}
},
lastCall = 0,
heldCall,
curr,
diff;
})();
.event.special[ event ] = {
setup: function() {
$( this ).bind( sourceEvent, ,window,undefined){
'.event.special,
// Does the browser support window.onhashchange? Note that IE8 running in
// IE7 compatibility mode reports true for 'onhashchange' in window, even
// though the event isn't supported, so also test document.documentMode.
doc_mode = doc.documentMode,
supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
// Get location.hash (or what you'd expect location.hash to be) sans any
// leading #. Thanks for making this necessary, Firefox!
function get_fragment( url ) {
url = url || location.href;
return '#' + url.replace( /^[^#]*#?(.*).fn[ str_hashchange ] = function( fn ) {
return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
};
// Property: jQuery.fn.hashchange.delay
//
// The numeric interval (in milliseconds) at which the <hashchange event>
// polling loop executes. Defaults to 50.
// Property: jQuery.fn.hashchange.domain
//
// If you're setting document.domain in your JavaScript, and you want hash
// history to work in IE6/7, not only must this property be set, but you must
// also set document.domain BEFORE jQuery is loaded into the page. This
// property is only applicable if you are supporting IE6/7 (or IE8 operating
// in "IE7 compatibility" mode).
//
// In addition, the <jQuery.fn.hashchange.src> property must be set to the
// path of the included "document-domain.html" file, which can be renamed or
// modified if necessary (note that the document.domain specified must be the
// same in both your main JavaScript as well as in this file).
//
// Usage:
//
// jQuery.fn.hashchange.domain = document.domain;
// Property: jQuery.fn.hashchange.src
//
// If, for some reason, you need to specify an Iframe src file (for example,
// when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
// do so using this property. Note that when using this property, history
// won't be recorded in IE6/7 until the Iframe src file loads. This property
// is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
// compatibility" mode).
//
// Usage:
//
// jQuery.fn.hashchange.src = 'path/to/file.html';
.fn[ str_hashchange ].domain = null;
.event.special.hashchange methods (allowing this plugin
// to be defined after jQuery BBQ in BBQ's source code).
special[ str_hashchange ] = .fn.hashchange.delay milliseconds to see
// if location.hash has changed, and triggers the 'hashchange' event on
// window when necessary.
function poll() {
var hash = get_fragment(),
history_hash = history_get( last_hash );
if ( hash !== last_hash ) {
history_set( last_hash = hash, history_hash );
window.trigger( str_hashchange );
} else if ( history_hash !== last_hash ) {
location.href = location.href.replace( /#.*/, '' ) + history_hash;
}
timeout_id = setTimeout( poll, .browser.msie && !supports_onhashchange && (function(){
// Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
// when running in "IE7 compatibility" mode.
var iframe,
iframe_src;
// When the event is bound and polling starts in IE 6/7, create a hidden
// Iframe for history handling.
self.start = function(){
if ( !iframe ) {
iframe_src = .fn[ str_hashchange ].domain;
if ( hash !== history_hash ) {
// Update Iframe with any initial `document.title` that might be set.
iframe_doc.title = doc.title;
// Opening the Iframe's document after it has been closed is what
// actually adds a history entry.
iframe_doc.open();
// Set document.domain for the Iframe document as well, if necessary.
domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
iframe_doc.close();
// Update the Iframe's hash, for great justice.
iframe.location.hash = hash;
}
};
})();
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return self;
})();
})(jQuery,this);
/*
* "page" plugin
*/
(function( .widget( "mobile.page", .trim(options.keepNative);
if( keepNativeDefined && options.keepNative !== options.keepNativeDefault ){
return [options.keepNative, options.keepNativeDefault].join(", ");
}
return options.keepNativeDefault;
}
});
})( jQuery );
/*
* "core" - The base file for jQm
*/
(function( .extend( .support.mediaquery || .mobile.browser.ie >= 7;
},
// TODO might be useful upstream in jquery itself ?
keyCode: {
ALT: 18,
BACKSPACE: 8,
CAPS_LOCK: 20,
COMMA: 188,
COMMAND: 91,
COMMAND_LEFT: 91, // COMMAND
COMMAND_RIGHT: 93,
CONTROL: 17,
DELETE: 46,
DOWN: 40,
END: 35,
ENTER: 13,
ESCAPE: 27,
HOME: 36,
INSERT: 45,
LEFT: 37,
MENU: 93, // COMMAND_RIGHT
NUMPAD_ADD: 107,
NUMPAD_DECIMAL: 110,
NUMPAD_DIVIDE: 111,
NUMPAD_ENTER: 108,
NUMPAD_MULTIPLY: 106,
NUMPAD_SUBTRACT: 109,
PAGE_DOWN: 34,
PAGE_UP: 33,
PERIOD: 190,
RIGHT: 39,
SHIFT: 16,
SPACE: 32,
TAB: 9,
UP: 38,
WINDOWS: 91 // COMMAND
},
// Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
silentScroll: function( ypos ) {
if ( .mobile.defaultHomeScroll;
}
// prevent scrollstart and scrollstop events
.event.special.scrollstart.enabled = true;
}, 150 );
},
// Expose our cache for testing purposes.
nsNormalizeDict: nsNormalizeDict,
// Take a data attribute property, prepend the namespace
// and then camel case the attribute string. Add the result
// to our nsNormalizeDict so we don't have to do this again.
nsNormalize: function( prop ) {
if ( !prop ) {
return;
}
return nsNormalizeDict[ prop ] || ( nsNormalizeDict[ prop ] = .mobile.ns + prop ) );
},
getInheritedTheme: function( el, defaultTheme ) {
// Find the closest parent with a theme class on it. Note that
// we are not using .fn.jqmData = function( prop, value ) {
var result;
if ( typeof prop != "undefined" ) {
result = this.data( prop ? .jqmData = function( elem, prop, value ) {
var result;
if ( typeof prop != "undefined" ) {
result = .mobile.nsNormalize( prop ) : prop, value );
}
return result;
};
.mobile.nsNormalize( prop ) );
};
.removeData( elem, .fn.removeWithDependents = function() {
.removeWithDependents = function( elem ) {
var elem.jqmData('dependents') || $() ).remove();
.fn.addDependents = function( newDependents ) {
.addDependents = function( elem, newDependents ) {
var dependents = elem.jqmData( 'dependents' ) || $();
elem.jqmData( 'dependents', .fn.getEncodedText = function() {
return $( "<div/>" ).text( this.text() ).html();
};
// Monkey-patching Sizzle to filter the :jqmData selector
var oldFind = .find = function( selector, context, ret, extra ) {
selector = selector.replace( jqmDataRE, "[data-" + ( .extend( .find.matches = function( expr, set ) {
return .find.matchesSelector = function( node, expr ) {
return , undefined ) {
//define vars for interal use
var html = $( 'html' ),
.type( url ) === "object" ) {
return url;
}
var matches = path.urlParseRE.exec( url || "" ) || [];
// Create an object that allows the caller to access the sub-matches
// by name. Note that IE returns an empty string instead of undefined,
// like all other browsers do, so we normalize everything so its consistent
// no matter what browser we're running on.
return {
href: matches[ 0 ] || "",
hrefNoHash: matches[ 1 ] || "",
hrefNoSearch: matches[ 2 ] || "",
domain: matches[ 3 ] || "",
protocol: matches[ 4 ] || "",
doubleSlash: matches[ 5 ] || "",
authority: matches[ 6 ] || "",
username: matches[ 8 ] || "",
password: matches[ 9 ] || "",
host: matches[ 10 ] || "",
hostname: matches[ 11 ] || "",
port: matches[ 12 ] || "",
pathname: matches[ 13 ] || "",
directory: matches[ 14 ] || "",
filename: matches[ 15 ] || "",
search: matches[ 16 ] || "",
hash: matches[ 17 ] || ""
};
},
//Turn relPath into an asbolute path. absPath is
//an optional absolute path which describes what
//relPath is relative to.
makePathAbsolute: function( relPath, absPath ) {
if ( relPath && relPath.charAt( 0 ) === "/" ) {
return relPath;
}
relPath = relPath || "";
absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+).param( params ) : params,
s = u.search || "?";
return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
},
convertUrlToDataUrl: function( absUrl ) {
var u = path.parseUrl( absUrl );
if ( path.isEmbeddedPage( u ) ) {
// For embedded pages, remove the dialog hash key as in getFilePath(),
// otherwise the Data Url won't match the id of the embedded Page.
return u.hash.split( dialogHashKey )[0].replace( /^#/, "" );
} else if ( path.isSameDomain( u, documentBase ) ) {
return u.hrefNoHash.replace( documentBase.domain, "" );
}
return absUrl;
},
//get path from current hash, or from a file path
get: function( newPath ) {
if( newPath === undefined ) {
newPath = location.hash;
}
return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+.mobile.subPageUrlKey;
return path && path.split( splitkey )[0].split( dialogHashKey )[0];
},
//set location hash to path
set: function( path ) {
location.hash = path;
},
//test if a given url (string) is a path
//NOTE might be exceptionally naive
isPath: function( url ) {
return ( /\// ).test( url );
},
//return a url path with the window's location protocol/hostname/pathname removed
clean: function( url ) {
return url.replace( documentBase.domain, "" );
},
//just return the url without an initial #
stripHash: function( url ) {
return url.replace( /^#/, "" );
},
//remove the preceding hash, any query params, and dialog notations
cleanHash: function( hash ) {
return path.stripHash( hash.replace( /\?.*.mobile.firstPage,
// Get the id of the first page element if it has one.
fpId = fp && fp[0] ? fp[0].id : undefined;
// The url refers to the first page if the path matches the document and
// it either has no hash value, or the hash is exactly equal to the id of the
// first page element.
return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
},
isEmbeddedPage: function( url ) {
var u = path.parseUrl( url );
//if the path is absolute, then we need to compare the url against
//both the documentUrl and the documentBase. The main reason for this
//is that links embedded within external documents will refer to the
//application document, whereas links embedded within the application
//document will be resolved against the document base.
if ( u.protocol !== "" ) {
return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) );
}
return (/^#/).test( u.href );
}
},
//will be defined when a link is clicked and given an active class
.each( urlHistory.stack, function( i, historyEntry ) {
//if the url is in the stack, it's a forward or a back
if( opts.currentUrl === historyEntry.url ) {
//define back and forward by whether url is older or newer than current page
back = i < urlHistory.activeIndex;
forward = !back;
newActiveIndex = i;
}
});
// save new page index, null check to prevent falsey 0 result
this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
if( back ) {
( opts.either || opts.isBack )( true );
} else if( forward ) {
( opts.either || opts.isForward )( false );
}
},
//disable hashchange event listener internally to ignore one change
//toggled internally when location.hash is updated to match the url of a successful page load
ignoreNextHashChange: false
},
//define first selector to receive focus when a page is shown
focusable = "[tabindex],a,button:visible,select:visible,input",
//queue to hold simultanious page transitions
pageTransitionQueue = [],
//indicates whether or not page is in process of transitioning
isPageTransitioning = false,
//nonsense hash change key for dialogs, so they create a history entry
dialogHashKey = "&ui-state=dialog",
//existing base tag?
head.children( "base" ),
//tuck away the original document URL minus any fragment.
documentUrl = path.parseUrl( location.href ),
//if the document has an embedded base tag, documentBase is set to its
//initial value. If a base tag does not exist, then we default to the documentUrl.
documentBase = base.attr( "href" ), documentUrl.href ) ) : documentUrl,
//cache the comparison once.
documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash );
//base element management, defined depending on dynamic base tag support
var base = base.length ? head ) ),
//set the generated BASE element's href attribute to a new page's base path
set: function( href ) {
base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) );
},
//set the generated BASE element's href attribute to a new page's base path
reset: function() {
base.element.attr( "href", documentBase.hrefNoHash );
}
} : undefined;
/*
internal utility functions
--------------------------------------*/
//direct focus to the page title, or otherwise first focusable element
function reFocus( page ) {
var pageTitle = page.find( ".ui-title:eq(0)" );
if( pageTitle.length ) {
pageTitle.focus();
}
else{
page.focus();
}
}
//remove active classes after page transition or error
function removeActiveLinkClass( forceRemoval ) {
if( !!activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval ) ) {
.mobile.activeBtnClass );
}
.mobile.changePage.apply( null, pageTransitionQueue.pop() );
}
}
// Save the last scroll distance per page, before it is hidden
var setLastScrollEnabled = true,
firstScrollElem, getScrollElem, setLastScroll, delayedSetLastScroll;
getScrollElem = function() {
var scrollElem = .support.touchOverflow && .mobile.urlHistory.getActive();
if( active ) {
var lastScroll = scrollElem && scrollElem.scrollTop();
// Set active page's lastScroll prop.
// If the location we're scrolling to is less than minScrollBack, let it go.
active.lastScroll = lastScroll < .mobile.defaultHomeScroll : lastScroll;
}
};
// bind to scrollstop to gather scroll position. The delay allows for the hashchange
// event to fire and disable scroll recording in the case where the browser scrolls
// to the hash targets location (sometimes the top of the page). once pagechange fires
// getLastScroll is again permitted to operate
delayedSetLastScroll = function() {
setTimeout( setLastScroll, 100, this );
};
// disable an scroll setting when a hashchange has been fired, this only works
// because the recording of the scroll position is delayed for 100ms after
// the browser might have changed the position because of the hashchange
.support.pushState ? "popstate" : "hashchange", function() {
setLastScrollEnabled = false;
});
// handle initial hashchange from chrome :(
.support.pushState ? "popstate" : "hashchange", function() {
setLastScrollEnabled = true;
});
// wait until the mobile page container has been determined to bind to pagechange
.mobile.pageContainer.bind( "pagechange", function() {
var scrollElem = getScrollElem();
setLastScrollEnabled = true;
// remove any binding that previously existed on the get scroll
// which may or may not be different than the scroll element determined for
// this page previously
scrollElem.unbind( "scrollstop", delayedSetLastScroll );
// determine and bind to the current scoll element which may be the window
// or in the case of touch overflow the element with touch overflow
scrollElem.bind( "scrollstop", delayedSetLastScroll );
});
});
// bind to scrollstop for the first page as "pagechange" won't be fired in that case
getScrollElem().bind( "scrollstop", delayedSetLastScroll );
// Make the iOS clock quick-scroll work again if we're using native overflow scrolling
/*
if( .mobile.touchOverflowEnabled ){
$( window ).bind( "scrollstop", function(){
if( $( this ).scrollTop() === 0 ){
//function for transitioning between two existing pages
function transitionPages( toPage, fromPage, transition, reverse ) {
//get current scroll distance
var active = .mobile.urlHistory.getActive(),
touchOverflow = .mobile.touchOverflowEnabled,
toScroll = active.lastScroll || ( touchOverflow ? 0 : .mobile.defaultHomeScroll );
if( fromPage ) {
//trigger before show/hide events
fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } );
}
if( !touchOverflow){
toPage.height( screenHeight + toScroll );
}
toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } );
//clear page loader
.mobile.transitionHandlers[transition || "none"] || .mobile.silentScroll( toScroll );
}
//trigger show/hide events
if( fromPage ) {
if( !touchOverflow ){
fromPage.height( "" );
}
fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } );
}
//trigger pageshow, define prevPage as either fromPage or empty jQuery obj
toPage.data( "page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } );
});
return promise;
}
//simply set the active page's minimum height to screen height, depending on orientation
function getScreenHeight(){
var orientation = .mobile.getScreenHeight = getScreenHeight;
//simply set the active page's minimum height to screen height, depending on orientation
function resetActivePageHeight(){
// Don't apply this height in touch overflow enabled mode
if( .mobile.touchOverflowEnabled ){
return;
}
$( "." + page, role ) {
// If a role was specified, make sure the data-role attribute
// on the page element is in sync.
if( role ) {
.mobile.ns + "role", role );
}
//run page plugin
.mobile methods */
//animation complete callback
.support.cssTransitions ) {
return $( this ).one( 'webkitAnimationEnd', callback );
}
else{
// defer execution for consistency between webkit/non webkit
setTimeout( callback, 0 );
return $( this );
}
};
//expose path object on .mobile.path = path;
//expose base object on .mobile.base = base;
//history stack
.mobile.dialogHashKey = dialogHashKey;
//default non-animation transition handler
toPage, fromPage ) {
.mobile.activePageClass );
}
.mobile.activePageClass );
return toPage, .mobile.defaultTransitionHandler = .mobile.transitionHandlers = {
none: .mobile.allowCrossDomainPages = false;
//return the original document url
.extend( {}, documentUrl ) : documentUrl.href;
};
//return the original document base url
.extend( {}, documentBase ) : documentBase.href;
};
this = $( this ),
prEvent = new this.trigger( prEvent );
if( !prEvent.isDefaultPrevented() ){
.mobile.loadPage = function( url, options ) {
// This function uses deferred notifications to let callers
// know when the page is done loading, or if an error has occurred.
var deferred = .extend( {}, .mobile.activePage && getClosestBaseUrl( .mobile.pageContainer;
// Check to see if the page already exists in the DOM.
page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
// injected by a developer, in which case it would be lacking a data-url
// attribute and in need of enhancement.
if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) {
page = settings.pageContainer.children( "#" + dataUrl )
.attr( "data-" + .mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) {
// Check to make sure our cached-first-page is actually
// in the DOM. Some user deployed apps are pruning the first
// page from the DOM for various reasons, we check for this
// case here because we don't want a first-page with an id
// falling through to the non-existent embedded page error
// case. If the first-page is not in the DOM, then we let
// things fall through to the ajax loading code below so
// that it gets reloaded.
if ( .mobile.firstPage );
}
} else if ( path.isEmbeddedPage( fileUrl ) ) {
deferred.reject( absUrl, options );
return deferred.promise();
}
}
// Reset base to the default document base.
if ( base ) {
base.reset();
}
// If the page we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Otherwise, track the
// existing page as a duplicated.
if ( page.length ) {
if ( !settings.reloadPage ) {
enhancePage( page, settings.role );
deferred.resolve( absUrl, options, page );
return deferred.promise();
}
dupCachedPage = page;
}
var mpc = settings.pageContainer,
pblEvent = new .mobile.showPageLoadingMsg();
}, settings.loadMsgDelay ),
// Shared logic for clearing timeout and removing message.
hideMsg = function(){
// Stop message show timer
clearTimeout( loadMsgDelay );
// Hide loading message
.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) {
deferred.reject( absUrl, options );
} else {
// Load the new page.
.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
dataUrlRegex = new RegExp( "\\bdata-" + .mobile.ns + "role='page'>" + html.split( /<\/?body[^>]*>/gmi )[1] + "</div>" );
}
if ( newPageTitle && !page.jqmData( "title" ) ) {
if ( ~newPageTitle.indexOf( "&" ) ) {
newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
}
page.jqmData( "title", newPageTitle );
}
//rewrite src and href attrs to use a base url
if( !.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
.attr( "data-" + .mobile._bindPageRemove );
enhancePage( page, settings.role );
// Enhancing the page may result in new dialogs/sub pages being inserted
// into the DOM. If the original absUrl refers to a sub-page, that is the
// real page we are interested in.
if ( absUrl.indexOf( "&" + .Event( "pageloadfailed" );
// Let listeners know the page load failed.
settings.pageContainer.trigger( plfEvent, triggerData );
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if( plfEvent.isDefaultPrevented() ){
return;
}
// Remove loading message.
if ( settings.showLoadMsg ) {
// Remove loading message.
hideMsg();
//show error message
$( "<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>"+ window.scrollTop() + 100 })
.appendTo( settings.pageContainer )
.delay( 800 )
.fadeOut( 400, function() {
$( this ).remove();
});
}
deferred.reject( absUrl, options );
}
});
}
return deferred.promise();
};
.mobile.changePage = function( toPage, options ) {
// If we are in the midst of a transition, queue the current request.
// We'll call changePage() once we're done with the current transition to
// service the request.
if( isPageTransitioning ) {
pageTransitionQueue.unshift( arguments );
return;
}
var settings = .mobile.changePage.defaults, options );
// Make sure we have a pageContainer to work with.
settings.pageContainer = settings.pageContainer || .mobile.activePage;
var mpc = settings.pageContainer,
pbcEvent = new .mobile.loadPage( toPage, settings )
.done(function( url, options, newPage, dupCachedPage ) {
isPageTransitioning = false;
options.duplicateCachedPage = dupCachedPage;
.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
settings.dataUrl = documentUrl.hrefNoHash;
}
// The caller passed us a real page DOM element. Update our
// internal state and then trigger a transition to the page.
var fromPage = settings.fromPage,
url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ),
// The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path
pageUrl = url,
fileUrl = path.getFilePath( url ),
active = urlHistory.getActive(),
activeIsInitialPage = urlHistory.activeIndex === 0,
historyDir = 0,
pageTitle = document.title,
isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog";
// By default, we prevent changePage requests when the fromPage and toPage
// are the same element, but folks that generate content manually/dynamically
// and reuse pages want to be able to transition to the same page. To allow
// this, they will need to change the default value of allowSamePageTransition
// to true, *OR*, pass it in as an option when they manually call changePage().
// It should be noted that our default transition animations assume that the
// formPage and toPage are different elements, so they may behave unexpectedly.
// It is up to the developer that turns on the allowSamePageTransitiona option
// to either turn off transition animations, or make sure that an appropriate
// animation transition is used.
if( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) {
isPageTransitioning = false;
mpc.trigger( "pagechange", triggerData );
return;
}
// We need to make sure the page we are given has already been enhanced.
enhancePage( toPage, settings.role );
// If the changePage request was sent from a hashChange event, check to see if the
// page is already within the urlHistory stack. If so, we'll assume the user hit
// the forward/back button and will try to match the transition accordingly.
if( settings.fromHashChange ) {
urlHistory.directHashChange({
currentUrl: url,
isBack: function() { historyDir = -1; },
isForward: function() { historyDir = 1; }
});
}
// Kill the keyboard.
// XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
// we should be tracking focus with a live() handler so we already have
// the element in hand at this point.
// Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
// is undefined when we are in an IFrame.
try {
if(document.activeElement && document.activeElement.nodeName.toLowerCase() != 'body') {
$(document.activeElement).blur();
} else {
$( "input:focus, textarea:focus, select:focus" ).blur();
}
} catch(e) {}
// If we're displaying the page as a dialog, we don't want the url
// for the dialog content to be used in the hash. Instead, we want
// to append the dialogHashKey to the url of the current page.
if ( isDialog && active ) {
// on the initial page load active.url is undefined and in that case should
// be an empty string. Moving the undefined -> empty string back into
// urlHistory.addNew seemed imprudent given undefined better represents
// the url state
url = ( active.url || "" ) + dialogHashKey;
}
// Set the location hash.
if( settings.changeHash !== false && url ) {
//disable hash listening temporarily
urlHistory.ignoreNextHashChange = true;
//update hash and history
path.set( url );
}
// if title element wasn't found, try the page div data attr too
// If this is a deep-link or a reload ( active === undefined ) then just use pageTitle
var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children(":jqmData(role='header')").find(".ui-title" ).getEncodedText();
if( !!newPageTitle && pageTitle == document.title ) {
pageTitle = newPageTitle;
}
if ( !toPage.jqmData( "title" ) ) {
toPage.jqmData( "title", pageTitle );
}
// Make sure we have a transition defined.
settings.transition = settings.transition
|| ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined )
|| ( isDialog ? .mobile.defaultPageTransition );
//add page to history stack if it's not back or forward
if( !historyDir ) {
urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role );
}
//set page title
document.title = urlHistory.getActive().title;
//set "toPage" as activePage
html.removeClass( "ui-mobile-rendering" );
releasePageTransitionLock();
// Let listeners know we're all done changing the current page.
mpc.trigger( "pagechange", triggerData );
});
};
.mobile._registerInternalEvents = function(){
//bind to form submit events, handle with Ajax
$( "form" ).live('submit', function( event ) {
var .mobile.ajaxEnabled ||
this.attr( "method" ),
target = this.attr( "action" );
// If no action is specified, browsers default to using the
// URL of the document containing the form. Since we dynamically
// pull in pages from external documents, the form should submit
// to the URL for the source document of the page containing
// the form.
if ( !url ) {
// Get the @data-url for the page containing the form.
url = getClosestBaseUrl( this) );
//external submits use regular HTTP
if( path.isExternal( url ) || target ) {
return;
}
this.serialize(),
transition: this.jqmData( "direction" ),
reloadPage: true
}
);
event.preventDefault();
});
//add active state on vclick
$( document ).bind( "vclick", function( event ) {
// if this isn't a left click we don't care. Its important to note
// that when the virtual event is generated it will create
if ( event.which > 1 || !activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" );
.mobile.activeBtnClass );
$( "." + .mobile.linkBindingEnabled ){
return;
}
var link = findClosestLink( event.target );
// If there is no link associated with the click or its not a left
// click we want to ignore the click
if ( !link || event.which > 1) {
return;
}
var link.is( ":jqmData(rel='back')" ) ) {
window.history.back();
return false;
}
var baseUrl = getClosestBaseUrl( link.attr( "href" ) || "#", baseUrl );
//if ajax is disabled, exit early
if( !link.is( "[rel='external']" ) || link.is( "[target]" ),
// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
// requests if the document doing the request was loaded via the file:// protocol.
// This is usually to allow the application to "phone home" and fetch app specific
// data. We normally let the browser handle external/cross-domain urls, but if the
// allowCrossDomainPages option is true, we will allow cross-domain http/https
// requests to go through our page loading logic.
isCrossDomainPageLoad = ( link.jqmData( "transition" ),
direction = link.jqmData( "back" ),
//this may need to be more specific as we use data-rel more
role = .mobile.ns + "rel" ) || undefined;
link = this,
url = .inArray( url, urls ) === -1 ) {
urls.push( url );
link.attr("data-" + .mobile._handleHashChange = function( hash ) {
//find first page via hash
var to = path.stripHash( hash ),
//transition is false if it's the first page, undefined otherwise (and may be overridden by default)
transition = .mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) {
urlHistory.ignoreNextHashChange = false;
return;
}
// special case for dialogs
if( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 ) {
// If current active page is not a dialog skip the dialog and continue
// in the same direction
if(!.mobile.urlHistory.getActive();
to = active.pageUrl;
// make sure to set the role, transition and reversal
// as most of this is lost by the domCache cleaning
.mobile.changePage( to, changePageOptions );
} else {
//there's no hash, go to the first page in the dom
.mobile.firstPage, changePageOptions );
}
};
//hashchange event handler
.mobile._handleHashChange( location.hash );
});
//set page min-heights to be device specific
$( document ).bind( "pageshow", resetActivePageHeight );
$( window ).bind( "throttledresize", resetActivePageHeight );
};//_registerInternalEvents callback
})( jQuery );
/*
* history.pushState support, layered on top of hashchange
*/
( function( .mobile._registerInternalEvents
// Scope self to pushStateHandler so we can reference it sanely within the
// methods handed off as event handlers
var pushStateHandler = {},
self = pushStateHandler,
.mobile.path.parseUrl( location.href );
.mobile.dialogHashKey,
subkey = "&" + .mobile.urlHistory.ignoreNextHashChange = value;
self.onHashChangeDisabled = value;
},
// on hash change we want to clean up the url
// NOTE this takes place *after* the vanilla navigation hash change
// handling has taken place and set the state of the DOM
onHashChange: function( e ) {
// disable this hash change
if( self.onHashChangeDisabled ){
return;
}
var href, state,
hash = location.hash,
isPath = .mobile.getDocumentUrl();
hash = isPath ? hash.replace( "#", "" ) : hash;
// propulate the hash when its not available
state = self.state();
// make the hash abolute with the current href
href = .mobile._handleHashChange( poppedState.hash );
}, 100);
}
},
init: function() {
win.bind( "popstate", self.onPopState );
// if there's no hash, we need to replacestate for returning to home
if ( location.hash === "" ) {
history.replaceState( self.state(), document.title, location.href );
}
}
});
$( function() {
if( .support.pushState ){
pushStateHandler.init();
}
});
})( jQuery, this );
/*
* "transitions" plugin - Page change tranistions
*/
(function( to, .Deferred(),
reverseClass = reverse ? " reverse" : "",
viewportClass = "ui-mobile-viewport-transitioning viewport-" + name,
doneFunc = function() {
from ).removeClass( "out in reverse " + name );
if ( from[ 0 ] !== from.removeClass( to.parent().removeClass( viewportClass );
deferred.resolve( name, reverse, from );
};
to.parent().addClass( viewportClass );
if ( from.addClass( name + " out" + reverseClass );
}
.mobile.activePageClass + " " + name + " in" + reverseClass );
return deferred.promise();
}
// Make our transition handler public.
.mobile.defaultTransitionHandler === .mobile.defaultTransitionHandler = css3TransitionHandler;
}
})( jQuery, this );
/*
* "degradeInputs" plugin - degrades inputs to another type after custom enhancements are made.
*/
(function( .mobile.page.prototype.options.degradeInputs = {
color: false,
date: false,
datetime: false,
"datetime-local": false,
email: false,
month: false,
number: false,
range: "number",
search: "text",
tel: false,
time: false,
url: false,
week: false
};
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
var page = $(e.target).closest(':jqmData(role="page")').data("page"), options;
if( !page ) {
return;
}
options = page.options;
// degrade inputs to avoid poorly implemented native functionality
$( e.target ).find( "input" ).not( page.keepNativeSelector() ).each(function() {
var this.clone() ).html(),
// In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead
hasType = html.indexOf( " type=" ) > -1,
findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/,
repstr = " type=\"" + optType + "\" data-" + this.replaceWith( html.replace( findstr, repstr ) );
}
});
});
})( jQuery );/*
* "dialog" plugin.
*/
(function( .widget( "mobile.dialog", el = this.element,
headerCloseButton = $( "<a href='#' data-" + .mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText + "</a>" );
el.attr( "role", "dialog" )
.addClass( "ui-dialog" )
.find( ":jqmData(role='header')" )
.addClass( "ui-corner-top ui-overlay-shadow" )
.prepend( headerCloseButton )
.end()
.find( ":jqmData(role='content'),:jqmData(role='footer')" )
.addClass( "ui-overlay-shadow" )
.last()
.addClass( "ui-corner-bottom" );
// this must be an anonymous function so that select menu dialogs can replace
// the close method. This is a change from previously just defining data-rel=back
// on the button and letting nav handle it
headerCloseButton.bind( "vclick", function() {
self.close();
});
/* bind events
- clicks and submits should use the closing transition that the dialog opened with
unless a data-transition is specified on the link/form
- if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally
*/
target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" ),
active;
if ( target.jqmData( "transition" ) ) {
active = target.attr( "data-" + .mobile.defaultDialogTransition ) )
.attr( "data-" + .mobile.activeBtnClass ).removeClass( .mobile.dialog.prototype.options.initSelector ).live( "pagecreate", function(){
$( this ).dialog();
});
})( jQuery, this );
/*
* This plugin handles theming and layout of headers, footers, and content areas
*/
(function( .mobile.page.prototype.options.backBtnText = "Back";
.mobile.page.prototype.options.backBtnTheme = null;
.mobile.page.prototype.options.footerTheme = "a";
page = $( this ),
o = page.jqmData( "role" ),
pageTheme = o.theme;
$( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", this ).each(function() {
var this.jqmData( "role" ),
theme = headeranchors,
leftbtn,
rightbtn,
backBtn;
this
//add theme class
.addClass( "ui-bar-" + thisTheme )
// Add ARIA role
.attr( "role", role === "header" ? "banner" : "contentinfo" );
// Right,left buttons
this.children( "a" );
leftbtn = headeranchors.hasClass( "ui-btn-right" );
leftbtn = leftbtn || headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
// Auto-add back btn on pages beyond first view
if ( o.addBackBtn &&
role === "header" &&
$( ".ui-page" ).length > 1 &&
.mobile.path.stripHash( location.hash ) &&
!leftbtn ) {
backBtn = $( "<a href='#' class='ui-btn-left' data-"+ .mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" )
// If theme is provided, override default inheritance
.attr( "data-"+ this );
}
// Page title
this.addClass( "ui-body-" + ( contentTheme ) );
}
// Add ARIA role
, undefined ) {
.mobile.widget, {
options: {
expandCueText: " click to expand contents",
collapseCueText: " click to collapse contents",
collapsed: true,
heading: "h1,h2,h3,h4,h5,h6,legend",
theme: null,
contentTheme: null,
iconTheme: "d",
initSelector: ":jqmData(role='collapsible')"
},
_create: function() {
var el.addClass( "ui-collapsible" ),
collapsibleHeading = el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ),
collapsiblesInSet = collapsibleSet.children( ":jqmData(role='collapsible')" );
// Replace collapsibleHeading if it's a legend
if ( collapsibleHeading.is( "legend" ) ) {
collapsibleHeading = $( "<div role='heading'>"+ collapsibleHeading.html() +"</div>" ).insertBefore( collapsibleHeading );
collapsibleHeading.next().remove();
}
// If we are in a collapsible set
if ( collapsibleSet.length ) {
// Inherit the theme from collapsible-set
if ( !o.theme ) {
o.theme = collapsibleSet.jqmData( "theme" );
}
// Inherit the content-theme from collapsible-set
if ( !o.contentTheme ) {
o.contentTheme = collapsibleSet.jqmData( "content-theme" );
}
}
collapsibleContent.addClass( ( o.contentTheme ) ? ( "ui-body-" + o.contentTheme ) : "");
collapsibleHeading
//drop heading in before content
.insertBefore( collapsibleContent )
//modify markup & attributes
.addClass( "ui-collapsible-heading" )
.append( "<span class='ui-collapsible-heading-status'></span>" )
.wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
.find( "a" )
.first()
.buttonMarkup({
shadow: false,
corners: false,
iconPos: "left",
icon: "plus",
theme: o.theme
});
if ( !collapsibleSet.length ) {
collapsibleHeading
.find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) )
.addClass( "ui-corner-top ui-corner-bottom" );
} else {
// If we are in a collapsible set
// Initialize the collapsible set if it's not already initialized
if ( !collapsibleSet.jqmData( "collapsiblebound" ) ) {
collapsibleSet
.jqmData( "collapsiblebound", true )
.bind( "expand", function( event ) {
$( event.target )
.closest( ".ui-collapsible" )
.siblings( ".ui-collapsible" )
.trigger( "collapse" );
});
}
collapsiblesInSet.first()
.find( "a" )
.first()
.addClass( "ui-corner-top" )
.find( ".ui-btn-inner" )
.addClass( "ui-corner-top" );
collapsiblesInSet.last()
.jqmData( "collapsible-last", true )
.find( "a" )
.first()
.addClass( "ui-corner-bottom" )
.find( ".ui-btn-inner" )
.addClass( "ui-corner-bottom" );
if ( collapsible.jqmData( "collapsible-last" ) ) {
collapsibleHeading
.find( "a" ).first().add ( collapsibleHeading.find( ".ui-btn-inner" ) )
.addClass( "ui-corner-bottom" );
}
}
//events
collapsible
.bind( "expand collapse", function( event ) {
if ( !event.isDefaultPrevented() ) {
event.preventDefault();
var this.toggleClass( "ui-collapsible-collapsed", isCollapse );
collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse );
if ( contentTheme && ( !collapsibleSet.length || collapsible.jqmData( "collapsible-last" ) ) ) {
collapsibleHeading
.find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) )
.toggleClass( "ui-corner-bottom", isCollapse );
collapsibleContent.toggleClass( "ui-corner-bottom", !isCollapse );
}
collapsibleContent.trigger( "updatelayout" );
}
})
.trigger( o.collapsed ? "collapse" : "expand" );
collapsibleHeading
.bind( "click", function( event ) {
var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ?
"expand" : "collapse";
collapsible.trigger( type );
event.preventDefault();
});
}
});
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
$( , undefined ) {
, undefined ) {
this = $( this ),
o = kids = kids.length <= 5 ) {
for ( var letter in gridCols ) {
if ( gridCols[ letter ] === this.addClass( "ui-grid-" + grid );
kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" );
}
if ( iterator > 2 ) {
kids.filter( ":nth-child(4n+4)" ).addClass( "ui-block-d" );
}
if ( iterator > 4 ) {
, undefined ) {
.mobile.widget, {
options: {
iconpos: "top",
grid: null,
initSelector: ":jqmData(role='navbar')"
},
_create: function(){
var navbtns = navbtns.filter( ":jqmData(icon)" ).length ?
this.options.iconpos : undefined;
navbar.addClass( "ui-navbar-noicons" );
}
navbar.delegate( "a", "vclick", function( event ) {
.mobile.activeBtnClass );
$( this ).addClass( .mobile.navbar.prototype.options.initSelector, e.target ).navbar();
});
})( jQuery );
/*
* "listview" plugin
*/
(function( .widget( "mobile.listview", li,
topli,
li = this.element.children( "li" );
// at create time the li are not visible yet so we need to rely on .ui-screen-hidden
li.not( ".ui-screen-hidden" ):li );
// Select the first visible li element
visibleli.first()
.addClass( "ui-corner-top" );
topli.find( ".ui-btn-inner" )
.not( ".ui-li-link-alt span:first-child" ) )
.addClass( "ui-corner-top" )
.end()
.find( ".ui-li-link-alt, .ui-li-link-alt span:first-child" )
.addClass( "ui-corner-tr" )
.end()
.find( ".ui-li-thumb" )
.not(".ui-li-icon")
.addClass( "ui-corner-tl" );
// Select the last visible li element
visibleli.last()
.addClass( "ui-corner-bottom" );
bottomli.find( ".ui-btn-inner" ) )
.find( ".ui-li-link-alt" )
.addClass( "ui-corner-br" )
.end()
.find( ".ui-li-thumb" )
.not(".ui-li-icon")
.addClass( "ui-corner-bl" );
}
if ( !create ) {
this.element.trigger( "updatelayout" );
}
},
// This is a generic utility method for finding the first
// node with a given nodeName. It uses basic DOM traversal
// to be fast and is meant to be a substitute for simple
// .fn.children() calls on a single
// element. Note that callers must pass both the lowerCase
// and upperCase version of the nodeName they are looking for.
// The main reason for this is that this function will be
// called many times and we want to avoid having to lowercase
// the nodeName from the element every time to ensure we have
// a match. Note that this function lives here for now, but may
// be moved into list = this.element,
self = this,
dividertheme = list.jqmData( "splittheme" ),
listspliticon = list[ 0 ], "li", "LI" ),
counter = .nodeName( list.find( ".ui-li-dec" ).remove();
}
if ( !o.theme ) {
o.theme = list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" )
.end()
.find( "p, dl" ).addClass( "ui-li-desc" )
.end()
.find( ".ui-li-aside" ).each(function() {
var this.prependTo( list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" );
// The idea here is to look at the first image in the list item
// itself, and any .ui-link-inherit element it may contain, so we
// can place the appropriate classes on the image and list item.
// Note that we used to use something like:
//
// li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... );
//
// But executing a find() like that on Windows Phone 7.5 took a
// really long time. Walking things manually with the code below
// allows the 400 listview item page to load in about 3 seconds as
// opposed to 30 seconds.
this._addThumbClasses( li );
this._addThumbClasses( .expando ],
parentListId = parentList.attr( "id" ),
o = this.options,
dns = "data-" + .trim(parent.contents()[ 0 ].nodeValue) + "</span>" ),
title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
id = ( parentUrl || "" ) + "&" + .mobile.pageContainer );
newPage.page();
anchor = parent.find('a:first');
if ( !anchor.length ) {
anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
}
anchor.attr( "href", "#" + id );
}).listview();
// on pagehide, remove any nested pages along with the parent page, as long as they aren't active
// and aren't embedded
if( hasSubPages &&
parentPage.is( ":jqmData(external-page='true')" ) &&
parentPage.data("page").options.domCache === false ) {
var newRemove = function( e, ui ){
var nextPage = ui.nextPage, npURL;
if( ui.nextPage ){
npURL = nextPage.jqmData( "url" );
if( npURL.indexOf( parentUrl + "&" + .mobile.subPageUrlKey +"')");
}
});
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
$( , undefined ) {
.mobile.listview.prototype.options.filterPlaceholder = "Filter items...";
.mobile.listview.prototype.options.filterCallback = function( text, searchValue ){
return text.toLowerCase().indexOf( searchValue ) === -1;
};
$( ":jqmData(role='listview')" ).live( "listviewcreate", function() {
var list = $( this ),
listview = list.data( "listview" );
if ( !listview.options.filter ) {
return;
}
var wrapper = $( "<form>", {
"class": "ui-listview-filter ui-bar-" + listview.options.filterTheme,
"role": "search"
}),
search = $( "<input>", {
placeholder: listview.options.filterPlaceholder
})
.attr( "data-" + this = this,
val = this.value.toLowerCase(),
listItems = null,
lastval = this.jqmData( "lastval" , val );
change = val.substr( 0 , lastval.length - 1 ).replace( lastval , "" );
if ( val.length < lastval.length || change.length != ( val.length - lastval.length ) ) {
// Removed chars or pasted something totally different, check all items
listItems = list.children();
} else {
// Only chars added, not removed, only use visible subset
listItems = list.children( ":not(.ui-screen-hidden)" );
}
if ( val ) {
// This handles hiding regular rows without the text we search for
// and any list dividers without regular rows shown under it
for ( var i = listItems.length - 1; i >= 0; i-- ) {
item = $( listItems[ i ] );
itemtext = item.jqmData( "filtertext" ) || item.text();
if ( item.is( "li:jqmData(role=list-divider)" ) ) {
item.toggleClass( "ui-filter-hidequeue" , !childItems );
// New bucket!
childItems = false;
} else if ( listview.options.filterCallback( itemtext, val ) ) {
//mark to be hidden
item.toggleClass( "ui-filter-hidequeue" , true );
} else {
// There's a shown item in the bucket
childItems = true;
}
}
// Show items, not marked to be hidden
listItems
.filter( ":not(.ui-filter-hidequeue)" )
.toggleClass( "ui-screen-hidden", false );
// Hide items, marked to be hidden
listItems
.filter( ".ui-filter-hidequeue" )
.toggleClass( "ui-screen-hidden", true )
.toggleClass( "ui-filter-hidequeue", false );
} else {
//filtervalue is empty => show all
listItems.toggleClass( "ui-screen-hidden", false );
}
listview._refreshCorners();
})
.appendTo( wrapper )
.textinput();
if ( $( this ).jqmData( "inset" ) ) {
wrapper.addClass( "ui-listview-filter-inset" );
}
wrapper.bind( "submit", function() {
return false;
})
.insertBefore( list );
});
})( jQuery );/*
* "nojs" plugin - class to make elements hidden to A grade browsers
*/
(function( , undefined ) {
.mobile.widget, {
options: {
theme: null,
initSelector: "input[type='checkbox'],input[type='radio']"
},
_create: function() {
var self = this,
input = this.element,
// NOTE: Windows Phone could not find the label through a selector
// filter works though.
label = input.closest( "form,fieldset,:jqmData(role='page')" ).find( "label[for='" + input[ 0 ].id + "']"),
inputtype = input.attr( "type" ),
checkedState = inputtype + "-on",
uncheckedState = inputtype + "-off",
icon = input.parents( ":jqmData(type='horizontal')" ).length ? undefined : uncheckedState,
activeBtn = icon ? "" : " " + .extend( this, {
label: label,
inputtype: inputtype,
checkedClass: checkedClass,
uncheckedClass: uncheckedClass,
checkedicon: checkedicon,
uncheckedicon: uncheckedicon
});
// If there's no selected theme...
if( !this.options.theme ) {
this.options.theme = this.element.jqmData( "theme" );
}
label.buttonMarkup({
theme: this.options.theme,
icon: icon,
shadow: false
});
// Wrap the input + label in a div
input.add( label )
.wrapAll( "<div class='ui-" + inputtype + "'></div>" );
label.bind({
vmouseover: function( event ) {
if ( $( this ).parent().is( ".ui-disabled" ) ) {
event.stopPropagation();
}
},
vclick: function( event ) {
if ( input.is( ":disabled" ) ) {
event.preventDefault();
return;
}
self._cacheVals();
input.prop( "checked", inputtype === "radio" && true || !input.prop( "checked" ) );
// trigger click handler's bound directly to the input as a substitute for
// how label clicks behave normally in the browsers
// TODO: it would be nice to let the browser's handle the clicks and pass them
// through to the associate input. we can swallow that click at the parent
// wrapper element level
input.triggerHandler( 'click' );
// Input set for common radio buttons will contain all the radio
// buttons, but will not for checkboxes. clearing the checked status
// of other radios ensures the active button state is applied properly
self._getInputSet().not( input ).prop( "checked", false );
self._updateAll();
return false;
}
});
input
.bind({
vmousedown: function() {
self._cacheVals();
},
vclick: function() {
var this.is( ":checked" ) ) {
this).prop( "checked", false );
} else {
this = this;
this.is( ":checked" ) );
});
},
//returns either a set of radios with the same name attribute, or a single checkbox
_getInputSet: function(){
if(this.inputtype == "checkbox") {
return this.element;
}
return this.element.closest( "form,fieldset,:jqmData(role='page')" )
.find( "input[name='"+ this.element.attr( "name" ) +"'][type='"+ this.inputtype +"']" );
},
_updateAll: function() {
var self = this;
this._getInputSet().each(function() {
var this.is( ":checked" ) || self.inputtype === "checkbox" ) {
.mobile.checkboxradio.prototype.enhanceWithin( e.target );
});
})( jQuery );
/*
* "button" plugin - links that proxy to native input/buttons
*/
(function( .widget( "mobile.button", el = this.element,
o = this.options,
type,
name,
el.text() || el )
.buttonMarkup({
theme: o.theme,
icon: o.icon,
iconpos: o.iconpos,
inline: o.inline,
corners: o.corners,
shadow: o.shadow,
iconshadow: o.iconshadow
})
.append( el.attr( "type" );
name = el.bind( "vclick", function() {
// Add hidden input if it doesn’t already exist.
if( buttonPlaceholder = $( "<input>", {
type: "hidden",
name: el.attr( "value" )
}).insertBefore( buttonPlaceholder.remove();
// reset the local var so that the hidden input
// will be re-added on subsequent clicks
el = this.element;
if ( el.text() || .mobile.button.prototype.enhanceWithin( e.target );
});
})( jQuery );/*
* "slider" plugin
*/
( function( .widget( "mobile.slider", .mobile.getInheritedTheme( control, "c" ),
theme = this.options.theme || parentTheme,
trackTheme = this.options.trackTheme || parentTheme,
cType = control[ 0 ].nodeName.toLowerCase(),
selectClass = ( cType == "select" ) ? "ui-slider-switch" : "",
controlID = control.attr( "id" ),
labelID = controlID + "-label",
label = $( "[for='"+ controlID +"']" ).attr( "id", labelID ),
val = function() {
return cType == "input" ? parseFloat( control.val() ) : control[0].selectedIndex;
},
min = cType == "input" ? parseFloat( control.attr( "min" ) ) : 0,
max = cType == "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
step = window.parseFloat( control.attr( "step" ) || 1 ),
slider = $( "<div class='ui-slider " + selectClass + " ui-btn-down-" + trackTheme +
" ui-btn-corner-all' role='application'></div>" ),
handle = $( "<a href='#' class='ui-slider-handle'></a>" )
.appendTo( slider )
.buttonMarkup({ corners: true, theme: theme, shadow: true })
.attr({
"role": "slider",
"aria-valuemin": min,
"aria-valuemax": max,
"aria-valuenow": val(),
"aria-valuetext": val(),
"title": val(),
"aria-labelledby": labelID
}),
options;
.mobile.activeBtnClass );
$( "<div class='ui-slider-labelbg ui-slider-labelbg-" + side + theme + " ui-btn-corner-" + corners + "'></div>" )
.prependTo( slider );
$( "<span class='ui-slider-label ui-slider-label-" + side + theme + " ui-btn-corner-" + corners + "' role='img'>" + $( this ).getEncodedText() + "</span>" )
.prependTo( handle );
});
}
label.addClass( "ui-slider" );
// monitor the input for updated values
control.addClass( cType === "input" ? "ui-slider-input" : "ui-slider-switch" )
.change( function() {
// if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
if (!self.mouseMoved) {
self.refresh( val(), true );
}
})
.keyup( function() { // necessary?
self.refresh( val(), true, true );
})
.blur( function() {
self.refresh( val(), true );
});
// prevent screen drag when slider activated
$( document ).bind( "vmousemove", function( event ) {
if ( self.dragging ) {
// self.mouseMoved must be updated before refresh() because it will be used in the control "change" event
self.mouseMoved = true;
if ( cType === "select" ) {
// make the handle move in sync with the mouse
handle.removeClass( "ui-slider-handle-snapping" );
}
self.refresh( event );
// only after refresh() you can calculate self.userModified
self.userModified = self.beforeStart !== control[0].selectedIndex;
return false;
}
});
slider.bind( "vmousedown", function( event ) {
self.dragging = true;
self.userModified = false;
self.mouseMoved = false;
if ( cType === "select" ) {
self.beforeStart = control[0].selectedIndex;
}
self.refresh( event );
return false;
});
slider.add( document )
.bind( "vmouseup", function() {
if ( self.dragging ) {
self.dragging = false;
if ( cType === "select") {
// make the handle move with a smooth transition
handle.addClass( "ui-slider-handle-snapping" );
if ( self.mouseMoved ) {
// this is a drag, change the value only if user dragged enough
if ( self.userModified ) {
self.refresh( self.beforeStart == 0 ? 1 : 0 );
}
else {
self.refresh( self.beforeStart );
}
}
else {
// this is just a click, change the value
self.refresh( self.beforeStart == 0 ? 1 : 0 );
}
}
self.mouseMoved = false;
return false;
}
});
slider.insertAfter( control );
// NOTE force focus on handle
this.handle
.bind( "vmousedown", function() {
$( this ).focus();
})
.bind( "vclick", false );
this.handle
.bind( "keydown", function( event ) {
var index = val();
if ( self.options.disabled ) {
return;
}
// In all cases prevent the default and mark the handle as active
switch ( event.keyCode ) {
case .mobile.keyCode.END:
case .mobile.keyCode.PAGE_DOWN:
case .mobile.keyCode.RIGHT:
case .mobile.keyCode.LEFT:
event.preventDefault();
if ( !self._keySliding ) {
self._keySliding = true;
$( this ).addClass( "ui-state-active" );
}
break;
}
// move the slider according to the keypress
switch ( event.keyCode ) {
case .mobile.keyCode.END:
self.refresh( max );
break;
case .mobile.keyCode.UP:
case .mobile.keyCode.PAGE_DOWN:
case .mobile.keyCode.LEFT:
self.refresh( index - step );
break;
}
}) // remove active mark
.keyup( function( event ) {
if ( self._keySliding ) {
self._keySliding = false;
$( this ).removeClass( "ui-state-active" );
}
});
this.refresh(undefined, undefined, true);
},
refresh: function( val, isfromControl, preventInputUpdate ) {
if ( this.options.disabled || this.element.attr('disabled')) {
this.disable();
}
var control = this.element, percent,
cType = control[0].nodeName.toLowerCase(),
min = cType === "input" ? parseFloat( control.attr( "min" ) ) : 0,
max = cType === "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length - 1;
if ( typeof val === "object" ) {
var data = val,
// a slight tolerance helped get to the ends of the slider
tol = 8;
if ( !this.dragging ||
data.pageX < this.slider.offset().left - tol ||
data.pageX > this.slider.offset().left + this.slider.width() + tol ) {
return;
}
percent = Math.round( ( ( data.pageX - this.slider.offset().left ) / this.slider.width() ) * 100 );
} else {
if ( val == null ) {
val = cType === "input" ? parseFloat( control.val() ) : control[0].selectedIndex;
}
percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
}
if ( isNaN( percent ) ) {
return;
}
if ( percent < 0 ) {
percent = 0;
}
if ( percent > 100 ) {
percent = 100;
}
var newval = Math.round( ( percent / 100 ) * ( max - min ) ) + min;
if ( newval < min ) {
newval = min;
}
if ( newval > max ) {
newval = max;
}
// Flip the stack of the bg colors
if ( percent > 60 && cType === "select" ) {
// TODO: Dead path?
}
this.handle.css( "left", percent + "%" );
this.handle.attr( {
"aria-valuenow": cType === "input" ? newval : control.find( "option" ).eq( newval ).attr( "value" ),
"aria-valuetext": cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText(),
title: newval
});
// add/remove classes for flip toggle switch
if ( cType === "select" ) {
if ( newval === 0 ) {
this.slider.addClass( "ui-slider-switch-a" )
.removeClass( "ui-slider-switch-b" );
} else {
this.slider.addClass( "ui-slider-switch-b" )
.removeClass( "ui-slider-switch-a" );
}
}
if ( !preventInputUpdate ) {
var valueChanged = false;
// update control"s value
if ( cType === "input" ) {
valueChanged = control.val() !== newval;
control.val( newval );
} else {
valueChanged = control[ 0 ].selectedIndex !== newval;
control[ 0 ].selectedIndex = newval;
}
if ( !isfromControl && valueChanged ) {
control.trigger( "change" );
}
}
},
enable: function() {
this.element.attr( "disabled", false );
this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false );
return this._setOption( "disabled", false );
},
disable: function() {
this.element.attr( "disabled", true );
this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true );
return this._setOption( "disabled", true );
}
});
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
, undefined ) {
.mobile.widget, {
options: {
theme: null,
initSelector: "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])"
},
_create: function() {
var input = this.element,
o = this.options,
theme = o.theme || .support.touchOverflow ) {
// Set the attribute instead of the property just in case there
// is code that attempts to make modifications via HTML.
input[0].setAttribute( "autocorrect", "off" );
input[0].setAttribute( "autocomplete", "off" );
}
//"search" input widget
if ( input.is( "[type='search'],:jqmData(type='search')" ) ) {
focusedEl = input.wrap( "<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield" + themeclass + "'></div>" ).parent();
clearbtn = $( "<a href='#' class='ui-input-clear' title='clear text'>clear text</a>" )
.tap(function( event ) {
input.val( "" ).focus();
input.trigger( "change" );
clearbtn.addClass( "ui-input-clear-hidden" );
event.preventDefault();
})
.appendTo( focusedEl )
.buttonMarkup({
icon: "delete",
iconpos: "notext",
corners: true,
shadow: true
});
function toggleClear() {
setTimeout(function() {
clearbtn.toggleClass( "ui-input-clear-hidden", !input.val() );
}, 0);
}
toggleClear();
input.bind('paste cut keyup focus change blur', toggleClear);
} else {
input.addClass( "ui-corner-all ui-shadow-inset" + themeclass );
}
input.focus(function() {
focusedEl.addClass( "ui-focus" );
})
.blur(function(){
focusedEl.removeClass( "ui-focus" );
});
// Autogrow
if ( input.is( "textarea" ) ) {
var extraLineHeight = 15,
keyupTimeoutBuffer = 100,
keyup = function() {
var scrollHeight = input[ 0 ].scrollHeight,
clientHeight = input[ 0 ].clientHeight;
if ( clientHeight < scrollHeight ) {
input.height(scrollHeight + extraLineHeight);
}
},
keyupTimeout;
input.keyup(function() {
clearTimeout( keyupTimeout );
keyupTimeout = setTimeout( keyup, keyupTimeoutBuffer );
});
// Issue 509: the browser is not providing scrollHeight properly until the styles load
if ( .mobile.textinput.prototype.enhanceWithin( e.target );
});
})( jQuery );
/*
* custom "selectmenu" plugin
*/
(function( .mobile.ns + "role='dialog' data-" +.mobile.ns + "overlay-theme='"+ widget.options.overlayTheme +"'>" +
"<div data-" + .mobile.ns + "role='content'></div>"+
"</div>" ).appendTo( .mobile.defaultDialogTransition } ).insertAfter(screen),
list = $( "<ul>", {
"class": "ui-selectmenu-list",
"id": menuId,
"role": "listbox",
"aria-labelledby": buttonId
}).attr( "data-" + .mobile.ns + "iconpos", "notext" ).attr( "data-" + .extend( widget, {
select: widget.select,
selectID: selectID,
buttonId: buttonId,
menuId: menuId,
thisPage: thisPage,
menuPage: menuPage,
label: label,
screen: screen,
selectOptions: selectOptions,
isMultiple: isMultiple,
theme: widget.options.theme,
listbox: listbox,
list: list,
header: header,
headerTitle: headerTitle,
headerClose: headerClose,
menuPageContent: menuPageContent,
menuPageClose: menuPageClose,
placeholder: "",
build: function() {
var self = this;
// Create list from select, update state
self.refresh();
self.select.attr( "tabindex", "-1" ).focus(function() {
$( this ).blur();
self.button.focus();
});
// Button events
self.button.bind( "vclick keydown" , function( event ) {
if ( event.type == "vclick" ||
event.keyCode && ( event.keyCode === .mobile.keyCode.SPACE ) ) {
self.open();
event.preventDefault();
}
});
// Events for list items
self.list.attr( "role", "listbox" )
.delegate( ".ui-li>a", "focusin", function() {
$( this ).attr( "tabindex", "0" );
})
.delegate( ".ui-li>a", "focusout", function() {
$( this ).attr( "tabindex", "-1" );
})
.delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) {
// index of option tag to be selected
var oldIndex = self.select[ 0 ].selectedIndex,
newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ),
option = self._selectOptions().eq( newIndex )[ 0 ];
// toggle selected status on the tag for multi selects
option.selected = self.isMultiple ? !option.selected : true;
// toggle checkbox class for multiple selects
if ( self.isMultiple ) {
$( this ).find( ".ui-icon" )
.toggleClass( "ui-icon-checkbox-on", option.selected )
.toggleClass( "ui-icon-checkbox-off", !option.selected );
}
// trigger change if value changed
if ( self.isMultiple || oldIndex !== newIndex ) {
self.select.trigger( "change" );
}
//hide custom select for single selects only
if ( !self.isMultiple ) {
self.close();
}
event.preventDefault();
})
.keydown(function( event ) { //keyboard events for menu items
var target = $( event.target ),
li = target.closest( "li" ),
prev, next;
// switch logic based on which key was pressed
switch ( event.keyCode ) {
// up or left arrow keys
case 38:
prev = li.prev();
// if there's a previous option, focus it
if ( prev.length ) {
target
.blur()
.attr( "tabindex", "-1" );
prev.find( "a" ).first().focus();
}
return false;
break;
// down or right arrow keys
case 40:
next = li.next();
// if there's a next option, focus it
if ( next.length ) {
target
.blur()
.attr( "tabindex", "-1" );
next.find( "a" ).first().focus();
}
return false;
break;
// If enter or space is pressed, trigger click
case 13:
case 32:
target.trigger( "click" );
return false;
break;
}
});
// button refocus ensures proper height calculation
// by removing the inline style and ensuring page inclusion
self.menuPage.bind( "pagehide", function() {
self.list.appendTo( self.listbox );
self._focusButton();
// TODO centralize page removal binding / handling in the page plugin.
// Suggestion from @jblas to do refcounting
//
// TODO extremely confusing dependency on the open method where the pagehide.remove
// bindings are stripped to prevent the parent page from disappearing. The way
// we're keeping pages in the DOM right now sucks
//
// rebind the page remove that was unbound in the open function
// to allow for the parent page removal from actions other than the use
// of a dialog sized custom select
//
// doing this here provides for the back button on the custom select dialog
.mobile.activeBtnClass )
.attr( "aria-selected", false )
.each(function( i ) {
if ( .mobile.activeBtnClass );
}
}
});
},
close: function() {
if ( this.options.disabled || !this.isOpen ) {
return;
}
var self = this;
if ( self.menuType == "page" ) {
// doesn't solve the possible issue with calling change page
// where the objects don't define data urls which prevents dialog key
// stripping - changePage has incoming refactor
window.history.back();
} else {
self.screen.addClass( "ui-screen-hidden" );
self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass( "in" );
self.list.appendTo( self.listbox );
self._focusButton();
}
// allow the dialog to be closed again
self.isOpen = false;
},
open: function() {
if ( this.options.disabled ) {
return;
}
var self = this,
menuHeight = self.list.parent().outerHeight(),
menuWidth = self.list.parent().outerWidth(),
activePage = $( ".ui-page-active" ),
tOverflow = .mobile.touchOverflowEnabled,
tScrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage;
scrollTop = tOverflow ? tScrollElem.scrollTop() : $( window ).scrollTop(),
btnOffset = self.button.offset().top,
screenHeight = window.innerHeight,
screenWidth = window.innerWidth;
//add active class to button
self.button.addClass( .mobile.activeBtnClass );
}, 300);
function focusMenuItem() {
self.list.find( .support.scrollTop ) {
// prevent the parent page from being removed from the DOM,
// otherwise the results of selecting a list item in the dialog
// fall into a black hole
self.thisPage.unbind( "pagehide.remove" );
//for WebOS/Opera Mini (set lastscroll using button offset)
if ( scrollTop == 0 && btnOffset > screenHeight ) {
self.thisPage.one( "pagehide", function() {
$( this ).jqmData( "lastScroll", btnOffset );
});
}
self.menuPage.one( "pageshow", function() {
// silentScroll() is called whenever a page is shown to restore
// any previous scroll position the page may have had. We need to
// wait for the "silentscroll" event before setting focus to avoid
// the browser"s "feature" which offsets rendering to make sure
// whatever has focus is in view.
$( window ).one( "silentscroll", function() {
focusMenuItem();
});
self.isOpen = true;
});
self.menuType = "page";
self.menuPageContent.append( self.list );
self.menuPage.find("div .ui-title").text(self.label.text());
.mobile.defaultDialogTransition
});
} else {
self.menuType = "overlay";
self.screen.height( document.height() )
.removeClass( "ui-screen-hidden" );
// Try and center the overlay over the button
var roomtop = btnOffset - scrollTop,
roombot = scrollTop + screenHeight - btnOffset,
halfheight = menuHeight / 2,
maxwidth = parseFloat( self.list.parent().css( "max-width" ) ),
newtop, newleft;
if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight;
} else {
// 30px tolerance off the edges
newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
}
// If the menuwidth is smaller than the screen center is
if ( menuWidth < maxwidth ) {
newleft = ( screenWidth - menuWidth ) / 2;
} else {
//otherwise insure a >= 30px offset from the left
newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2;
// 30px tolerance off the edges
if ( newleft < 30 ) {
newleft = 30;
} else if ( (newleft + menuWidth) > screenWidth ) {
newleft = screenWidth - menuWidth - 30;
}
}
self.listbox.append( self.list )
.removeClass( "ui-selectmenu-hidden" )
.css({
top: newtop,
left: newleft
})
.addClass( "in" );
focusMenuItem();
// duplicate with value set in page show for dialog sized selects
self.isOpen = true;
}
},
_buildList: function() {
var self = this,
o = this.options,
placeholder = this.placeholder,
optgroups = [],
lis = [],
dataIcon = self.isMultiple ? "checkbox-off" : "false";
self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
// Populate menu with options from select element
self.select.find( "option" ).each( function( i ) {
var parent = this.getEncodedText(),
anchor = "<a href='#'>"+ text +"</a>",
classes = [],
extraAttrs = [];
// Are we inside an optgroup?
if ( parent.attr( "label" );
// has this optgroup already been built yet?
if ( .mobile.ns + "role='list-divider'>"+ optLabel +"</li>" );
optgroups.push( optLabel );
}
}
// Find placeholder text
// TODO: Are you sure you want to use getAttribute? ^RW
if ( !this.getAttribute( "value" ) || text.length == 0 || .mobile.ns + "option-index='" + i + "' data-" + , undefined ) {
.mobile.widget, {
options: {
theme: null,
disabled: false,
icon: "arrow-d",
iconpos: "right",
inline: null,
corners: true,
shadow: true,
iconshadow: true,
menuPageTheme: "b",
overlayTheme: "a",
hidePlaceholderMenuItems: true,
closeText: "Close",
nativeMenu: true,
initSelector: "select:not(:jqmData(role='slider'))"
},
_button: function(){
return $( "<div/>" );
},
_setDisabled: function( value ) {
this.element.attr( "disabled", value );
this.button.attr( "aria-disabled", value );
return this._setOption( "disabled", value );
},
_focusButton : function() {
var self = this;
setTimeout( function() {
self.button.focus();
}, 40);
},
_selectOptions: function() {
return this.select.find( "option" );
},
// setup items that are generally necessary for select menu extension
_preExtension: function(){
this.select = this.element.wrap( "<div class='ui-select'>" );
this.selectID = this.select.attr( "id" );
this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" );
this.isMultiple = this.select[ 0 ].multiple;
if ( !this.options.theme ) {
this.options.theme = .mobile.activeBtnClass );
})
.bind( "focus vmouseover", function() {
self.button.trigger( "vmouseover" );
})
.bind( "vmousemove", function() {
// Remove active class on scroll/touchmove
self.button.removeClass( .mobile.activeBtnClass );
})
.bind( "change blur", function() {
self.button.removeClass( "ui-btn-down-" + self.options.theme );
});
},
selected: function() {
return this._selectOptions().filter( ":selected" );
},
selectedIndices: function() {
var self = this;
return this.selected().map( function() {
return self._selectOptions().index( this );
}).get();
},
setButtonText: function() {
var self = this, selected = this.selected();
this.button.find( ".ui-btn-text" ).text( function() {
if ( !self.isMultiple ) {
return selected.text();
}
return selected.length ? selected.map( function() {
return $( this ).text();
}).get().join( ", " ) : self.placeholder;
});
},
setButtonCount: function() {
var selected = this.selected();
// multiple count inside button
if ( this.isMultiple ) {
this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
}
},
refresh: function() {
this.setButtonText();
this.setButtonCount();
},
// open and close preserved in native selects
// to simplify users code when looping over selects
open: .noop,
disable: function() {
this._setDisabled( true );
this.button.addClass( "ui-disabled" );
},
enable: function() {
this._setDisabled( false );
this.button.removeClass( "ui-disabled" );
}
});
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
, undefined ) {
.extend( {}, .mobile.getInheritedTheme( el, "c" );
}
buttonClass = "ui-btn ui-btn-up-" + o.theme;
if ( o.inline ) {
buttonClass += " ui-btn-inline";
}
if ( o.icon ) {
o.icon = "ui-icon-" + o.icon;
o.iconpos = o.iconpos || "left";
iconClass = "ui-icon " + o.icon;
if ( o.iconshadow ) {
iconClass += " ui-icon-shadow";
}
}
if ( o.iconpos ) {
buttonClass += " ui-btn-icon-" + o.iconpos;
if ( o.iconpos == "notext" && !el.attr( "title" ) ) {
el.attr( "title", el.getEncodedText() );
}
}
if ( o.corners ) {
buttonClass += " ui-btn-corner-all";
innerClass += " ui-btn-corner-all";
}
if ( o.shadow ) {
buttonClass += " ui-shadow";
}
e.setAttribute( "data-" + .data( e, 'textWrapper', $( buttonText ) );
}
return this;
};
.inArray( "ui-btn", cname ) > -1 && btn, theme;
if ( btn ) {
btn.attr( "data-" + btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme );
}
},
"vmousecancel vmouseup": function( event ) {
var btn = closestEnabledButton( event.target ),
btn = $( btn );
theme = .mobile.ns + "theme" );
btn, theme;
if ( btn ) {
btn.attr( "data-" + btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme );
}
},
"vmouseout blur": function( event ) {
var btn = closestEnabledButton( event.target ),
btn = $( btn );
theme = .mobile.ns + "theme" );
, undefined ) {
el = $( this ),
o = el.jqmData( "type" ) || "vertical",
shadow: false,
excludeInvisible: true
}, options ),
groupheading = el.find( "input" ).first().attr( "type" );
// Replace legend with more stylable replacement div
if ( groupheading.length ) {
el.children(0) );
groupheading.remove();
}
el.find( ".ui-btn" + ( o.excludeInvisible ? ":visible" : "" ) ) );
flipClasses( el.addClass( "ui-shadow" );
}
});
};
//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ){
$( ":jqmData(role='controlgroup')", e.target ).controlgroup({ excludeInvisible: false });
});
})(jQuery);/*
* "links" plugin - simple class additions for links
*/
(function( , undefined ) {
var slideDownClass = "ui-header-fixed ui-fixed-inline fade",
slideUpClass = "ui-footer-fixed ui-fixed-inline fade",
slideDownSelector = ".ui-header:jqmData(position='fixed')",
slideUpSelector = ".ui-footer:jqmData(position='fixed')";
.support.scrollTop || ( .mobile.touchOverflowEnabled ) ) {
return this;
}
return this.each(function() {
var this.jqmData( "fullscreen" ) ) {
this.find( slideDownSelector ).addClass( slideDownClass );
// Should be slideup
.mobile.fixedToolbars = (function() {
if ( !.support.touchOverflow && .support.touch,
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
stateBefore = null,
scrollTriggered = false,
touchToggleEnabled = true;
function showEventCallback( event ) {
// An event that affects the dimensions of the visual viewport has
// been triggered. If the header and/or footer for the current page are in overlay
// mode, we want to hide them, and then fire off a timer to show them at a later
// point. Events like a resize can be triggered continuously during a scroll, on
// some platforms, so the timer is used to delay the actual positioning until the
// flood of events have subsided.
//
// If we are in autoHideMode, we don't do anything because we know the scroll
// callbacks for the plugin will fire off a show when the scrolling has stopped.
if ( !autoHideMode && currentstate === "overlay" ) {
if ( !delayTimer ) {
.mobile.fixedToolbars.startShowTimer();
}
}
$(function() {
var window = $( window );
.mobile.fixedToolbars.toggle( stateBefore );
stateBefore = null;
}
}
})
.bind( "silentscroll", showEventCallback );
// The below checks first for a document.scrollTop() value, and if zero, binds scroll events to window instead.
// If the scrollTop value is actually zero, both will return zero anyway.
//
// Works with document, not window : Opera Mobile (WinMO phone; kinda broken anyway)
// Works with window, not document : IE 7/8
// Works with either window or document : Chrome, FF 3.6/4, Android 1.6/2.1, iOS
// Needs work either way : BB5, Opera Mobile (iOS)
( ( window : .mobile.fixedToolbars.clearShowTimer();
if ( isOverlayState ) {
.mobile.fixedToolbars.startShowTimer();
autoHideMode = false;
}
stateBefore = null;
});
.mobile.pageContainer ) );
}
})
.live( "pageshow", function( event, ui ) {
var this ).addClass( "fade" ) );
stickyFooter = null;
}, 500);
}
.mobile.fixedToolbars.clearShowTimer();
currentstate = "overlay";
var .mobile.activePage ? ap.children( toolbarSelector ).each(function() {
var el = $( this ),
fromTop = $( window ).scrollTop(),
// el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead.
thisTop = getOffsetTop( el[ 0 ] ),
screenHeight = window.innerHeight,
thisHeight = el.outerHeight(),
alreadyVisible = ( el.is( ".ui-header-fixed" ) && fromTop <= thisTop + thisHeight ) ||
( el.is( ".ui-footer-fixed" ) && thisTop <= fromTop + screenHeight );
// Add state class
el.addClass( "ui-fixed-overlay" ).removeClass( "ui-fixed-inline" );
if ( !alreadyVisible && !immediately ) {
el.animationComplete(function() {
el.removeClass( "in" );
}).addClass( "in" );
}
setTop(el);
});
},
hide: function( immediately ) {
currentstate = "inline";
var .mobile.activePage ? ap.children( toolbarSelector ).each(function() {
var el = this,
thisCSStop = el.css( "top" ),
classes;
thisCSStop = thisCSStop == "auto" ? 0 :
parseFloat(thisCSStop);
// Add state class
el.addClass( "ui-fixed-inline" ).removeClass( "ui-fixed-overlay" );
if ( thisCSStop < 0 || ( el.is( ".ui-header-fixed" ) && thisCSStop !== 0 ) ) {
if ( immediately ) {
el.css( "top", 0);
} else {
if ( el.css( "top" ) !== "auto" && parseFloat( el.css( "top" ) ) !== 0 ) {
classes = "out reverse";
el.animationComplete(function() {
el.removeClass( classes ).css( "top", 0 );
}).addClass( classes );
}
}
}
});
},
startShowTimer: function() {
.mobile.fixedToolbars.show.apply( null, args );
}, showDelay);
},
clearShowTimer: function() {
if ( delayTimer ) {
clearTimeout( delayTimer );
}
delayTimer = undefined;
},
toggle: function( from ) {
if ( from ) {
currentstate = from;
}
return ( currentstate === "overlay" ) ? .mobile.fixedToolbars.show();
},
setTouchToggleEnabled: function( enabled ) {
touchToggleEnabled = enabled;
}
};
})();
//auto self-init widgets
$( document ).bind( "pagecreate create", function( event ) {
if ( $( ":jqmData(position='fixed')", event.target ).length ) {
$( event.target ).each(function() {
if ( !.support.touchOverflow && this = $( this );
if ( this.addClass( "ui-page-fullscreen" );
}
// Should be slidedown
this.find( slideUpSelector ).addClass( slideUpClass );
})
}
});
})( jQuery );
/*
* "fixHeaderFooter" native plugin - Behavior for "fixed" headers,footers, and scrolling inner content
*/
(function( .mobile.touchOverflowEnabled = false;
// Enabled zoom when touch overflow is enabled. Can cause usability issues, unfortunately
.support.touchOverflow && target = $( event.target ),
scrollStartY = 0;
if( target.each(function() {
var fixies = page.jqmData( "fullscreen" ),
fixies.length ? page;
scrollElem.bind( "scrollstop", function(){
if( .mobile.defaultHomeScroll );
}
});
if( page.addClass( "ui-native-fixed" );
if( fullScreen ){
fixies.addClass( "fade in" );
$( document ).bind( "vclick", function(){
, window, undefined ) {
var head = $( "head" ),
.mobile settings before they're used
$( window.document ).trigger( "mobileinit" );
// support conditions
// if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
// otherwise, proceed with the enhancements
if ( !.mobile.ajaxBlacklist ) {
html.addClass( "ui-mobile ui-mobile-rendering" );
// loading div which appears during Ajax requests
// will not appear if loader = $( "<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1></h1></div>" );
.mobile, {
// turn on/off page loading message.
showPageLoadingMsg: function() {
if ( .mobile.activeBtnClass ).first();
.mobile.loadingMessage )
.end()
.appendTo( .support.scrollTop && window.height() / 2 ||
activeBtn.length && activeBtn.offset().top || 100
});
}
html.removeClass( "ui-loading" );
},
// find and enhance the pages in the dom and transition to the first page.
initializePage: function() {
// find present pages
var pages.length ) {
.mobile.ns + "role='page'></div>" ).children( 0 );
}
// add dialogs, set data-url attrs
this = this;
// unless the data url is already set set it to the pathname
if ( !this.attr( "data-" + this.attr( "id" ) || location.pathname + location.search );
}
});
// define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
pages.first();
// define page container
pages.first().parent().addClass( "ui-mobile-viewport" );
// alert listeners that the pagecontainer has been determined for binding
// to events triggered on it
.mobile.showPageLoadingMsg();
// if hashchange listening is disabled or there's no hash deeplink, change to the first page in the DOM
if ( !.mobile.path.stripHash( location.hash ) ) {
.mobile.firstPage, { transition: "none", reverse: true, changeHash: false, fromHashChange: true } );
}
// otherwise, trigger a hashchange to load a deeplink
else {
.support.touchOverflow && .mobile.touchOverflowZoomEnabled ){
disableZoom();
}
// initialize events now, after mobileinit has occurred
.mobile.defaultHomeScroll = ( !.mobile.autoInitializePage ){
window.load(
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.