mirror of
https://github.com/Hopiu/jquery-mobile.git
synced 2026-03-21 00:10:24 +00:00
- Moved the settings.transition calculation code before the code that pushes a new history item on the stack. This ensures that the proper transition is stored with the history item.
1454 lines
50 KiB
JavaScript
Executable file
1454 lines
50 KiB
JavaScript
Executable file
/*
|
|
* jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt,
|
|
* Copyright (c) jQuery Project
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
* http://jquery.org/license
|
|
*/
|
|
( function( $, undefined ) {
|
|
|
|
//define vars for interal use
|
|
var $window = $( window ),
|
|
$html = $( 'html' ),
|
|
$head = $( 'head' ),
|
|
|
|
//url path helpers for use in relative url management
|
|
path = {
|
|
|
|
// This scary looking regular expression parses an absolute URL or its relative
|
|
// variants (protocol, site, document, query, and hash), into the various
|
|
// components (protocol, host, path, query, fragment, etc that make up the
|
|
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
|
|
// or String.match, it parses the URL into a results array that looks like this:
|
|
//
|
|
// [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
|
|
// [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
|
|
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
|
|
// [3]: http://jblas:password@mycompany.com:8080
|
|
// [4]: http:
|
|
// [5]: //
|
|
// [6]: jblas:password@mycompany.com:8080
|
|
// [7]: jblas:password
|
|
// [8]: jblas
|
|
// [9]: password
|
|
// [10]: mycompany.com:8080
|
|
// [11]: mycompany.com
|
|
// [12]: 8080
|
|
// [13]: /mail/inbox
|
|
// [14]: /mail/
|
|
// [15]: inbox
|
|
// [16]: ?msg=1234&type=unread
|
|
// [17]: #msg-content
|
|
//
|
|
urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
|
|
|
|
//Parse a URL into a structure that allows easy access to
|
|
//all of the URL components by name.
|
|
parseUrl: function( url ) {
|
|
// If we're passed an object, we'll assume that it is
|
|
// a parsed url object and just return it back to the caller.
|
|
if ( $.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( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
|
|
|
|
var absStack = absPath ? absPath.split( "/" ) : [],
|
|
relStack = relPath.split( "/" );
|
|
for ( var i = 0; i < relStack.length; i++ ) {
|
|
var d = relStack[ i ];
|
|
switch ( d ) {
|
|
case ".":
|
|
break;
|
|
case "..":
|
|
if ( absStack.length ) {
|
|
absStack.pop();
|
|
}
|
|
break;
|
|
default:
|
|
absStack.push( d );
|
|
break;
|
|
}
|
|
}
|
|
return "/" + absStack.join( "/" );
|
|
},
|
|
|
|
//Returns true if both urls have the same domain.
|
|
isSameDomain: function( absUrl1, absUrl2 ) {
|
|
return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
|
|
},
|
|
|
|
//Returns true for any relative variant.
|
|
isRelativeUrl: function( url ) {
|
|
// All relative Url variants have one thing in common, no protocol.
|
|
return path.parseUrl( url ).protocol === "";
|
|
},
|
|
|
|
//Returns true for an absolute url.
|
|
isAbsoluteUrl: function( url ) {
|
|
return path.parseUrl( url ).protocol !== "";
|
|
},
|
|
|
|
//Turn the specified realtive URL into an absolute one. This function
|
|
//can handle all relative variants (protocol, site, document, query, fragment).
|
|
makeUrlAbsolute: function( relUrl, absUrl ) {
|
|
if ( !path.isRelativeUrl( relUrl ) ) {
|
|
return relUrl;
|
|
}
|
|
|
|
var relObj = path.parseUrl( relUrl ),
|
|
absObj = path.parseUrl( absUrl ),
|
|
protocol = relObj.protocol || absObj.protocol,
|
|
doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
|
|
authority = relObj.authority || absObj.authority,
|
|
hasPath = relObj.pathname !== "",
|
|
pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
|
|
search = relObj.search || ( !hasPath && absObj.search ) || "",
|
|
hash = relObj.hash;
|
|
|
|
return protocol + doubleSlash + authority + pathname + search + hash;
|
|
},
|
|
|
|
//Add search (aka query) params to the specified url.
|
|
addSearchParams: function( url, params ) {
|
|
var u = path.parseUrl( url ),
|
|
p = ( typeof params === "object" ) ? $.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( /[^\/]*\.[^\/*]+$/, '' );
|
|
},
|
|
|
|
//return the substring of a filepath before the sub-page key, for making a server request
|
|
getFilePath: function( path ) {
|
|
var splitkey = '&' + $.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( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
|
|
},
|
|
|
|
//check whether a url is referencing the same domain, or an external domain or different protocol
|
|
//could be mailto, etc
|
|
isExternal: function( url ) {
|
|
var u = path.parseUrl( url );
|
|
return u.protocol && u.domain !== documentUrl.domain ? true : false;
|
|
},
|
|
|
|
hasProtocol: function( url ) {
|
|
return ( /^(:?\w+:)/ ).test( url );
|
|
},
|
|
|
|
//check if the specified url refers to the first page in the main application document.
|
|
isFirstPageUrl: function( url ) {
|
|
// We only deal with absolute paths.
|
|
var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ),
|
|
|
|
// Does the url have the same path as the document?
|
|
samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ),
|
|
|
|
// Get the first page element.
|
|
fp = $.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
|
|
$activeClickedLink = null,
|
|
|
|
//urlHistory is purely here to make guesses at whether the back or forward button was clicked
|
|
//and provide an appropriate transition
|
|
urlHistory = {
|
|
// Array of pages that are visited during a single page load.
|
|
// Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs)
|
|
stack: [],
|
|
|
|
//maintain an index number for the active page in the stack
|
|
activeIndex: 0,
|
|
|
|
//get active
|
|
getActive: function() {
|
|
return urlHistory.stack[ urlHistory.activeIndex ];
|
|
},
|
|
|
|
getPrev: function() {
|
|
return urlHistory.stack[ urlHistory.activeIndex - 1 ];
|
|
},
|
|
|
|
getNext: function() {
|
|
return urlHistory.stack[ urlHistory.activeIndex + 1 ];
|
|
},
|
|
|
|
// addNew is used whenever a new page is added
|
|
addNew: function( url, transition, title, pageUrl, role ) {
|
|
//if there's forward history, wipe it
|
|
if( urlHistory.getNext() ) {
|
|
urlHistory.clearForward();
|
|
}
|
|
|
|
urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl, role: role } );
|
|
|
|
urlHistory.activeIndex = urlHistory.stack.length - 1;
|
|
},
|
|
|
|
//wipe urls ahead of active index
|
|
clearForward: function() {
|
|
urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 );
|
|
},
|
|
|
|
directHashChange: function( opts ) {
|
|
var back , forward, newActiveIndex, prev = this.getActive();
|
|
|
|
// check if url isp in history and if it's ahead or behind current page
|
|
$.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?
|
|
$base = $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.length ? path.parseUrl( path.makeUrlAbsolute( $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 = $.support.dynamicBaseTag ? {
|
|
|
|
//define base element, for use in routing asset urls that are referenced in Ajax-requested markup
|
|
element: ( $base.length ? $base : $( "<base>", { href: documentBase.hrefNoHash } ).prependTo( $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 && ( !$activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval ) ) {
|
|
$activeClickedLink.removeClass( $.mobile.activeBtnClass );
|
|
}
|
|
$activeClickedLink = null;
|
|
}
|
|
|
|
function releasePageTransitionLock() {
|
|
isPageTransitioning = false;
|
|
if( pageTransitionQueue.length > 0 ) {
|
|
$.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 = $window, activePage,
|
|
touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled;
|
|
|
|
if( touchOverflow ){
|
|
activePage = $( ".ui-page-active" );
|
|
scrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage;
|
|
}
|
|
|
|
return scrollElem;
|
|
};
|
|
|
|
setLastScroll = function( scrollElem ) {
|
|
// this barrier prevents setting the scroll value based on the browser
|
|
// scrolling the window based on a hashchange
|
|
if( !setLastScrollEnabled ) {
|
|
return;
|
|
}
|
|
|
|
var active = $.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.minScrollBack ? $.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
|
|
$window.bind( $.support.pushState ? "popstate" : "hashchange", function() {
|
|
setLastScrollEnabled = false;
|
|
});
|
|
|
|
// handle initial hashchange from chrome :(
|
|
$window.one( $.support.pushState ? "popstate" : "hashchange", function() {
|
|
setLastScrollEnabled = true;
|
|
});
|
|
|
|
// wait until the mobile page container has been determined to bind to pagechange
|
|
$window.one( "pagecontainercreate", function(){
|
|
// once the page has changed, re-enable the scroll recording
|
|
$.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( $.support.touchOverflow ){
|
|
if( $.mobile.touchOverflowEnabled ){
|
|
$( window ).bind( "scrollstop", function(){
|
|
if( $( this ).scrollTop() === 0 ){
|
|
$.mobile.activePage.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 = $.support.touchOverflow && $.mobile.touchOverflowEnabled,
|
|
toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ),
|
|
screenHeight = getScreenHeight();
|
|
|
|
// Scroll to top, hide addr bar
|
|
window.scrollTo( 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.hidePageLoadingMsg();
|
|
|
|
if( touchOverflow && toScroll ){
|
|
|
|
toPage.addClass( "ui-mobile-pre-transition" );
|
|
// Send focus to page as it is now display: block
|
|
reFocus( toPage );
|
|
|
|
//set page's scrollTop to remembered distance
|
|
if( toPage.is( ".ui-native-fixed" ) ){
|
|
toPage.find( ".ui-content" ).scrollTop( toScroll );
|
|
}
|
|
else{
|
|
toPage.scrollTop( toScroll );
|
|
}
|
|
}
|
|
|
|
//find the transition handler for the specified transition. If there
|
|
//isn't one in our transitionHandlers dictionary, use the default one.
|
|
//call the handler immediately to kick-off the transition.
|
|
var th = $.mobile.transitionHandlers[transition || "none"] || $.mobile.defaultTransitionHandler,
|
|
promise = th( transition, reverse, toPage, fromPage );
|
|
|
|
promise.done(function() {
|
|
//reset toPage height back
|
|
if( !touchOverflow ){
|
|
toPage.height( "" );
|
|
// Send focus to the newly shown page
|
|
reFocus( toPage );
|
|
}
|
|
|
|
// Jump to top or prev scroll, sometimes on iOS the page has not rendered yet.
|
|
if( !touchOverflow ){
|
|
$.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 = jQuery.event.special.orientationchange.orientation(),
|
|
port = orientation === "portrait",
|
|
winMin = port ? 480 : 320,
|
|
screenHeight = port ? screen.availHeight : screen.availWidth,
|
|
winHeight = Math.max( winMin, $( window ).height() ),
|
|
pageMin = Math.min( screenHeight, winHeight );
|
|
|
|
return pageMin;
|
|
}
|
|
|
|
$.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( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){
|
|
return;
|
|
}
|
|
$( "." + $.mobile.activePageClass ).css( "min-height", getScreenHeight() );
|
|
}
|
|
|
|
//shared page enhancements
|
|
function enhancePage( $page, role ) {
|
|
// If a role was specified, make sure the data-role attribute
|
|
// on the page element is in sync.
|
|
if( role ) {
|
|
$page.attr( "data-" + $.mobile.ns + "role", role );
|
|
}
|
|
|
|
//run page plugin
|
|
$page.page();
|
|
}
|
|
|
|
/* exposed $.mobile methods */
|
|
|
|
//animation complete callback
|
|
$.fn.animationComplete = function( callback ) {
|
|
if( $.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
|
|
$.mobile.path = path;
|
|
|
|
//expose base object on $.mobile
|
|
$.mobile.base = base;
|
|
|
|
//history stack
|
|
$.mobile.urlHistory = urlHistory;
|
|
|
|
$.mobile.dialogHashKey = dialogHashKey;
|
|
|
|
//default non-animation transition handler
|
|
$.mobile.noneTransitionHandler = function( name, reverse, $toPage, $fromPage ) {
|
|
if ( $fromPage ) {
|
|
$fromPage.removeClass( $.mobile.activePageClass );
|
|
}
|
|
$toPage.addClass( $.mobile.activePageClass );
|
|
|
|
return $.Deferred().resolve( name, reverse, $toPage, $fromPage ).promise();
|
|
};
|
|
|
|
//default handler for unknown transitions
|
|
$.mobile.defaultTransitionHandler = $.mobile.noneTransitionHandler;
|
|
|
|
//transition handler dictionary for 3rd party transitions
|
|
$.mobile.transitionHandlers = {
|
|
none: $.mobile.defaultTransitionHandler
|
|
};
|
|
|
|
//enable cross-domain page support
|
|
$.mobile.allowCrossDomainPages = false;
|
|
|
|
//return the original document url
|
|
$.mobile.getDocumentUrl = function(asParsedObject) {
|
|
return asParsedObject ? $.extend( {}, documentUrl ) : documentUrl.href;
|
|
};
|
|
|
|
//return the original document base url
|
|
$.mobile.getDocumentBase = function(asParsedObject) {
|
|
return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href;
|
|
};
|
|
|
|
$.mobile._bindPageRemove = function() {
|
|
var page = $(this);
|
|
|
|
// when dom caching is not enabled or the page is embedded bind to remove the page on hide
|
|
if( !page.data("page").options.domCache
|
|
&& page.is(":jqmData(external-page='true')") ) {
|
|
|
|
page.bind( 'pagehide.remove', function() {
|
|
var $this = $( this ),
|
|
prEvent = new $.Event( "pageremove" );
|
|
|
|
$this.trigger( prEvent );
|
|
|
|
if( !prEvent.isDefaultPrevented() ){
|
|
$this.removeWithDependents();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Load a page into the DOM.
|
|
$.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 = $.Deferred(),
|
|
|
|
// The default loadPage options with overrides specified by
|
|
// the caller.
|
|
settings = $.extend( {}, $.mobile.loadPage.defaults, options ),
|
|
|
|
// The DOM element for the page after it has been loaded.
|
|
page = null,
|
|
|
|
// If the reloadPage option is true, and the page is already
|
|
// in the DOM, dupCachedPage will be set to the page element
|
|
// so that it can be removed after the new version of the
|
|
// page is loaded off the network.
|
|
dupCachedPage = null,
|
|
|
|
// determine the current base url
|
|
findBaseWithDefault = function(){
|
|
var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
|
|
return closestBase || documentBase.hrefNoHash;
|
|
},
|
|
|
|
// The absolute version of the URL passed into the function. This
|
|
// version of the URL may contain dialog/subpage params in it.
|
|
absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() );
|
|
|
|
|
|
// If the caller provided data, and we're using "get" request,
|
|
// append the data to the URL.
|
|
if ( settings.data && settings.type === "get" ) {
|
|
absUrl = path.addSearchParams( absUrl, settings.data );
|
|
settings.data = undefined;
|
|
}
|
|
|
|
// If the caller is using a "post" request, reloadPage must be true
|
|
if( settings.data && settings.type === "post" ){
|
|
settings.reloadPage = true;
|
|
}
|
|
|
|
// The absolute version of the URL minus any dialog/subpage params.
|
|
// In otherwords the real URL of the page to be loaded.
|
|
var fileUrl = path.getFilePath( absUrl ),
|
|
|
|
// The version of the Url actually stored in the data-url attribute of
|
|
// the page. For embedded pages, it is just the id of the page. For pages
|
|
// within the same domain as the document base, it is the site relative
|
|
// path. For cross-domain pages (Phone Gap only) the entire absolute Url
|
|
// used to load the page.
|
|
dataUrl = path.convertUrlToDataUrl( absUrl );
|
|
|
|
// Make sure we have a pageContainer to work with.
|
|
settings.pageContainer = settings.pageContainer || $.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.ns + "url", dataUrl )
|
|
}
|
|
|
|
// If we failed to find a page in the DOM, check the URL to see if it
|
|
// refers to the first page in the application.
|
|
if ( page.length === 0 && $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) {
|
|
page = $( $.mobile.firstPage );
|
|
}
|
|
|
|
// 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 $.Event( "pagebeforeload" ),
|
|
triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
|
|
|
|
// Let listeners know we're about to load a page.
|
|
mpc.trigger( pblEvent, triggerData );
|
|
|
|
// If the default behavior is prevented, stop here!
|
|
if( pblEvent.isDefaultPrevented() ){
|
|
return deferred.promise();
|
|
}
|
|
|
|
if ( settings.showLoadMsg ) {
|
|
|
|
// This configurable timeout allows cached pages a brief delay to load without showing a message
|
|
var loadMsgDelay = setTimeout(function(){
|
|
$.mobile.showPageLoadingMsg();
|
|
}, settings.loadMsgDelay ),
|
|
|
|
// Shared logic for clearing timeout and removing message.
|
|
hideMsg = function(){
|
|
|
|
// Stop message show timer
|
|
clearTimeout( loadMsgDelay );
|
|
|
|
// Hide loading message
|
|
$.mobile.hidePageLoadingMsg();
|
|
};
|
|
}
|
|
|
|
if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) {
|
|
deferred.reject( absUrl, options );
|
|
} else {
|
|
// Load the new page.
|
|
$.ajax({
|
|
url: fileUrl,
|
|
type: settings.type,
|
|
data: settings.data,
|
|
dataType: "html",
|
|
success: function( html ) {
|
|
//pre-parse html to check for a data-url,
|
|
//use it as the new fileUrl, base path, etc
|
|
var all = $( "<div></div>" ),
|
|
|
|
//page title regexp
|
|
newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,
|
|
|
|
// TODO handle dialogs again
|
|
pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
|
|
dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );
|
|
|
|
|
|
// data-url must be provided for the base tag so resource requests can be directed to the
|
|
// correct url. loading into a temprorary element makes these requests immediately
|
|
if( pageElemRegex.test( html )
|
|
&& RegExp.$1
|
|
&& dataUrlRegex.test( RegExp.$1 )
|
|
&& RegExp.$1 ) {
|
|
url = fileUrl = path.getFilePath( RegExp.$1 );
|
|
}
|
|
|
|
if ( base ) {
|
|
base.set( fileUrl );
|
|
}
|
|
|
|
//workaround to allow scripts to execute when included in page divs
|
|
all.get( 0 ).innerHTML = html;
|
|
page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
|
|
|
|
//if page elem couldn't be found, create one and insert the body element's contents
|
|
if( !page.length ){
|
|
page = $( "<div data-" + $.mobile.ns + "role='page'>" + html.split( /<\/?body[^>]*>/gmi )[1] + "</div>" );
|
|
}
|
|
|
|
if ( newPageTitle && !page.jqmData( "title" ) ) {
|
|
page.jqmData( "title", newPageTitle );
|
|
}
|
|
|
|
//rewrite src and href attrs to use a base url
|
|
if( !$.support.dynamicBaseTag ) {
|
|
var newPath = path.get( fileUrl );
|
|
page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
|
|
var thisAttr = $( this ).is( '[href]' ) ? 'href' :
|
|
$(this).is('[src]') ? 'src' : 'action',
|
|
thisUrl = $( this ).attr( thisAttr );
|
|
|
|
// XXX_jblas: We need to fix this so that it removes the document
|
|
// base URL, and then prepends with the new page URL.
|
|
//if full path exists and is same, chop it - helps IE out
|
|
thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );
|
|
|
|
if( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
|
|
$( this ).attr( thisAttr, newPath + thisUrl );
|
|
}
|
|
});
|
|
}
|
|
|
|
//append to page and enhance
|
|
// TODO taging a page with external to make sure that embedded pages aren't removed
|
|
// by the various page handling code is bad. Having page handling code in many
|
|
// places is bad. Solutions post 1.0
|
|
page
|
|
.attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
|
|
.attr( "data-" + $.mobile.ns + "external-page", true )
|
|
.appendTo( settings.pageContainer );
|
|
|
|
// wait for page creation to leverage options defined on widget
|
|
page.one( 'pagecreate', $.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( "&" + $.mobile.subPageUrlKey ) > -1 ) {
|
|
page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
|
|
}
|
|
|
|
//bind pageHide to removePage after it's hidden, if the page options specify to do so
|
|
|
|
// Remove loading message.
|
|
if ( settings.showLoadMsg ) {
|
|
hideMsg();
|
|
}
|
|
|
|
// Add the page reference to our triggerData.
|
|
triggerData.page = page;
|
|
|
|
// Let listeners know the page loaded successfully.
|
|
settings.pageContainer.trigger( "pageload", triggerData );
|
|
|
|
deferred.resolve( absUrl, options, page, dupCachedPage );
|
|
},
|
|
error: function() {
|
|
//set base back to current path
|
|
if( base ) {
|
|
base.set( path.get() );
|
|
}
|
|
|
|
var plfEvent = new $.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>"+ $.mobile.pageLoadErrorMessage +"</h1></div>" )
|
|
.css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
|
|
.appendTo( settings.pageContainer )
|
|
.delay( 800 )
|
|
.fadeOut( 400, function() {
|
|
$( this ).remove();
|
|
});
|
|
}
|
|
|
|
deferred.reject( absUrl, options );
|
|
}
|
|
});
|
|
}
|
|
|
|
return deferred.promise();
|
|
};
|
|
|
|
$.mobile.loadPage.defaults = {
|
|
type: "get",
|
|
data: undefined,
|
|
reloadPage: false,
|
|
role: undefined, // By default we rely on the role defined by the @data-role attribute.
|
|
showLoadMsg: false,
|
|
pageContainer: undefined,
|
|
loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message.
|
|
};
|
|
|
|
// Show a specific page in the page container.
|
|
$.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 = $.extend( {}, $.mobile.changePage.defaults, options );
|
|
|
|
// Make sure we have a pageContainer to work with.
|
|
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
|
|
|
|
// Make sure we have a fromPage.
|
|
settings.fromPage = settings.fromPage || $.mobile.activePage;
|
|
|
|
var mpc = settings.pageContainer,
|
|
pbcEvent = new $.Event( "pagebeforechange" ),
|
|
triggerData = { toPage: toPage, options: settings };
|
|
|
|
// Let listeners know we're about to change the current page.
|
|
mpc.trigger( pbcEvent, triggerData );
|
|
|
|
// If the default behavior is prevented, stop here!
|
|
if( pbcEvent.isDefaultPrevented() ){
|
|
return;
|
|
}
|
|
|
|
// We allow "pagebeforechange" observers to modify the toPage in the trigger
|
|
// data to allow for redirects. Make sure our toPage is updated.
|
|
|
|
toPage = triggerData.toPage;
|
|
|
|
// Set the isPageTransitioning flag to prevent any requests from
|
|
// entering this method while we are in the midst of loading a page
|
|
// or transitioning.
|
|
|
|
isPageTransitioning = true;
|
|
|
|
// If the caller passed us a url, call loadPage()
|
|
// to make sure it is loaded into the DOM. We'll listen
|
|
// to the promise object it returns so we know when
|
|
// it is done loading or if an error ocurred.
|
|
if ( typeof toPage == "string" ) {
|
|
$.mobile.loadPage( toPage, settings )
|
|
.done(function( url, options, newPage, dupCachedPage ) {
|
|
isPageTransitioning = false;
|
|
options.duplicateCachedPage = dupCachedPage;
|
|
$.mobile.changePage( newPage, options );
|
|
})
|
|
.fail(function( url, options ) {
|
|
isPageTransitioning = false;
|
|
|
|
//clear out the active button state
|
|
removeActiveLinkClass( true );
|
|
|
|
//release transition lock so navigation is free again
|
|
releasePageTransitionLock();
|
|
settings.pageContainer.trigger( "pagechangefailed", triggerData );
|
|
});
|
|
return;
|
|
}
|
|
|
|
// If we are going to the first-page of the application, we need to make
|
|
// sure settings.dataUrl is set to the application document url. This allows
|
|
// us to avoid generating a document url with an id hash in the case where the
|
|
// first-page of the document has an id attribute specified.
|
|
if ( toPage[ 0 ] === $.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 {
|
|
$( document.activeElement || "" ).add( "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
|
|
var newPageTitle = toPage.jqmData( "title" ) || toPage.children(":jqmData(role='header')").find(".ui-title" ).getEncodedText();
|
|
if( !!newPageTitle && pageTitle == document.title ) {
|
|
pageTitle = newPageTitle;
|
|
}
|
|
|
|
// Make sure we have a transition defined.
|
|
settings.transition = settings.transition
|
|
|| ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined )
|
|
|| ( isDialog ? $.mobile.defaultDialogTransition : $.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
|
|
$.mobile.activePage = toPage;
|
|
|
|
// If we're navigating back in the URL history, set reverse accordingly.
|
|
settings.reverse = settings.reverse || historyDir < 0;
|
|
|
|
transitionPages( toPage, fromPage, settings.transition, settings.reverse )
|
|
.done(function() {
|
|
removeActiveLinkClass();
|
|
|
|
//if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
|
|
if ( settings.duplicateCachedPage ) {
|
|
settings.duplicateCachedPage.remove();
|
|
}
|
|
|
|
//remove initial build class (only present on first pageshow)
|
|
$html.removeClass( "ui-mobile-rendering" );
|
|
|
|
releasePageTransitionLock();
|
|
|
|
// Let listeners know we're all done changing the current page.
|
|
mpc.trigger( "pagechange", triggerData );
|
|
});
|
|
};
|
|
|
|
$.mobile.changePage.defaults = {
|
|
transition: undefined,
|
|
reverse: false,
|
|
changeHash: true,
|
|
fromHashChange: false,
|
|
role: undefined, // By default we rely on the role defined by the @data-role attribute.
|
|
duplicateCachedPage: undefined,
|
|
pageContainer: undefined,
|
|
showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
|
|
dataUrl: undefined,
|
|
fromPage: undefined,
|
|
allowSamePageTransition: false
|
|
};
|
|
|
|
/* Event Bindings - hashchange, submit, and click */
|
|
function findClosestLink( ele )
|
|
{
|
|
while ( ele ) {
|
|
if ( ele.nodeName.toLowerCase() == "a" ) {
|
|
break;
|
|
}
|
|
ele = ele.parentNode;
|
|
}
|
|
return ele;
|
|
}
|
|
|
|
// The base URL for any given element depends on the page it resides in.
|
|
function getClosestBaseUrl( ele )
|
|
{
|
|
// Find the closest page and extract out its url.
|
|
var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ),
|
|
base = documentBase.hrefNoHash;
|
|
|
|
if ( !url || !path.isPath( url ) ) {
|
|
url = base;
|
|
}
|
|
|
|
return path.makeUrlAbsolute( url, base);
|
|
}
|
|
|
|
|
|
//The following event bindings should be bound after mobileinit has been triggered
|
|
//the following function is called in the init file
|
|
$.mobile._registerInternalEvents = function(){
|
|
|
|
//bind to form submit events, handle with Ajax
|
|
$( "form" ).live('submit', function( event ) {
|
|
var $this = $( this );
|
|
if( !$.mobile.ajaxEnabled ||
|
|
$this.is( ":jqmData(ajax='false')" ) ) {
|
|
return;
|
|
}
|
|
|
|
var type = $this.attr( "method" ),
|
|
target = $this.attr( "target" ),
|
|
url = $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 );
|
|
if ( url === documentBase.hrefNoHash ) {
|
|
// The url we got back matches the document base,
|
|
// which means the page must be an internal/embedded page,
|
|
// so default to using the actual document url as a browser
|
|
// would.
|
|
url = documentUrl.hrefNoSearch;
|
|
}
|
|
}
|
|
|
|
url = path.makeUrlAbsolute( url, getClosestBaseUrl($this) );
|
|
|
|
//external submits use regular HTTP
|
|
if( path.isExternal( url ) || target ) {
|
|
return;
|
|
}
|
|
|
|
$.mobile.changePage(
|
|
url,
|
|
{
|
|
type: type && type.length && type.toLowerCase() || "get",
|
|
data: $this.serialize(),
|
|
transition: $this.jqmData( "transition" ),
|
|
direction: $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 ){
|
|
return;
|
|
}
|
|
|
|
var link = findClosestLink( event.target );
|
|
if ( link ) {
|
|
if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) {
|
|
removeActiveLinkClass( true );
|
|
$activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" );
|
|
$activeClickedLink.addClass( $.mobile.activeBtnClass );
|
|
$( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur();
|
|
}
|
|
}
|
|
});
|
|
|
|
// click routing - direct to HTTP or Ajax, accordingly
|
|
$( document ).bind( "click", function( event ) {
|
|
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 = $( link ),
|
|
//remove active link class if external (then it won't be there if you come back)
|
|
httpCleanup = function(){
|
|
window.setTimeout( function() { removeActiveLinkClass( true ); }, 200 );
|
|
};
|
|
|
|
//if there's a data-rel=back attr, go back in history
|
|
if( $link.is( ":jqmData(rel='back')" ) ) {
|
|
window.history.back();
|
|
return false;
|
|
}
|
|
|
|
var baseUrl = getClosestBaseUrl( $link ),
|
|
|
|
//get href, if defined, otherwise default to empty hash
|
|
href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
|
|
|
|
//if ajax is disabled, exit early
|
|
if( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ){
|
|
httpCleanup();
|
|
//use default click handling
|
|
return;
|
|
}
|
|
|
|
// XXX_jblas: Ideally links to application pages should be specified as
|
|
// an url to the application document with a hash that is either
|
|
// the site relative path or id to the page. But some of the
|
|
// internal code that dynamically generates sub-pages for nested
|
|
// lists and select dialogs, just write a hash in the link they
|
|
// create. This means the actual URL path is based on whatever
|
|
// the current value of the base tag is at the time this code
|
|
// is called. For now we are just assuming that any url with a
|
|
// hash in it is an application page reference.
|
|
if ( href.search( "#" ) != -1 ) {
|
|
href = href.replace( /[^#]*#/, "" );
|
|
if ( !href ) {
|
|
//link was an empty hash meant purely
|
|
//for interaction, so we ignore it.
|
|
event.preventDefault();
|
|
return;
|
|
} else if ( path.isPath( href ) ) {
|
|
//we have apath so make it the href we want to load.
|
|
href = path.makeUrlAbsolute( href, baseUrl );
|
|
} else {
|
|
//we have a simple id so use the documentUrl as its base.
|
|
href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
|
|
}
|
|
}
|
|
|
|
// Should we handle this link, or let the browser deal with it?
|
|
var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $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 = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search( /^https?:/ ) != -1 ),
|
|
|
|
//check for protocol or rel and its not an embedded page
|
|
//TODO overlap in logic from isExternal, rel=external check should be
|
|
// moved into more comprehensive isExternalLink
|
|
isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad );
|
|
|
|
if( isExternal ) {
|
|
httpCleanup();
|
|
//use default click handling
|
|
return;
|
|
}
|
|
|
|
//use ajax
|
|
var transition = $link.jqmData( "transition" ),
|
|
direction = $link.jqmData( "direction" ),
|
|
reverse = ( direction && direction === "reverse" ) ||
|
|
// deprecated - remove by 1.0
|
|
$link.jqmData( "back" ),
|
|
|
|
//this may need to be more specific as we use data-rel more
|
|
role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
|
|
|
|
$.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } );
|
|
event.preventDefault();
|
|
});
|
|
|
|
//prefetch pages when anchors with data-prefetch are encountered
|
|
$( ".ui-page" ).live( "pageshow.prefetch", function() {
|
|
var urls = [];
|
|
$( this ).find( "a:jqmData(prefetch)" ).each(function(){
|
|
var $link = $(this),
|
|
url = $link.attr( "href" );
|
|
|
|
if ( url && $.inArray( url, urls ) === -1 ) {
|
|
urls.push( url );
|
|
|
|
$.mobile.loadPage( url, {role: $link.attr("data-" + $.mobile.ns + "rel")} );
|
|
}
|
|
});
|
|
});
|
|
|
|
$.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.urlHistory.stack.length === 0 ? "none" : undefined,
|
|
|
|
// default options for the changPage calls made after examining the current state
|
|
// of the page and the hash
|
|
changePageOptions = {
|
|
transition: transition,
|
|
changeHash: false,
|
|
fromHashChange: true
|
|
};
|
|
|
|
//if listening is disabled (either globally or temporarily), or it's a dialog hash
|
|
if( !$.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.activePage.is( ".ui-dialog" )) {
|
|
//determine if we're heading forward or backward and continue accordingly past
|
|
//the current dialog
|
|
urlHistory.directHashChange({
|
|
currentUrl: to,
|
|
isBack: function() { window.history.back(); },
|
|
isForward: function() { window.history.forward(); }
|
|
});
|
|
|
|
// prevent changePage()
|
|
return;
|
|
} else {
|
|
// if the current active page is a dialog and we're navigating
|
|
// to a dialog use the dialog objected saved in the stack
|
|
urlHistory.directHashChange({
|
|
currentUrl: to,
|
|
|
|
// regardless of the direction of the history change
|
|
// do the following
|
|
either: function( isBack ) {
|
|
var active = $.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
|
|
$.extend( changePageOptions, {
|
|
role: active.role,
|
|
transition: active.transition,
|
|
reverse: isBack
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
//if to is defined, load it
|
|
if ( to ) {
|
|
// At this point, 'to' can be one of 3 things, a cached page element from
|
|
// a history stack entry, an id, or site-relative/absolute URL. If 'to' is
|
|
// an id, we need to resolve it against the documentBase, not the location.href,
|
|
// since the hashchange could've been the result of a forward/backward navigation
|
|
// that crosses from an external page/dialog to an internal page/dialog.
|
|
to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to;
|
|
$.mobile.changePage( to, changePageOptions );
|
|
} else {
|
|
//there's no hash, go to the first page in the dom
|
|
$.mobile.changePage( $.mobile.firstPage, changePageOptions );
|
|
}
|
|
};
|
|
|
|
//hashchange event handler
|
|
$window.bind( "hashchange", function( e, triggered ) {
|
|
$.mobile._handleHashChange( location.hash );
|
|
});
|
|
|
|
//set page min-heights to be device specific
|
|
$( document ).bind( "pageshow", resetActivePageHeight );
|
|
$( window ).bind( "throttledresize", resetActivePageHeight );
|
|
|
|
};//_registerInternalEvents callback
|
|
|
|
})( jQuery );
|