2 * core utilities for auto ajax navigation, base tag mgmt,
5 ( function( $, undefined ) {
7 //define vars for interal use
8 var $window = $( window ),
12 //url path helpers for use in relative url management
15 // This scary looking regular expression parses an absolute URL or its relative
16 // variants (protocol, site, document, query, and hash), into the various
17 // components (protocol, host, path, query, fragment, etc that make up the
18 // URL as well as some other commonly used sub-parts. When used with RegExp.exec()
19 // or String.match, it parses the URL into a results array that looks like this:
21 // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
22 // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
23 // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
24 // [3]: http://jblas:password@mycompany.com:8080
27 // [6]: jblas:password@mycompany.com:8080
28 // [7]: jblas:password
31 // [10]: mycompany.com:8080
32 // [11]: mycompany.com
37 // [16]: ?msg=1234&type=unread
40 urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
42 //Parse a URL into a structure that allows easy access to
43 //all of the URL components by name.
44 parseUrl: function( url ) {
45 // If we're passed an object, we'll assume that it is
46 // a parsed url object and just return it back to the caller.
47 if ( $.type( url ) === "object" ) {
51 var matches = path.urlParseRE.exec( url || "" ) || [];
53 // Create an object that allows the caller to access the sub-matches
54 // by name. Note that IE returns an empty string instead of undefined,
55 // like all other browsers do, so we normalize everything so its consistent
56 // no matter what browser we're running on.
58 href: matches[ 0 ] || "",
59 hrefNoHash: matches[ 1 ] || "",
60 hrefNoSearch: matches[ 2 ] || "",
61 domain: matches[ 3 ] || "",
62 protocol: matches[ 4 ] || "",
63 doubleSlash: matches[ 5 ] || "",
64 authority: matches[ 6 ] || "",
65 username: matches[ 8 ] || "",
66 password: matches[ 9 ] || "",
67 host: matches[ 10 ] || "",
68 hostname: matches[ 11 ] || "",
69 port: matches[ 12 ] || "",
70 pathname: matches[ 13 ] || "",
71 directory: matches[ 14 ] || "",
72 filename: matches[ 15 ] || "",
73 search: matches[ 16 ] || "",
74 hash: matches[ 17 ] || ""
78 //Turn relPath into an asbolute path. absPath is
79 //an optional absolute path which describes what
80 //relPath is relative to.
81 makePathAbsolute: function( relPath, absPath ) {
82 if ( relPath && relPath.charAt( 0 ) === "/" ) {
86 relPath = relPath || "";
87 absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
89 var absStack = absPath ? absPath.split( "/" ) : [],
90 relStack = relPath.split( "/" );
91 for ( var i = 0; i < relStack.length; i++ ) {
92 var d = relStack[ i ];
97 if ( absStack.length ) {
106 return "/" + absStack.join( "/" );
109 //Returns true if both urls have the same domain.
110 isSameDomain: function( absUrl1, absUrl2 ) {
111 return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
114 //Returns true for any relative variant.
115 isRelativeUrl: function( url ) {
116 // All relative Url variants have one thing in common, no protocol.
117 return path.parseUrl( url ).protocol === "";
120 //Returns true for an absolute url.
121 isAbsoluteUrl: function( url ) {
122 return path.parseUrl( url ).protocol !== "";
125 //Turn the specified realtive URL into an absolute one. This function
126 //can handle all relative variants (protocol, site, document, query, fragment).
127 makeUrlAbsolute: function( relUrl, absUrl ) {
128 if ( !path.isRelativeUrl( relUrl ) ) {
132 var relObj = path.parseUrl( relUrl ),
133 absObj = path.parseUrl( absUrl ),
134 protocol = relObj.protocol || absObj.protocol,
135 doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
136 authority = relObj.authority || absObj.authority,
137 hasPath = relObj.pathname !== "",
138 pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
139 search = relObj.search || ( !hasPath && absObj.search ) || "",
142 return protocol + doubleSlash + authority + pathname + search + hash;
145 //Add search (aka query) params to the specified url.
146 addSearchParams: function( url, params ) {
147 var u = path.parseUrl( url ),
148 p = ( typeof params === "object" ) ? $.param( params ) : params,
150 return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
153 convertUrlToDataUrl: function( absUrl ) {
154 var u = path.parseUrl( absUrl );
155 if ( path.isEmbeddedPage( u ) ) {
156 // For embedded pages, remove the dialog hash key as in getFilePath(),
157 // otherwise the Data Url won't match the id of the embedded Page.
158 return u.hash.split( dialogHashKey )[0].replace( /^#/, "" );
159 } else if ( path.isSameDomain( u, documentBase ) ) {
160 return u.hrefNoHash.replace( documentBase.domain, "" );
165 //get path from current hash, or from a file path
166 get: function( newPath ) {
167 if( newPath === undefined ) {
168 newPath = location.hash;
170 return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
173 //return the substring of a filepath before the sub-page key, for making a server request
174 getFilePath: function( path ) {
175 var splitkey = '&' + $.mobile.subPageUrlKey;
176 return path && path.split( splitkey )[0].split( dialogHashKey )[0];
179 //set location hash to path
180 set: function( path ) {
181 location.hash = path;
184 //test if a given url (string) is a path
185 //NOTE might be exceptionally naive
186 isPath: function( url ) {
187 return ( /\// ).test( url );
190 //return a url path with the window's location protocol/hostname/pathname removed
191 clean: function( url ) {
192 return url.replace( documentBase.domain, "" );
195 //just return the url without an initial #
196 stripHash: function( url ) {
197 return url.replace( /^#/, "" );
200 //remove the preceding hash, any query params, and dialog notations
201 cleanHash: function( hash ) {
202 return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
205 //check whether a url is referencing the same domain, or an external domain or different protocol
206 //could be mailto, etc
207 isExternal: function( url ) {
208 var u = path.parseUrl( url );
209 return u.protocol && u.domain !== documentUrl.domain ? true : false;
212 hasProtocol: function( url ) {
213 return ( /^(:?\w+:)/ ).test( url );
216 //check if the specified url refers to the first page in the main application document.
217 isFirstPageUrl: function( url ) {
218 // We only deal with absolute paths.
219 var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ),
221 // Does the url have the same path as the document?
222 samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ),
224 // Get the first page element.
225 fp = $.mobile.firstPage,
227 // Get the id of the first page element if it has one.
228 fpId = fp && fp[0] ? fp[0].id : undefined;
230 // The url refers to the first page if the path matches the document and
231 // it either has no hash value, or the hash is exactly equal to the id of the
232 // first page element.
233 return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
236 isEmbeddedPage: function( url ) {
237 var u = path.parseUrl( url );
239 //if the path is absolute, then we need to compare the url against
240 //both the documentUrl and the documentBase. The main reason for this
241 //is that links embedded within external documents will refer to the
242 //application document, whereas links embedded within the application
243 //document will be resolved against the document base.
244 if ( u.protocol !== "" ) {
245 return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) );
247 return (/^#/).test( u.href );
251 //will be defined when a link is clicked and given an active class
252 $activeClickedLink = null,
254 //urlHistory is purely here to make guesses at whether the back or forward button was clicked
255 //and provide an appropriate transition
257 // Array of pages that are visited during a single page load.
258 // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs)
261 //maintain an index number for the active page in the stack
265 getActive: function() {
266 return urlHistory.stack[ urlHistory.activeIndex ];
269 getPrev: function() {
270 return urlHistory.stack[ urlHistory.activeIndex - 1 ];
273 getNext: function() {
274 return urlHistory.stack[ urlHistory.activeIndex + 1 ];
277 // addNew is used whenever a new page is added
278 addNew: function( url, transition, title, pageUrl, role ) {
279 //if there's forward history, wipe it
280 if( urlHistory.getNext() ) {
281 urlHistory.clearForward();
284 urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl, role: role } );
286 urlHistory.activeIndex = urlHistory.stack.length - 1;
289 //wipe urls ahead of active index
290 clearForward: function() {
291 urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 );
294 directHashChange: function( opts ) {
295 var back , forward, newActiveIndex, prev = this.getActive();
297 // check if url isp in history and if it's ahead or behind current page
298 $.each( urlHistory.stack, function( i, historyEntry ) {
300 //if the url is in the stack, it's a forward or a back
301 if( opts.currentUrl === historyEntry.url ) {
302 //define back and forward by whether url is older or newer than current page
303 back = i < urlHistory.activeIndex;
309 // save new page index, null check to prevent falsey 0 result
310 this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
313 ( opts.either || opts.isBack )( true );
314 } else if( forward ) {
315 ( opts.either || opts.isForward )( false );
319 //disable hashchange event listener internally to ignore one change
320 //toggled internally when location.hash is updated to match the url of a successful page load
321 ignoreNextHashChange: false
324 //define first selector to receive focus when a page is shown
325 focusable = "[tabindex],a,button:visible,select:visible,input",
327 //queue to hold simultanious page transitions
328 pageTransitionQueue = [],
330 //indicates whether or not page is in process of transitioning
331 isPageTransitioning = false,
333 //nonsense hash change key for dialogs, so they create a history entry
334 dialogHashKey = "&ui-state=dialog",
337 $base = $head.children( "base" ),
339 //tuck away the original document URL minus any fragment.
340 documentUrl = path.parseUrl( location.href ),
342 //if the document has an embedded base tag, documentBase is set to its
343 //initial value. If a base tag does not exist, then we default to the documentUrl.
344 documentBase = $base.length ? path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), documentUrl.href ) ) : documentUrl,
346 //cache the comparison once.
347 documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash );
349 //base element management, defined depending on dynamic base tag support
350 var base = $.support.dynamicBaseTag ? {
352 //define base element, for use in routing asset urls that are referenced in Ajax-requested markup
353 element: ( $base.length ? $base : $( "<base>", { href: documentBase.hrefNoHash } ).prependTo( $head ) ),
355 //set the generated BASE element's href attribute to a new page's base path
356 set: function( href ) {
357 base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) );
360 //set the generated BASE element's href attribute to a new page's base path
362 base.element.attr( "href", documentBase.hrefNoHash );
368 internal utility functions
369 --------------------------------------*/
372 //direct focus to the page title, or otherwise first focusable element
373 function reFocus( page ) {
374 var pageTitle = page.find( ".ui-title:eq(0)" );
376 if( pageTitle.length ) {
384 //remove active classes after page transition or error
385 function removeActiveLinkClass( forceRemoval ) {
386 if( !!$activeClickedLink && ( !$activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval ) ) {
387 $activeClickedLink.removeClass( $.mobile.activeBtnClass );
389 $activeClickedLink = null;
392 function releasePageTransitionLock() {
393 isPageTransitioning = false;
394 if( pageTransitionQueue.length > 0 ) {
395 $.mobile.changePage.apply( null, pageTransitionQueue.pop() );
399 // Save the last scroll distance per page, before it is hidden
400 var setLastScrollEnabled = true,
401 firstScrollElem, getScrollElem, setLastScroll, delayedSetLastScroll;
403 getScrollElem = function() {
404 var scrollElem = $window, activePage,
405 touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled;
408 activePage = $( ".ui-page-active" );
409 scrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage;
415 setLastScroll = function( scrollElem ) {
416 // this barrier prevents setting the scroll value based on the browser
417 // scrolling the window based on a hashchange
418 if( !setLastScrollEnabled ) {
422 var active = $.mobile.urlHistory.getActive();
425 var lastScroll = scrollElem && scrollElem.scrollTop();
427 // Set active page's lastScroll prop.
428 // If the location we're scrolling to is less than minScrollBack, let it go.
429 active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll;
433 // bind to scrollstop to gather scroll position. The delay allows for the hashchange
434 // event to fire and disable scroll recording in the case where the browser scrolls
435 // to the hash targets location (sometimes the top of the page). once pagechange fires
436 // getLastScroll is again permitted to operate
437 delayedSetLastScroll = function() {
438 setTimeout( setLastScroll, 100, $(this) );
441 // disable an scroll setting when a hashchange has been fired, this only works
442 // because the recording of the scroll position is delayed for 100ms after
443 // the browser might have changed the position because of the hashchange
444 $window.bind( $.support.pushState ? "popstate" : "hashchange", function() {
445 setLastScrollEnabled = false;
448 // handle initial hashchange from chrome :(
449 $window.one( $.support.pushState ? "popstate" : "hashchange", function() {
450 setLastScrollEnabled = true;
453 // wait until the mobile page container has been determined to bind to pagechange
454 $window.one( "pagecontainercreate", function(){
455 // once the page has changed, re-enable the scroll recording
456 $.mobile.pageContainer.bind( "pagechange", function() {
457 var scrollElem = getScrollElem();
459 setLastScrollEnabled = true;
461 // remove any binding that previously existed on the get scroll
462 // which may or may not be different than the scroll element determined for
463 // this page previously
464 scrollElem.unbind( "scrollstop", delayedSetLastScroll );
466 // determine and bind to the current scoll element which may be the window
467 // or in the case of touch overflow the element with touch overflow
468 scrollElem.bind( "scrollstop", delayedSetLastScroll );
472 // bind to scrollstop for the first page as "pagechange" won't be fired in that case
473 getScrollElem().bind( "scrollstop", delayedSetLastScroll );
475 // Make the iOS clock quick-scroll work again if we're using native overflow scrolling
477 if( $.support.touchOverflow ){
478 if( $.mobile.touchOverflowEnabled ){
479 $( window ).bind( "scrollstop", function(){
480 if( $( this ).scrollTop() === 0 ){
481 $.mobile.activePage.scrollTop( 0 );
488 //function for transitioning between two existing pages
489 function transitionPages( toPage, fromPage, transition, reverse ) {
491 //get current scroll distance
492 var active = $.mobile.urlHistory.getActive(),
493 touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled,
494 toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ),
495 screenHeight = getScreenHeight();
497 // Scroll to top, hide addr bar
498 window.scrollTo( 0, $.mobile.defaultHomeScroll );
501 //trigger before show/hide events
502 fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } );
506 toPage.height( screenHeight + toScroll );
509 toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } );
512 $.mobile.hidePageLoadingMsg();
514 if( touchOverflow && toScroll ){
516 toPage.addClass( "ui-mobile-pre-transition" );
517 // Send focus to page as it is now display: block
520 //set page's scrollTop to remembered distance
521 if( toPage.is( ".ui-native-fixed" ) ){
522 toPage.find( ".ui-content" ).scrollTop( toScroll );
525 toPage.scrollTop( toScroll );
529 //find the transition handler for the specified transition. If there
530 //isn't one in our transitionHandlers dictionary, use the default one.
531 //call the handler immediately to kick-off the transition.
532 var th = $.mobile.transitionHandlers[transition || "none"] || $.mobile.defaultTransitionHandler,
533 promise = th( transition, reverse, toPage, fromPage );
535 promise.done(function() {
536 //reset toPage height back
537 if( !touchOverflow ){
539 // Send focus to the newly shown page
543 // Jump to top or prev scroll, sometimes on iOS the page has not rendered yet.
544 if( !touchOverflow ){
545 $.mobile.silentScroll( toScroll );
548 //trigger show/hide events
550 if( !touchOverflow ){
551 fromPage.height( "" );
554 fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } );
557 //trigger pageshow, define prevPage as either fromPage or empty jQuery obj
558 toPage.data( "page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } );
564 //simply set the active page's minimum height to screen height, depending on orientation
565 function getScreenHeight(){
566 var orientation = $.event.special.orientationchange.orientation(),
567 port = orientation === "portrait",
568 winMin = port ? 480 : 320,
569 screenHeight = port ? screen.availHeight : screen.availWidth,
570 winHeight = Math.max( winMin, $( window ).height() ),
571 pageMin = Math.min( screenHeight, winHeight );
576 $.mobile.getScreenHeight = getScreenHeight;
578 //simply set the active page's minimum height to screen height, depending on orientation
579 function resetActivePageHeight(){
580 // Don't apply this height in touch overflow enabled mode
581 if( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){
584 $( "." + $.mobile.activePageClass ).css( "min-height", getScreenHeight() );
587 //shared page enhancements
588 function enhancePage( $page, role ) {
589 // If a role was specified, make sure the data-role attribute
590 // on the page element is in sync.
592 $page.attr( "data-" + $.mobile.ns + "role", role );
599 /* exposed $.mobile methods */
601 //animation complete callback
602 $.fn.animationComplete = function( callback ) {
603 if( $.support.cssTransitions ) {
604 return $( this ).one( 'webkitAnimationEnd', callback );
607 // defer execution for consistency between webkit/non webkit
608 setTimeout( callback, 0 );
613 //expose path object on $.mobile
614 $.mobile.path = path;
616 //expose base object on $.mobile
617 $.mobile.base = base;
620 $.mobile.urlHistory = urlHistory;
622 $.mobile.dialogHashKey = dialogHashKey;
624 //default non-animation transition handler
625 $.mobile.noneTransitionHandler = function( name, reverse, $toPage, $fromPage ) {
627 $fromPage.removeClass( $.mobile.activePageClass );
629 $toPage.addClass( $.mobile.activePageClass );
631 return $.Deferred().resolve( name, reverse, $toPage, $fromPage ).promise();
634 //default handler for unknown transitions
635 $.mobile.defaultTransitionHandler = $.mobile.noneTransitionHandler;
637 //transition handler dictionary for 3rd party transitions
638 $.mobile.transitionHandlers = {
639 none: $.mobile.defaultTransitionHandler
642 //enable cross-domain page support
643 $.mobile.allowCrossDomainPages = false;
645 //return the original document url
646 $.mobile.getDocumentUrl = function(asParsedObject) {
647 return asParsedObject ? $.extend( {}, documentUrl ) : documentUrl.href;
650 //return the original document base url
651 $.mobile.getDocumentBase = function(asParsedObject) {
652 return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href;
655 $.mobile._bindPageRemove = function() {
658 // when dom caching is not enabled or the page is embedded bind to remove the page on hide
659 if( !page.data("page").options.domCache
660 && page.is(":jqmData(external-page='true')") ) {
662 page.bind( 'pagehide.remove', function() {
663 var $this = $( this ),
664 prEvent = new $.Event( "pageremove" );
666 $this.trigger( prEvent );
668 if( !prEvent.isDefaultPrevented() ){
669 $this.removeWithDependents();
675 // Load a page into the DOM.
676 $.mobile.loadPage = function( url, options ) {
677 // This function uses deferred notifications to let callers
678 // know when the page is done loading, or if an error has occurred.
679 var deferred = $.Deferred(),
681 // The default loadPage options with overrides specified by
683 settings = $.extend( {}, $.mobile.loadPage.defaults, options ),
685 // The DOM element for the page after it has been loaded.
688 // If the reloadPage option is true, and the page is already
689 // in the DOM, dupCachedPage will be set to the page element
690 // so that it can be removed after the new version of the
691 // page is loaded off the network.
692 dupCachedPage = null,
694 // determine the current base url
695 findBaseWithDefault = function(){
696 var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
697 return closestBase || documentBase.hrefNoHash;
700 // The absolute version of the URL passed into the function. This
701 // version of the URL may contain dialog/subpage params in it.
702 absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() );
705 // If the caller provided data, and we're using "get" request,
706 // append the data to the URL.
707 if ( settings.data && settings.type === "get" ) {
708 absUrl = path.addSearchParams( absUrl, settings.data );
709 settings.data = undefined;
712 // If the caller is using a "post" request, reloadPage must be true
713 if( settings.data && settings.type === "post" ){
714 settings.reloadPage = true;
717 // The absolute version of the URL minus any dialog/subpage params.
718 // In otherwords the real URL of the page to be loaded.
719 var fileUrl = path.getFilePath( absUrl ),
721 // The version of the Url actually stored in the data-url attribute of
722 // the page. For embedded pages, it is just the id of the page. For pages
723 // within the same domain as the document base, it is the site relative
724 // path. For cross-domain pages (Phone Gap only) the entire absolute Url
725 // used to load the page.
726 dataUrl = path.convertUrlToDataUrl( absUrl );
728 // Make sure we have a pageContainer to work with.
729 settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
731 // Check to see if the page already exists in the DOM.
732 page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
734 // If we failed to find the page, check to see if the url is a
735 // reference to an embedded page. If so, it may have been dynamically
736 // injected by a developer, in which case it would be lacking a data-url
737 // attribute and in need of enhancement.
738 if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) {
739 page = settings.pageContainer.children( "#" + dataUrl )
740 .attr( "data-" + $.mobile.ns + "url", dataUrl );
743 // If we failed to find a page in the DOM, check the URL to see if it
744 // refers to the first page in the application. If it isn't a reference
745 // to the first page and refers to non-existent embedded page, error out.
746 if ( page.length === 0 ) {
747 if ( $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) {
748 // Check to make sure our cached-first-page is actually
749 // in the DOM. Some user deployed apps are pruning the first
750 // page from the DOM for various reasons, we check for this
751 // case here because we don't want a first-page with an id
752 // falling through to the non-existent embedded page error
753 // case. If the first-page is not in the DOM, then we let
754 // things fall through to the ajax loading code below so
755 // that it gets reloaded.
756 if ( $.mobile.firstPage.parent().length ) {
757 page = $( $.mobile.firstPage );
759 } else if ( path.isEmbeddedPage( fileUrl ) ) {
760 deferred.reject( absUrl, options );
761 return deferred.promise();
765 // Reset base to the default document base.
770 // If the page we are interested in is already in the DOM,
771 // and the caller did not indicate that we should force a
772 // reload of the file, we are done. Otherwise, track the
773 // existing page as a duplicated.
775 if ( !settings.reloadPage ) {
776 enhancePage( page, settings.role );
777 deferred.resolve( absUrl, options, page );
778 return deferred.promise();
780 dupCachedPage = page;
783 var mpc = settings.pageContainer,
784 pblEvent = new $.Event( "pagebeforeload" ),
785 triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
787 // Let listeners know we're about to load a page.
788 mpc.trigger( pblEvent, triggerData );
790 // If the default behavior is prevented, stop here!
791 if( pblEvent.isDefaultPrevented() ){
792 return deferred.promise();
795 if ( settings.showLoadMsg ) {
797 // This configurable timeout allows cached pages a brief delay to load without showing a message
798 var loadMsgDelay = setTimeout(function(){
799 $.mobile.showPageLoadingMsg();
800 }, settings.loadMsgDelay ),
802 // Shared logic for clearing timeout and removing message.
803 hideMsg = function(){
805 // Stop message show timer
806 clearTimeout( loadMsgDelay );
808 // Hide loading message
809 $.mobile.hidePageLoadingMsg();
813 if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) {
814 deferred.reject( absUrl, options );
816 // Load the new page.
822 success: function( html, textStatus, xhr ) {
823 //pre-parse html to check for a data-url,
824 //use it as the new fileUrl, base path, etc
825 var all = $( "<div></div>" ),
828 newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,
830 // TODO handle dialogs again
831 pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
832 dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );
835 // data-url must be provided for the base tag so resource requests can be directed to the
836 // correct url. loading into a temprorary element makes these requests immediately
837 if( pageElemRegex.test( html )
839 && dataUrlRegex.test( RegExp.$1 )
841 url = fileUrl = path.getFilePath( RegExp.$1 );
848 //workaround to allow scripts to execute when included in page divs
849 all.get( 0 ).innerHTML = html;
850 page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
852 //if page elem couldn't be found, create one and insert the body element's contents
854 page = $( "<div data-" + $.mobile.ns + "role='page'>" + html.split( /<\/?body[^>]*>/gmi )[1] + "</div>" );
857 if ( newPageTitle && !page.jqmData( "title" ) ) {
858 if ( ~newPageTitle.indexOf( "&" ) ) {
859 newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
861 page.jqmData( "title", newPageTitle );
864 //rewrite src and href attrs to use a base url
865 if( !$.support.dynamicBaseTag ) {
866 var newPath = path.get( fileUrl );
867 page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
868 var thisAttr = $( this ).is( '[href]' ) ? 'href' :
869 $(this).is('[src]') ? 'src' : 'action',
870 thisUrl = $( this ).attr( thisAttr );
872 // XXX_jblas: We need to fix this so that it removes the document
873 // base URL, and then prepends with the new page URL.
874 //if full path exists and is same, chop it - helps IE out
875 thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );
877 if( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
878 $( this ).attr( thisAttr, newPath + thisUrl );
883 //append to page and enhance
884 // TODO taging a page with external to make sure that embedded pages aren't removed
885 // by the various page handling code is bad. Having page handling code in many
886 // places is bad. Solutions post 1.0
888 .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
889 .attr( "data-" + $.mobile.ns + "external-page", true )
890 .appendTo( settings.pageContainer );
892 // wait for page creation to leverage options defined on widget
893 page.one( 'pagecreate', $.mobile._bindPageRemove );
895 enhancePage( page, settings.role );
897 // Enhancing the page may result in new dialogs/sub pages being inserted
898 // into the DOM. If the original absUrl refers to a sub-page, that is the
899 // real page we are interested in.
900 if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
901 page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
904 //bind pageHide to removePage after it's hidden, if the page options specify to do so
906 // Remove loading message.
907 if ( settings.showLoadMsg ) {
911 // Add the page reference and xhr to our triggerData.
912 triggerData.xhr = xhr;
913 triggerData.textStatus = textStatus;
914 triggerData.page = page;
916 // Let listeners know the page loaded successfully.
917 settings.pageContainer.trigger( "pageload", triggerData );
919 deferred.resolve( absUrl, options, page, dupCachedPage );
921 error: function( xhr, textStatus, errorThrown ) {
922 //set base back to current path
924 base.set( path.get() );
927 // Add error info to our triggerData.
928 triggerData.xhr = xhr;
929 triggerData.textStatus = textStatus;
930 triggerData.errorThrown = errorThrown;
932 var plfEvent = new $.Event( "pageloadfailed" );
934 // Let listeners know the page load failed.
935 settings.pageContainer.trigger( plfEvent, triggerData );
937 // If the default behavior is prevented, stop here!
938 // Note that it is the responsibility of the listener/handler
939 // that called preventDefault(), to resolve/reject the
940 // deferred object within the triggerData.
941 if( plfEvent.isDefaultPrevented() ){
945 // Remove loading message.
946 if ( settings.showLoadMsg ) {
948 // Remove loading message.
952 $( "<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>"+ $.mobile.pageLoadErrorMessage +"</h1></div>" )
953 .css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
954 .appendTo( settings.pageContainer )
956 .fadeOut( 400, function() {
961 deferred.reject( absUrl, options );
966 return deferred.promise();
969 $.mobile.loadPage.defaults = {
973 role: undefined, // By default we rely on the role defined by the @data-role attribute.
975 pageContainer: undefined,
976 loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message.
979 // Show a specific page in the page container.
980 $.mobile.changePage = function( toPage, options ) {
981 // If we are in the midst of a transition, queue the current request.
982 // We'll call changePage() once we're done with the current transition to
983 // service the request.
984 if( isPageTransitioning ) {
985 pageTransitionQueue.unshift( arguments );
989 var settings = $.extend( {}, $.mobile.changePage.defaults, options );
991 // Make sure we have a pageContainer to work with.
992 settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
994 // Make sure we have a fromPage.
995 settings.fromPage = settings.fromPage || $.mobile.activePage;
997 var mpc = settings.pageContainer,
998 pbcEvent = new $.Event( "pagebeforechange" ),
999 triggerData = { toPage: toPage, options: settings };
1001 // Let listeners know we're about to change the current page.
1002 mpc.trigger( pbcEvent, triggerData );
1004 // If the default behavior is prevented, stop here!
1005 if( pbcEvent.isDefaultPrevented() ){
1009 // We allow "pagebeforechange" observers to modify the toPage in the trigger
1010 // data to allow for redirects. Make sure our toPage is updated.
1012 toPage = triggerData.toPage;
1014 // Set the isPageTransitioning flag to prevent any requests from
1015 // entering this method while we are in the midst of loading a page
1016 // or transitioning.
1018 isPageTransitioning = true;
1020 // If the caller passed us a url, call loadPage()
1021 // to make sure it is loaded into the DOM. We'll listen
1022 // to the promise object it returns so we know when
1023 // it is done loading or if an error ocurred.
1024 if ( typeof toPage == "string" ) {
1025 $.mobile.loadPage( toPage, settings )
1026 .done(function( url, options, newPage, dupCachedPage ) {
1027 isPageTransitioning = false;
1028 options.duplicateCachedPage = dupCachedPage;
1029 $.mobile.changePage( newPage, options );
1031 .fail(function( url, options ) {
1032 isPageTransitioning = false;
1034 //clear out the active button state
1035 removeActiveLinkClass( true );
1037 //release transition lock so navigation is free again
1038 releasePageTransitionLock();
1039 settings.pageContainer.trigger( "pagechangefailed", triggerData );
1044 // If we are going to the first-page of the application, we need to make
1045 // sure settings.dataUrl is set to the application document url. This allows
1046 // us to avoid generating a document url with an id hash in the case where the
1047 // first-page of the document has an id attribute specified.
1048 if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
1049 settings.dataUrl = documentUrl.hrefNoHash;
1052 // The caller passed us a real page DOM element. Update our
1053 // internal state and then trigger a transition to the page.
1054 var fromPage = settings.fromPage,
1055 url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ),
1056 // The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path
1058 fileUrl = path.getFilePath( url ),
1059 active = urlHistory.getActive(),
1060 activeIsInitialPage = urlHistory.activeIndex === 0,
1062 pageTitle = document.title,
1063 isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog";
1065 // By default, we prevent changePage requests when the fromPage and toPage
1066 // are the same element, but folks that generate content manually/dynamically
1067 // and reuse pages want to be able to transition to the same page. To allow
1068 // this, they will need to change the default value of allowSamePageTransition
1069 // to true, *OR*, pass it in as an option when they manually call changePage().
1070 // It should be noted that our default transition animations assume that the
1071 // formPage and toPage are different elements, so they may behave unexpectedly.
1072 // It is up to the developer that turns on the allowSamePageTransitiona option
1073 // to either turn off transition animations, or make sure that an appropriate
1074 // animation transition is used.
1075 if( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) {
1076 isPageTransitioning = false;
1077 mpc.trigger( "pagechange", triggerData );
1081 // We need to make sure the page we are given has already been enhanced.
1082 enhancePage( toPage, settings.role );
1084 // If the changePage request was sent from a hashChange event, check to see if the
1085 // page is already within the urlHistory stack. If so, we'll assume the user hit
1086 // the forward/back button and will try to match the transition accordingly.
1087 if( settings.fromHashChange ) {
1088 urlHistory.directHashChange({
1090 isBack: function() { historyDir = -1; },
1091 isForward: function() { historyDir = 1; }
1095 // Kill the keyboard.
1096 // XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
1097 // we should be tracking focus with a live() handler so we already have
1098 // the element in hand at this point.
1099 // Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
1100 // is undefined when we are in an IFrame.
1102 if(document.activeElement && document.activeElement.nodeName.toLowerCase() != 'body') {
1103 $(document.activeElement).blur();
1105 $( "input:focus, textarea:focus, select:focus" ).blur();
1109 // If we're displaying the page as a dialog, we don't want the url
1110 // for the dialog content to be used in the hash. Instead, we want
1111 // to append the dialogHashKey to the url of the current page.
1112 if ( isDialog && active ) {
1113 // on the initial page load active.url is undefined and in that case should
1114 // be an empty string. Moving the undefined -> empty string back into
1115 // urlHistory.addNew seemed imprudent given undefined better represents
1117 url = ( active.url || "" ) + dialogHashKey;
1120 // Set the location hash.
1121 if( settings.changeHash !== false && url ) {
1122 //disable hash listening temporarily
1123 urlHistory.ignoreNextHashChange = true;
1124 //update hash and history
1128 // if title element wasn't found, try the page div data attr too
1129 // If this is a deep-link or a reload ( active === undefined ) then just use pageTitle
1130 var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children(":jqmData(role='header')").find(".ui-title" ).getEncodedText();
1131 if( !!newPageTitle && pageTitle == document.title ) {
1132 pageTitle = newPageTitle;
1134 if ( !toPage.jqmData( "title" ) ) {
1135 toPage.jqmData( "title", pageTitle );
1138 // Make sure we have a transition defined.
1139 settings.transition = settings.transition
1140 || ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined )
1141 || ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
1143 //add page to history stack if it's not back or forward
1145 urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role );
1149 document.title = urlHistory.getActive().title;
1151 //set "toPage" as activePage
1152 $.mobile.activePage = toPage;
1154 // If we're navigating back in the URL history, set reverse accordingly.
1155 settings.reverse = settings.reverse || historyDir < 0;
1157 transitionPages( toPage, fromPage, settings.transition, settings.reverse )
1159 removeActiveLinkClass();
1161 //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
1162 if ( settings.duplicateCachedPage ) {
1163 settings.duplicateCachedPage.remove();
1166 //remove initial build class (only present on first pageshow)
1167 $html.removeClass( "ui-mobile-rendering" );
1169 releasePageTransitionLock();
1171 // Let listeners know we're all done changing the current page.
1172 mpc.trigger( "pagechange", triggerData );
1176 $.mobile.changePage.defaults = {
1177 transition: undefined,
1180 fromHashChange: false,
1181 role: undefined, // By default we rely on the role defined by the @data-role attribute.
1182 duplicateCachedPage: undefined,
1183 pageContainer: undefined,
1184 showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
1186 fromPage: undefined,
1187 allowSamePageTransition: false
1190 /* Event Bindings - hashchange, submit, and click */
1191 function findClosestLink( ele )
1194 // Look for the closest element with a nodeName of "a".
1195 // Note that we are checking if we have a valid nodeName
1196 // before attempting to access it. This is because the
1197 // node we get called with could have originated from within
1198 // an embedded SVG document where some symbol instance elements
1199 // don't have nodeName defined on them, or strings are of type
1200 // SVGAnimatedString.
1201 if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() == "a" ) {
1204 ele = ele.parentNode;
1209 // The base URL for any given element depends on the page it resides in.
1210 function getClosestBaseUrl( ele )
1212 // Find the closest page and extract out its url.
1213 var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ),
1214 base = documentBase.hrefNoHash;
1216 if ( !url || !path.isPath( url ) ) {
1220 return path.makeUrlAbsolute( url, base);
1224 //The following event bindings should be bound after mobileinit has been triggered
1225 //the following function is called in the init file
1226 $.mobile._registerInternalEvents = function(){
1228 //bind to form submit events, handle with Ajax
1229 $( "form" ).live('submit', function( event ) {
1230 var $this = $( this );
1231 if( !$.mobile.ajaxEnabled ||
1232 $this.is( ":jqmData(ajax='false')" ) ) {
1236 var type = $this.attr( "method" ),
1237 target = $this.attr( "target" ),
1238 url = $this.attr( "action" );
1240 // If no action is specified, browsers default to using the
1241 // URL of the document containing the form. Since we dynamically
1242 // pull in pages from external documents, the form should submit
1243 // to the URL for the source document of the page containing
1246 // Get the @data-url for the page containing the form.
1247 url = getClosestBaseUrl( $this );
1248 if ( url === documentBase.hrefNoHash ) {
1249 // The url we got back matches the document base,
1250 // which means the page must be an internal/embedded page,
1251 // so default to using the actual document url as a browser
1253 url = documentUrl.hrefNoSearch;
1257 url = path.makeUrlAbsolute( url, getClosestBaseUrl($this) );
1259 //external submits use regular HTTP
1260 if( path.isExternal( url ) || target ) {
1264 $.mobile.changePage(
1267 type: type && type.length && type.toLowerCase() || "get",
1268 data: $this.serialize(),
1269 transition: $this.jqmData( "transition" ),
1270 direction: $this.jqmData( "direction" ),
1274 event.preventDefault();
1277 //add active state on vclick
1278 $( document ).bind( "vclick", function( event ) {
1279 // if this isn't a left click we don't care. Its important to note
1280 // that when the virtual event is generated it will create
1281 if ( event.which > 1 || !$.mobile.linkBindingEnabled ){
1285 var link = findClosestLink( event.target );
1287 if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) {
1288 removeActiveLinkClass( true );
1289 $activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" );
1290 $activeClickedLink.addClass( $.mobile.activeBtnClass );
1291 $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur();
1296 // click routing - direct to HTTP or Ajax, accordingly
1297 $( document ).bind( "click", function( event ) {
1298 if( !$.mobile.linkBindingEnabled ){
1302 var link = findClosestLink( event.target );
1304 // If there is no link associated with the click or its not a left
1305 // click we want to ignore the click
1306 if ( !link || event.which > 1) {
1310 var $link = $( link ),
1311 //remove active link class if external (then it won't be there if you come back)
1312 httpCleanup = function(){
1313 window.setTimeout( function() { removeActiveLinkClass( true ); }, 200 );
1316 //if there's a data-rel=back attr, go back in history
1317 if( $link.is( ":jqmData(rel='back')" ) ) {
1318 window.history.back();
1322 var baseUrl = getClosestBaseUrl( $link ),
1324 //get href, if defined, otherwise default to empty hash
1325 href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
1327 //if ajax is disabled, exit early
1328 if( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ){
1330 //use default click handling
1334 // XXX_jblas: Ideally links to application pages should be specified as
1335 // an url to the application document with a hash that is either
1336 // the site relative path or id to the page. But some of the
1337 // internal code that dynamically generates sub-pages for nested
1338 // lists and select dialogs, just write a hash in the link they
1339 // create. This means the actual URL path is based on whatever
1340 // the current value of the base tag is at the time this code
1341 // is called. For now we are just assuming that any url with a
1342 // hash in it is an application page reference.
1343 if ( href.search( "#" ) != -1 ) {
1344 href = href.replace( /[^#]*#/, "" );
1346 //link was an empty hash meant purely
1347 //for interaction, so we ignore it.
1348 event.preventDefault();
1350 } else if ( path.isPath( href ) ) {
1351 //we have apath so make it the href we want to load.
1352 href = path.makeUrlAbsolute( href, baseUrl );
1354 //we have a simple id so use the documentUrl as its base.
1355 href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
1359 // Should we handle this link, or let the browser deal with it?
1360 var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ),
1362 // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
1363 // requests if the document doing the request was loaded via the file:// protocol.
1364 // This is usually to allow the application to "phone home" and fetch app specific
1365 // data. We normally let the browser handle external/cross-domain urls, but if the
1366 // allowCrossDomainPages option is true, we will allow cross-domain http/https
1367 // requests to go through our page loading logic.
1368 isCrossDomainPageLoad = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search( /^https?:/ ) != -1 ),
1370 //check for protocol or rel and its not an embedded page
1371 //TODO overlap in logic from isExternal, rel=external check should be
1372 // moved into more comprehensive isExternalLink
1373 isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad );
1377 //use default click handling
1382 var transition = $link.jqmData( "transition" ),
1383 direction = $link.jqmData( "direction" ),
1384 reverse = ( direction && direction === "reverse" ) ||
1385 // deprecated - remove by 1.0
1386 $link.jqmData( "back" ),
1388 //this may need to be more specific as we use data-rel more
1389 role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
1391 $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } );
1392 event.preventDefault();
1395 //prefetch pages when anchors with data-prefetch are encountered
1396 $( ".ui-page" ).live( "pageshow.prefetch", function() {
1398 $( this ).find( "a:jqmData(prefetch)" ).each(function(){
1399 var $link = $(this),
1400 url = $link.attr( "href" );
1402 if ( url && $.inArray( url, urls ) === -1 ) {
1405 $.mobile.loadPage( url, {role: $link.attr("data-" + $.mobile.ns + "rel")} );
1410 $.mobile._handleHashChange = function( hash ) {
1411 //find first page via hash
1412 var to = path.stripHash( hash ),
1413 //transition is false if it's the first page, undefined otherwise (and may be overridden by default)
1414 transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined,
1416 // default options for the changPage calls made after examining the current state
1417 // of the page and the hash
1418 changePageOptions = {
1419 transition: transition,
1421 fromHashChange: true
1424 //if listening is disabled (either globally or temporarily), or it's a dialog hash
1425 if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) {
1426 urlHistory.ignoreNextHashChange = false;
1430 // special case for dialogs
1431 if( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 ) {
1433 // If current active page is not a dialog skip the dialog and continue
1434 // in the same direction
1435 if(!$.mobile.activePage.is( ".ui-dialog" )) {
1436 //determine if we're heading forward or backward and continue accordingly past
1437 //the current dialog
1438 urlHistory.directHashChange({
1440 isBack: function() { window.history.back(); },
1441 isForward: function() { window.history.forward(); }
1444 // prevent changePage()
1447 // if the current active page is a dialog and we're navigating
1448 // to a dialog use the dialog objected saved in the stack
1449 urlHistory.directHashChange({
1452 // regardless of the direction of the history change
1454 either: function( isBack ) {
1455 var active = $.mobile.urlHistory.getActive();
1457 to = active.pageUrl;
1459 // make sure to set the role, transition and reversal
1460 // as most of this is lost by the domCache cleaning
1461 $.extend( changePageOptions, {
1463 transition: active.transition,
1471 //if to is defined, load it
1473 // At this point, 'to' can be one of 3 things, a cached page element from
1474 // a history stack entry, an id, or site-relative/absolute URL. If 'to' is
1475 // an id, we need to resolve it against the documentBase, not the location.href,
1476 // since the hashchange could've been the result of a forward/backward navigation
1477 // that crosses from an external page/dialog to an internal page/dialog.
1478 to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to;
1479 $.mobile.changePage( to, changePageOptions );
1481 //there's no hash, go to the first page in the dom
1482 $.mobile.changePage( $.mobile.firstPage, changePageOptions );
1486 //hashchange event handler
1487 $window.bind( "hashchange", function( e, triggered ) {
1488 $.mobile._handleHashChange( location.hash );
1491 //set page min-heights to be device specific
1492 $( document ).bind( "pageshow", resetActivePageHeight );
1493 $( window ).bind( "throttledresize", resetActivePageHeight );
1495 };//_registerInternalEvents callback