Tizen 2.1 base
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.2.0 / js / jquery.mobile.navigation.js
1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Applies the AJAX navigation system to links and forms to enable page transitions
3 //>>label: AJAX Navigation System
4 //>>group: Navigation
5
6 define( [
7         "jquery",
8         "./jquery.mobile.core",
9         "./jquery.mobile.events",
10         "./jquery.mobile.support",
11         "depend!./jquery.hashchange[jquery]",
12         "./widgets/page",
13         "./jquery.mobile.transition" ], function( $ ) {
14 //>>excludeEnd("jqmBuildExclude");
15 (function( $, undefined ) {
16
17         //define vars for interal use
18         var $window = $( window ),
19                 $html = $( 'html' ),
20                 $head = $( 'head' ),
21
22                 //url path helpers for use in relative url management
23                 path = {
24
25                         // This scary looking regular expression parses an absolute URL or its relative
26                         // variants (protocol, site, document, query, and hash), into the various
27                         // components (protocol, host, path, query, fragment, etc that make up the
28                         // URL as well as some other commonly used sub-parts. When used with RegExp.exec()
29                         // or String.match, it parses the URL into a results array that looks like this:
30                         //
31                         //     [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
32                         //     [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
33                         //     [2]: http://jblas:password@mycompany.com:8080/mail/inbox
34                         //     [3]: http://jblas:password@mycompany.com:8080
35                         //     [4]: http:
36                         //     [5]: //
37                         //     [6]: jblas:password@mycompany.com:8080
38                         //     [7]: jblas:password
39                         //     [8]: jblas
40                         //     [9]: password
41                         //    [10]: mycompany.com:8080
42                         //    [11]: mycompany.com
43                         //    [12]: 8080
44                         //    [13]: /mail/inbox
45                         //    [14]: /mail/
46                         //    [15]: inbox
47                         //    [16]: ?msg=1234&type=unread
48                         //    [17]: #msg-content
49                         //
50                         urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
51
52                         // Abstraction to address xss (Issue #4787) by removing the authority in
53                         // browsers that auto   decode it. All references to location.href should be
54                         // replaced with a call to this method so that it can be dealt with properly here
55                         getLocation: function( url ) {
56                                 var uri = url ? this.parseUrl( url ) : location,
57                                         hash = this.parseUrl( url || location.href ).hash;
58
59                                 // mimic the browser with an empty string when the hash is empty
60                                 hash = hash === "#" ? "" : hash;
61
62                                 // Make sure to parse the url or the location object for the hash because using location.hash
63                                 // is autodecoded in firefox, the rest of the url should be from the object (location unless
64                                 // we're testing) to avoid the inclusion of the authority
65                                 return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
66                         },
67
68                         parseLocation: function() {
69                                 return this.parseUrl( this.getLocation() );
70                         },
71
72                         //Parse a URL into a structure that allows easy access to
73                         //all of the URL components by name.
74                         parseUrl: function( url ) {
75                                 // If we're passed an object, we'll assume that it is
76                                 // a parsed url object and just return it back to the caller.
77                                 if ( $.type( url ) === "object" ) {
78                                         return url;
79                                 }
80
81                                 var matches = path.urlParseRE.exec( url || "" ) || [];
82
83                                         // Create an object that allows the caller to access the sub-matches
84                                         // by name. Note that IE returns an empty string instead of undefined,
85                                         // like all other browsers do, so we normalize everything so its consistent
86                                         // no matter what browser we're running on.
87                                         return {
88                                                 href:         matches[  0 ] || "",
89                                                 hrefNoHash:   matches[  1 ] || "",
90                                                 hrefNoSearch: matches[  2 ] || "",
91                                                 domain:       matches[  3 ] || "",
92                                                 protocol:     matches[  4 ] || "",
93                                                 doubleSlash:  matches[  5 ] || "",
94                                                 authority:    matches[  6 ] || "",
95                                                 username:     matches[  8 ] || "",
96                                                 password:     matches[  9 ] || "",
97                                                 host:         matches[ 10 ] || "",
98                                                 hostname:     matches[ 11 ] || "",
99                                                 port:         matches[ 12 ] || "",
100                                                 pathname:     matches[ 13 ] || "",
101                                                 directory:    matches[ 14 ] || "",
102                                                 filename:     matches[ 15 ] || "",
103                                                 search:       matches[ 16 ] || "",
104                                                 hash:         matches[ 17 ] || ""
105                                         };
106                         },
107
108                         //Turn relPath into an asbolute path. absPath is
109                         //an optional absolute path which describes what
110                         //relPath is relative to.
111                         makePathAbsolute: function( relPath, absPath ) {
112                                 if ( relPath && relPath.charAt( 0 ) === "/" ) {
113                                         return relPath;
114                                 }
115
116                                 relPath = relPath || "";
117                                 absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
118
119                                 var absStack = absPath ? absPath.split( "/" ) : [],
120                                         relStack = relPath.split( "/" );
121                                 for ( var i = 0; i < relStack.length; i++ ) {
122                                         var d = relStack[ i ];
123                                         switch ( d ) {
124                                                 case ".":
125                                                         break;
126                                                 case "..":
127                                                         if ( absStack.length ) {
128                                                                 absStack.pop();
129                                                         }
130                                                         break;
131                                                 default:
132                                                         absStack.push( d );
133                                                         break;
134                                         }
135                                 }
136                                 return "/" + absStack.join( "/" );
137                         },
138
139                         //Returns true if both urls have the same domain.
140                         isSameDomain: function( absUrl1, absUrl2 ) {
141                                 return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
142                         },
143
144                         //Returns true for any relative variant.
145                         isRelativeUrl: function( url ) {
146                                 // All relative Url variants have one thing in common, no protocol.
147                                 return path.parseUrl( url ).protocol === "";
148                         },
149
150                         //Returns true for an absolute url.
151                         isAbsoluteUrl: function( url ) {
152                                 return path.parseUrl( url ).protocol !== "";
153                         },
154
155                         //Turn the specified realtive URL into an absolute one. This function
156                         //can handle all relative variants (protocol, site, document, query, fragment).
157                         makeUrlAbsolute: function( relUrl, absUrl ) {
158                                 if ( !path.isRelativeUrl( relUrl ) ) {
159                                         return relUrl;
160                                 }
161
162                                 if ( absUrl === undefined ) {
163                                         absUrl = documentBase;
164                                 }
165
166                                 var relObj = path.parseUrl( relUrl ),
167                                         absObj = path.parseUrl( absUrl ),
168                                         protocol = relObj.protocol || absObj.protocol,
169                                         doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
170                                         authority = relObj.authority || absObj.authority,
171                                         hasPath = relObj.pathname !== "",
172                                         pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
173                                         search = relObj.search || ( !hasPath && absObj.search ) || "",
174                                         hash = relObj.hash;
175
176                                 return protocol + doubleSlash + authority + pathname + search + hash;
177                         },
178
179                         //Add search (aka query) params to the specified url.
180                         addSearchParams: function( url, params ) {
181                                 var u = path.parseUrl( url ),
182                                         p = ( typeof params === "object" ) ? $.param( params ) : params,
183                                         s = u.search || "?";
184                                 return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
185                         },
186
187                         convertUrlToDataUrl: function( absUrl ) {
188                                 var u = path.parseUrl( absUrl );
189                                 if ( path.isEmbeddedPage( u ) ) {
190                                         // For embedded pages, remove the dialog hash key as in getFilePath(),
191                                         // otherwise the Data Url won't match the id of the embedded Page.
192                                         return u.hash.split( dialogHashKey )[0].replace( /^#/, "" );
193                                 } else if ( path.isSameDomain( u, documentBase ) ) {
194                                         return u.hrefNoHash.replace( documentBase.domain, "" ).split( dialogHashKey )[0];
195                                 }
196
197                                 return window.decodeURIComponent(absUrl);
198                         },
199
200                         //get path from current hash, or from a file path
201                         get: function( newPath ) {
202                                 if ( newPath === undefined ) {
203                                         newPath = path.parseLocation().hash;
204                                 }
205                                 return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
206                         },
207
208                         //return the substring of a filepath before the sub-page key, for making a server request
209                         getFilePath: function( path ) {
210                                 var splitkey = '&' + $.mobile.subPageUrlKey;
211                                 return path && path.split( splitkey )[0].split( dialogHashKey )[0];
212                         },
213
214                         //set location hash to path
215                         set: function( path ) {
216                                 location.hash = path;
217                         },
218
219                         //test if a given url (string) is a path
220                         //NOTE might be exceptionally naive
221                         isPath: function( url ) {
222                                 return ( /\// ).test( url );
223                         },
224
225                         //return a url path with the window's location protocol/hostname/pathname removed
226                         clean: function( url ) {
227                                 return url.replace( documentBase.domain, "" );
228                         },
229
230                         //just return the url without an initial #
231                         stripHash: function( url ) {
232                                 return url.replace( /^#/, "" );
233                         },
234
235                         //remove the preceding hash, any query params, and dialog notations
236                         cleanHash: function( hash ) {
237                                 return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
238                         },
239
240                         isHashValid: function( hash ) {
241                                 return ( /^#[^#]+$/ ).test( hash );
242                         },
243
244                         //check whether a url is referencing the same domain, or an external domain or different protocol
245                         //could be mailto, etc
246                         isExternal: function( url ) {
247                                 var u = path.parseUrl( url );
248                                 return u.protocol && u.domain !== documentUrl.domain ? true : false;
249                         },
250
251                         hasProtocol: function( url ) {
252                                 return ( /^(:?\w+:)/ ).test( url );
253                         },
254
255                         //check if the specified url refers to the first page in the main application document.
256                         isFirstPageUrl: function( url ) {
257                                 // We only deal with absolute paths.
258                                 var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ),
259
260                                         // Does the url have the same path as the document?
261                                         samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ),
262
263                                         // Get the first page element.
264                                         fp = $.mobile.firstPage,
265
266                                         // Get the id of the first page element if it has one.
267                                         fpId = fp && fp[0] ? fp[0].id : undefined;
268
269                                         // The url refers to the first page if the path matches the document and
270                                         // it either has no hash value, or the hash is exactly equal to the id of the
271                                         // first page element.
272                                         return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
273                         },
274
275                         isEmbeddedPage: function( url ) {
276                                 var u = path.parseUrl( url );
277
278                                 //if the path is absolute, then we need to compare the url against
279                                 //both the documentUrl and the documentBase. The main reason for this
280                                 //is that links embedded within external documents will refer to the
281                                 //application document, whereas links embedded within the application
282                                 //document will be resolved against the document base.
283                                 if ( u.protocol !== "" ) {
284                                         return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) );
285                                 }
286                                 return ( /^#/ ).test( u.href );
287                         },
288
289
290                         // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
291                         // requests if the document doing the request was loaded via the file:// protocol.
292                         // This is usually to allow the application to "phone home" and fetch app specific
293                         // data. We normally let the browser handle external/cross-domain urls, but if the
294                         // allowCrossDomainPages option is true, we will allow cross-domain http/https
295                         // requests to go through our page loading logic.
296                         isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
297                                 return $.mobile.allowCrossDomainPages &&
298                                         docUrl.protocol === "file:" &&
299                                         reqUrl.search( /^https?:/ ) !== -1;
300                         }
301                 },
302
303                 //will be defined when a link is clicked and given an active class
304                 $activeClickedLink = null,
305
306                 //urlHistory is purely here to make guesses at whether the back or forward button was clicked
307                 //and provide an appropriate transition
308                 urlHistory = {
309                         // Array of pages that are visited during a single page load.
310                         // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs)
311                         stack: [],
312
313                         //maintain an index number for the active page in the stack
314                         activeIndex: 0,
315
316                         //get active
317                         getActive: function() {
318                                 return urlHistory.stack[ urlHistory.activeIndex ];
319                         },
320
321                         getPrev: function() {
322                                 return urlHistory.stack[ urlHistory.activeIndex - 1 ];
323                         },
324
325                         getNext: function() {
326                                 return urlHistory.stack[ urlHistory.activeIndex + 1 ];
327                         },
328
329                         // addNew is used whenever a new page is added
330                         addNew: function( url, transition, title, pageUrl, role ) {
331                                 //if there's forward history, wipe it
332                                 if ( urlHistory.getNext() ) {
333                                         urlHistory.clearForward();
334                                 }
335
336                                 urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl, role: role } );
337
338                                 urlHistory.activeIndex = urlHistory.stack.length - 1;
339                         },
340
341                         //wipe urls ahead of active index
342                         clearForward: function() {
343                                 urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 );
344                         },
345
346                         directHashChange: function( opts ) {
347                                 var back , forward, newActiveIndex, prev = this.getActive();
348
349                                 // check if url is in history and if it's ahead or behind current page
350                                 $.each( urlHistory.stack, function( i, historyEntry ) {
351
352                                         //if the url is in the stack, it's a forward or a back
353                                         if ( decodeURIComponent( opts.currentUrl ) === decodeURIComponent( historyEntry.url ) ) {
354                                                 //define back and forward by whether url is older or newer than current page
355                                                 back = i < urlHistory.activeIndex;
356                                                 forward = !back;
357                                                 newActiveIndex = i;
358                                         }
359                                 });
360
361                                 // save new page index, null check to prevent falsey 0 result
362                                 this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
363
364                                 if ( back ) {
365                                         ( opts.either || opts.isBack )( true );
366                                 } else if ( forward ) {
367                                         ( opts.either || opts.isForward )( false );
368                                 }
369                         },
370
371                         //disable hashchange event listener internally to ignore one change
372                         //toggled internally when location.hash is updated to match the url of a successful page load
373                         ignoreNextHashChange: false
374                 },
375
376                 //define first selector to receive focus when a page is shown
377                 focusable = "[tabindex],a,button:visible,select:visible,input",
378
379                 //queue to hold simultanious page transitions
380                 pageTransitionQueue = [],
381
382                 //indicates whether or not page is in process of transitioning
383                 isPageTransitioning = false,
384
385                 //nonsense hash change key for dialogs, so they create a history entry
386                 dialogHashKey = "&ui-state=dialog",
387
388                 //existing base tag?
389                 $base = $head.children( "base" ),
390
391                 //tuck away the original document URL minus any fragment.
392                 documentUrl = path.parseLocation(),
393
394                 //if the document has an embedded base tag, documentBase is set to its
395                 //initial value. If a base tag does not exist, then we default to the documentUrl.
396                 documentBase = $base.length ? path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), documentUrl.href ) ) : documentUrl,
397
398                 //cache the comparison once.
399                 documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash ),
400
401                 getScreenHeight = $.mobile.getScreenHeight;
402
403                 //base element management, defined depending on dynamic base tag support
404                 var base = $.support.dynamicBaseTag ? {
405
406                         //define base element, for use in routing asset urls that are referenced in Ajax-requested markup
407                         element: ( $base.length ? $base : $( "<base>", { href: documentBase.hrefNoHash } ).prependTo( $head ) ),
408
409                         //set the generated BASE element's href attribute to a new page's base path
410                         set: function( href ) {
411                                 base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) );
412                         },
413
414                         //set the generated BASE element's href attribute to a new page's base path
415                         reset: function() {
416                                 base.element.attr( "href", documentBase.hrefNoHash );
417                         }
418
419                 } : undefined;
420
421         /* internal utility functions */
422
423         // NOTE Issue #4950 Android phonegap doesn't navigate back properly
424         //      when a full page refresh has taken place. It appears that hashchange
425         //      and replacestate history alterations work fine but we need to support
426         //      both forms of history traversal in our code that uses backward history
427         //      movement
428         $.mobile.back = function() {
429                 var nav = window.navigator;
430
431                 // if the setting is on and the navigator object is
432                 // available use the phonegap navigation capability
433                 if( this.phonegapNavigationEnabled &&
434                         nav &&
435                         nav.app &&
436                         nav.app.backHistory ){
437                         nav.app.backHistory();
438                 } else {
439                         window.history.back();
440                 }
441         };
442
443         //direct focus to the page title, or otherwise first focusable element
444         $.mobile.focusPage = function ( page ) {
445                 var autofocus = page.find( "[autofocus]" ),
446                         pageTitle = page.find( ".ui-title:eq(0)" );
447
448                 if ( autofocus.length ) {
449                         autofocus.focus();
450                         return;
451                 }
452
453                 if ( pageTitle.length ) {
454                         pageTitle.focus();
455                 } else{
456                         page.focus();
457                 }
458         };
459
460         //remove active classes after page transition or error
461         function removeActiveLinkClass( forceRemoval ) {
462                 if ( !!$activeClickedLink && ( !$activeClickedLink.closest( "." + $.mobile.activePageClass ).length || forceRemoval ) ) {
463                         $activeClickedLink.removeClass( $.mobile.activeBtnClass );
464                 }
465                 $activeClickedLink = null;
466         }
467
468         function releasePageTransitionLock() {
469                 isPageTransitioning = false;
470                 if ( pageTransitionQueue.length > 0 ) {
471                         $.mobile.changePage.apply( null, pageTransitionQueue.pop() );
472                 }
473         }
474
475         // Save the last scroll distance per page, before it is hidden
476         var setLastScrollEnabled = true,
477                 setLastScroll, delayedSetLastScroll;
478
479         setLastScroll = function() {
480                 // this barrier prevents setting the scroll value based on the browser
481                 // scrolling the window based on a hashchange
482                 if ( !setLastScrollEnabled ) {
483                         return;
484                 }
485
486                 var active = $.mobile.urlHistory.getActive();
487
488                 if ( active ) {
489                         var lastScroll = $window.scrollTop();
490
491                         // Set active page's lastScroll prop.
492                         // If the location we're scrolling to is less than minScrollBack, let it go.
493                         active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll;
494                 }
495         };
496
497         // bind to scrollstop to gather scroll position. The delay allows for the hashchange
498         // event to fire and disable scroll recording in the case where the browser scrolls
499         // to the hash targets location (sometimes the top of the page). once pagechange fires
500         // getLastScroll is again permitted to operate
501         delayedSetLastScroll = function() {
502                 setTimeout( setLastScroll, 100 );
503         };
504
505         // disable an scroll setting when a hashchange has been fired, this only works
506         // because the recording of the scroll position is delayed for 100ms after
507         // the browser might have changed the position because of the hashchange
508         $window.bind( $.support.pushState ? "popstate" : "hashchange", function() {
509                 setLastScrollEnabled = false;
510         });
511
512         // handle initial hashchange from chrome :(
513         $window.one( $.support.pushState ? "popstate" : "hashchange", function() {
514                 setLastScrollEnabled = true;
515         });
516
517         // wait until the mobile page container has been determined to bind to pagechange
518         $window.one( "pagecontainercreate", function() {
519                 // once the page has changed, re-enable the scroll recording
520                 $.mobile.pageContainer.bind( "pagechange", function() {
521
522                         setLastScrollEnabled = true;
523
524                         // remove any binding that previously existed on the get scroll
525                         // which may or may not be different than the scroll element determined for
526                         // this page previously
527                         $window.unbind( "scrollstop", delayedSetLastScroll );
528
529                         // determine and bind to the current scoll element which may be the window
530                         // or in the case of touch overflow the element with touch overflow
531                         $window.bind( "scrollstop", delayedSetLastScroll );
532                 });
533         });
534
535         // bind to scrollstop for the first page as "pagechange" won't be fired in that case
536         $window.bind( "scrollstop", delayedSetLastScroll );
537
538         // No-op implementation of transition degradation
539         $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
540                 return transition;
541         };
542
543         //function for transitioning between two existing pages
544         function transitionPages( toPage, fromPage, transition, reverse ) {
545
546                 if ( fromPage ) {
547                         //trigger before show/hide events
548                         fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } );
549                 }
550
551                 toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } );
552
553                 //clear page loader
554                 $.mobile.hidePageLoadingMsg();
555
556                 transition = $.mobile._maybeDegradeTransition( transition );
557
558                 //find the transition handler for the specified transition. If there
559                 //isn't one in our transitionHandlers dictionary, use the default one.
560                 //call the handler immediately to kick-off the transition.
561                 var th = $.mobile.transitionHandlers[ transition || "default" ] || $.mobile.defaultTransitionHandler,
562                         promise = th( transition, reverse, toPage, fromPage );
563
564                 promise.done(function() {
565
566                         //trigger show/hide events
567                         if ( fromPage ) {
568                                 fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } );
569                         }
570
571                         //trigger pageshow, define prevPage as either fromPage or empty jQuery obj
572                         toPage.data( "page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } );
573                 });
574
575                 return promise;
576         }
577
578         //simply set the active page's minimum height to screen height, depending on orientation
579         function resetActivePageHeight() {
580                 var aPage = $( "." + $.mobile.activePageClass ),
581                         aPagePadT = parseFloat( aPage.css( "padding-top" ) ),
582                         aPagePadB = parseFloat( aPage.css( "padding-bottom" ) ),
583                         aPageBorderT = parseFloat( aPage.css( "border-top-width" ) ),
584                         aPageBorderB = parseFloat( aPage.css( "border-bottom-width" ) );
585
586                 aPage.css( "min-height", getScreenHeight() - aPagePadT - aPagePadB - aPageBorderT - aPageBorderB );
587         }
588
589         //shared page enhancements
590         function enhancePage( $page, role ) {
591                 // If a role was specified, make sure the data-role attribute
592                 // on the page element is in sync.
593                 if ( role ) {
594                         $page.attr( "data-" + $.mobile.ns + "role", role );
595                 }
596
597                 //run page plugin
598                 $page.page();
599         }
600
601         /* exposed $.mobile methods */
602
603         //animation complete callback
604         $.fn.animationComplete = function( callback ) {
605                 if ( $.support.cssTransitions ) {
606                         return $( this ).one( 'webkitAnimationEnd animationend', callback );
607                 }
608                 else{
609                         // defer execution for consistency between webkit/non webkit
610                         setTimeout( callback, 0 );
611                         return $( this );
612                 }
613         };
614
615         //expose path object on $.mobile
616         $.mobile.path = path;
617
618         //expose base object on $.mobile
619         $.mobile.base = base;
620
621         //history stack
622         $.mobile.urlHistory = urlHistory;
623
624         $.mobile.dialogHashKey = dialogHashKey;
625
626
627
628         //enable cross-domain page support
629         $.mobile.allowCrossDomainPages = false;
630
631         //return the original document url
632         $.mobile.getDocumentUrl = function( asParsedObject ) {
633                 return asParsedObject ? $.extend( {}, documentUrl ) : documentUrl.href;
634         };
635
636         //return the original document base url
637         $.mobile.getDocumentBase = function( asParsedObject ) {
638                 return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href;
639         };
640
641         $.mobile._bindPageRemove = function() {
642                 var page = $( this );
643
644                 // when dom caching is not enabled or the page is embedded bind to remove the page on hide
645                 if ( !page.data( "page" ).options.domCache &&
646                                 page.is( ":jqmData(external-page='true')" ) ) {
647
648                         page.bind( 'pagehide.remove', function() {
649                                 var $this = $( this ),
650                                         prEvent = new $.Event( "pageremove" );
651
652                                 $this.trigger( prEvent );
653
654                                 if ( !prEvent.isDefaultPrevented() ) {
655                                         $this.removeWithDependents();
656                                 }
657                         });
658                 }
659         };
660
661         // Load a page into the DOM.
662         $.mobile.loadPage = function( url, options ) {
663                 // This function uses deferred notifications to let callers
664                 // know when the page is done loading, or if an error has occurred.
665                 var deferred = $.Deferred(),
666
667                         // The default loadPage options with overrides specified by
668                         // the caller.
669                         settings = $.extend( {}, $.mobile.loadPage.defaults, options ),
670
671                         // The DOM element for the page after it has been loaded.
672                         page = null,
673
674                         // If the reloadPage option is true, and the page is already
675                         // in the DOM, dupCachedPage will be set to the page element
676                         // so that it can be removed after the new version of the
677                         // page is loaded off the network.
678                         dupCachedPage = null,
679
680                         // determine the current base url
681                         findBaseWithDefault = function() {
682                                 var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
683                                 return closestBase || documentBase.hrefNoHash;
684                         },
685
686                         // The absolute version of the URL passed into the function. This
687                         // version of the URL may contain dialog/subpage params in it.
688                         absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() );
689
690
691                 // If the caller provided data, and we're using "get" request,
692                 // append the data to the URL.
693                 if ( settings.data && settings.type === "get" ) {
694                         absUrl = path.addSearchParams( absUrl, settings.data );
695                         settings.data = undefined;
696                 }
697
698                 // If the caller is using a "post" request, reloadPage must be true
699                 if ( settings.data && settings.type === "post" ) {
700                         settings.reloadPage = true;
701                 }
702
703                 // The absolute version of the URL minus any dialog/subpage params.
704                 // In otherwords the real URL of the page to be loaded.
705                 var fileUrl = path.getFilePath( absUrl ),
706
707                         // The version of the Url actually stored in the data-url attribute of
708                         // the page. For embedded pages, it is just the id of the page. For pages
709                         // within the same domain as the document base, it is the site relative
710                         // path. For cross-domain pages (Phone Gap only) the entire absolute Url
711                         // used to load the page.
712                         dataUrl = path.convertUrlToDataUrl( absUrl );
713
714                 // Make sure we have a pageContainer to work with.
715                 settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
716
717                 // Check to see if the page already exists in the DOM.
718                 // NOTE do _not_ use the :jqmData psuedo selector because parenthesis
719                 //      are a valid url char and it breaks on the first occurence
720                 page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
721
722                 // If we failed to find the page, check to see if the url is a
723                 // reference to an embedded page. If so, it may have been dynamically
724                 // injected by a developer, in which case it would be lacking a data-url
725                 // attribute and in need of enhancement.
726                 if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) {
727                         page = settings.pageContainer.children( "#" + dataUrl )
728                                 .attr( "data-" + $.mobile.ns + "url", dataUrl )
729                                 .jqmData( "url", dataUrl );
730                 }
731
732                 // If we failed to find a page in the DOM, check the URL to see if it
733                 // refers to the first page in the application. If it isn't a reference
734                 // to the first page and refers to non-existent embedded page, error out.
735                 if ( page.length === 0 ) {
736                         if ( $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) {
737                                 // Check to make sure our cached-first-page is actually
738                                 // in the DOM. Some user deployed apps are pruning the first
739                                 // page from the DOM for various reasons, we check for this
740                                 // case here because we don't want a first-page with an id
741                                 // falling through to the non-existent embedded page error
742                                 // case. If the first-page is not in the DOM, then we let
743                                 // things fall through to the ajax loading code below so
744                                 // that it gets reloaded.
745                                 if ( $.mobile.firstPage.parent().length ) {
746                                         page = $( $.mobile.firstPage );
747                                 }
748                         } else if ( path.isEmbeddedPage( fileUrl )  ) {
749                                 deferred.reject( absUrl, options );
750                                 return deferred.promise();
751                         }
752                 }
753
754                 // If the page we are interested in is already in the DOM,
755                 // and the caller did not indicate that we should force a
756                 // reload of the file, we are done. Otherwise, track the
757                 // existing page as a duplicated.
758                 if ( page.length ) {
759                         if ( !settings.reloadPage ) {
760                                 enhancePage( page, settings.role );
761                                 deferred.resolve( absUrl, options, page );
762                                 return deferred.promise();
763                         }
764                         dupCachedPage = page;
765                 }
766
767                 var mpc = settings.pageContainer,
768                         pblEvent = new $.Event( "pagebeforeload" ),
769                         triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
770
771                 // Let listeners know we're about to load a page.
772                 mpc.trigger( pblEvent, triggerData );
773
774                 // If the default behavior is prevented, stop here!
775                 if ( pblEvent.isDefaultPrevented() ) {
776                         return deferred.promise();
777                 }
778
779                 if ( settings.showLoadMsg ) {
780
781                         // This configurable timeout allows cached pages a brief delay to load without showing a message
782                         var loadMsgDelay = setTimeout(function() {
783                                         $.mobile.showPageLoadingMsg();
784                                 }, settings.loadMsgDelay ),
785
786                                 // Shared logic for clearing timeout and removing message.
787                                 hideMsg = function() {
788
789                                         // Stop message show timer
790                                         clearTimeout( loadMsgDelay );
791
792                                         // Hide loading message
793                                         $.mobile.hidePageLoadingMsg();
794                                 };
795                 }
796
797                 // Reset base to the default document base.
798                 if ( base ) {
799                         base.reset();
800                 }
801
802                 if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) {
803                         deferred.reject( absUrl, options );
804                 } else {
805                         // Load the new page.
806                         $.ajax({
807                                 url: fileUrl,
808                                 type: settings.type,
809                                 data: settings.data,
810                                 dataType: "html",
811                                 success: function( html, textStatus, xhr ) {
812                                         //pre-parse html to check for a data-url,
813                                         //use it as the new fileUrl, base path, etc
814                                         var all = $( "<div></div>" ),
815
816                                                 //page title regexp
817                                                 newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,
818
819                                                 // TODO handle dialogs again
820                                                 pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
821                                                 dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );
822
823
824                                         // data-url must be provided for the base tag so resource requests can be directed to the
825                                         // correct url. loading into a temprorary element makes these requests immediately
826                                         if ( pageElemRegex.test( html ) &&
827                                                         RegExp.$1 &&
828                                                         dataUrlRegex.test( RegExp.$1 ) &&
829                                                         RegExp.$1 ) {
830                                                 url = fileUrl = path.getFilePath( $( "<div>" + RegExp.$1 + "</div>" ).text() );
831                                         }
832
833                                         if ( base ) {
834                                                 base.set( fileUrl );
835                                         }
836
837                                         //workaround to allow scripts to execute when included in page divs
838                                         all.get( 0 ).innerHTML = html;
839                                         page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
840
841                                         //if page elem couldn't be found, create one and insert the body element's contents
842                                         if ( !page.length ) {
843                                                 page = $( "<div data-" + $.mobile.ns + "role='page'>" + html.split( /<\/?body[^>]*>/gmi )[1] + "</div>" );
844                                         }
845
846                                         if ( newPageTitle && !page.jqmData( "title" ) ) {
847                                                 if ( ~newPageTitle.indexOf( "&" ) ) {
848                                                         newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
849                                                 }
850                                                 page.jqmData( "title", newPageTitle );
851                                         }
852
853                                         //rewrite src and href attrs to use a base url
854                                         if ( !$.support.dynamicBaseTag ) {
855                                                 var newPath = path.get( fileUrl );
856                                                 page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
857                                                         var thisAttr = $( this ).is( '[href]' ) ? 'href' :
858                                                                         $( this ).is( '[src]' ) ? 'src' : 'action',
859                                                                 thisUrl = $( this ).attr( thisAttr );
860
861                                                         // XXX_jblas: We need to fix this so that it removes the document
862                                                         //            base URL, and then prepends with the new page URL.
863                                                         //if full path exists and is same, chop it - helps IE out
864                                                         thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );
865
866                                                         if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
867                                                                 $( this ).attr( thisAttr, newPath + thisUrl );
868                                                         }
869                                                 });
870                                         }
871
872                                         //append to page and enhance
873                                         // TODO taging a page with external to make sure that embedded pages aren't removed
874                                         //      by the various page handling code is bad. Having page handling code in many
875                                         //      places is bad. Solutions post 1.0
876                                         page
877                                                 .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
878                                                 .attr( "data-" + $.mobile.ns + "external-page", true )
879                                                 .appendTo( settings.pageContainer );
880
881                                         // wait for page creation to leverage options defined on widget
882                                         page.one( 'pagecreate', $.mobile._bindPageRemove );
883
884                                         enhancePage( page, settings.role );
885
886                                         // Enhancing the page may result in new dialogs/sub pages being inserted
887                                         // into the DOM. If the original absUrl refers to a sub-page, that is the
888                                         // real page we are interested in.
889                                         if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
890                                                 page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
891                                         }
892
893                                         //bind pageHide to removePage after it's hidden, if the page options specify to do so
894
895                                         // Remove loading message.
896                                         if ( settings.showLoadMsg ) {
897                                                 hideMsg();
898                                         }
899
900                                         // Add the page reference and xhr to our triggerData.
901                                         triggerData.xhr = xhr;
902                                         triggerData.textStatus = textStatus;
903                                         triggerData.page = page;
904
905                                         // Let listeners know the page loaded successfully.
906                                         settings.pageContainer.trigger( "pageload", triggerData );
907
908                                         deferred.resolve( absUrl, options, page, dupCachedPage );
909                                 },
910                                 error: function( xhr, textStatus, errorThrown ) {
911                                         //set base back to current path
912                                         if ( base ) {
913                                                 base.set( path.get() );
914                                         }
915
916                                         // Add error info to our triggerData.
917                                         triggerData.xhr = xhr;
918                                         triggerData.textStatus = textStatus;
919                                         triggerData.errorThrown = errorThrown;
920
921                                         var plfEvent = new $.Event( "pageloadfailed" );
922
923                                         // Let listeners know the page load failed.
924                                         settings.pageContainer.trigger( plfEvent, triggerData );
925
926                                         // If the default behavior is prevented, stop here!
927                                         // Note that it is the responsibility of the listener/handler
928                                         // that called preventDefault(), to resolve/reject the
929                                         // deferred object within the triggerData.
930                                         if ( plfEvent.isDefaultPrevented() ) {
931                                                 return;
932                                         }
933
934                                         // Remove loading message.
935                                         if ( settings.showLoadMsg ) {
936
937                                                 // Remove loading message.
938                                                 hideMsg();
939
940                                                 // show error message
941                                                 $.mobile.showPageLoadingMsg( $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true );
942
943                                                 // hide after delay
944                                                 setTimeout( $.mobile.hidePageLoadingMsg, 1500 );
945                                         }
946
947                                         deferred.reject( absUrl, options );
948                                 }
949                         });
950                 }
951
952                 return deferred.promise();
953         };
954
955         $.mobile.loadPage.defaults = {
956                 type: "get",
957                 data: undefined,
958                 reloadPage: false,
959                 role: undefined, // By default we rely on the role defined by the @data-role attribute.
960                 showLoadMsg: false,
961                 pageContainer: undefined,
962                 loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message.
963         };
964
965         // Show a specific page in the page container.
966         $.mobile.changePage = function( toPage, options ) {
967                 // If we are in the midst of a transition, queue the current request.
968                 // We'll call changePage() once we're done with the current transition to
969                 // service the request.
970                 if ( isPageTransitioning ) {
971                         pageTransitionQueue.unshift( arguments );
972                         return;
973                 }
974
975                 var settings = $.extend( {}, $.mobile.changePage.defaults, options );
976
977                 // Make sure we have a pageContainer to work with.
978                 settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
979
980                 // Make sure we have a fromPage.
981                 settings.fromPage = settings.fromPage || $.mobile.activePage;
982
983                 var mpc = settings.pageContainer,
984                         pbcEvent = new $.Event( "pagebeforechange" ),
985                         triggerData = { toPage: toPage, options: settings };
986
987                 // Let listeners know we're about to change the current page.
988                 mpc.trigger( pbcEvent, triggerData );
989
990                 // If the default behavior is prevented, stop here!
991                 if ( pbcEvent.isDefaultPrevented() ) {
992                         return;
993                 }
994
995                 // We allow "pagebeforechange" observers to modify the toPage in the trigger
996                 // data to allow for redirects. Make sure our toPage is updated.
997
998                 toPage = triggerData.toPage;
999
1000                 // Set the isPageTransitioning flag to prevent any requests from
1001                 // entering this method while we are in the midst of loading a page
1002                 // or transitioning.
1003
1004                 isPageTransitioning = true;
1005
1006                 // If the caller passed us a url, call loadPage()
1007                 // to make sure it is loaded into the DOM. We'll listen
1008                 // to the promise object it returns so we know when
1009                 // it is done loading or if an error ocurred.
1010                 if ( typeof toPage === "string" ) {
1011                         $.mobile.loadPage( toPage, settings )
1012                                 .done(function( url, options, newPage, dupCachedPage ) {
1013                                         isPageTransitioning = false;
1014                                         options.duplicateCachedPage = dupCachedPage;
1015                                         $.mobile.changePage( newPage, options );
1016                                 })
1017                                 .fail(function( url, options ) {
1018                                         isPageTransitioning = false;
1019
1020                                         //clear out the active button state
1021                                         removeActiveLinkClass( true );
1022
1023                                         //release transition lock so navigation is free again
1024                                         releasePageTransitionLock();
1025                                         settings.pageContainer.trigger( "pagechangefailed", triggerData );
1026                                 });
1027                         return;
1028                 }
1029
1030                 // If we are going to the first-page of the application, we need to make
1031                 // sure settings.dataUrl is set to the application document url. This allows
1032                 // us to avoid generating a document url with an id hash in the case where the
1033                 // first-page of the document has an id attribute specified.
1034                 if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
1035                         settings.dataUrl = documentUrl.hrefNoHash;
1036                 }
1037
1038                 // The caller passed us a real page DOM element. Update our
1039                 // internal state and then trigger a transition to the page.
1040                 var fromPage = settings.fromPage,
1041                         url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ),
1042                         // The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path
1043                         pageUrl = url,
1044                         fileUrl = path.getFilePath( url ),
1045                         active = urlHistory.getActive(),
1046                         activeIsInitialPage = urlHistory.activeIndex === 0,
1047                         historyDir = 0,
1048                         pageTitle = document.title,
1049                         isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog";
1050
1051                 // By default, we prevent changePage requests when the fromPage and toPage
1052                 // are the same element, but folks that generate content manually/dynamically
1053                 // and reuse pages want to be able to transition to the same page. To allow
1054                 // this, they will need to change the default value of allowSamePageTransition
1055                 // to true, *OR*, pass it in as an option when they manually call changePage().
1056                 // It should be noted that our default transition animations assume that the
1057                 // formPage and toPage are different elements, so they may behave unexpectedly.
1058                 // It is up to the developer that turns on the allowSamePageTransitiona option
1059                 // to either turn off transition animations, or make sure that an appropriate
1060                 // animation transition is used.
1061                 if ( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) {
1062                         isPageTransitioning = false;
1063                         mpc.trigger( "pagechange", triggerData );
1064
1065                         // Even if there is no page change to be done, we should keep the urlHistory in sync with the hash changes
1066                         if ( settings.fromHashChange ) {
1067                                 urlHistory.directHashChange({
1068                                         currentUrl:     url,
1069                                         isBack:         function() {},
1070                                         isForward:      function() {}
1071                                 });
1072                         }
1073
1074                         return;
1075                 }
1076
1077                 // We need to make sure the page we are given has already been enhanced.
1078                 enhancePage( toPage, settings.role );
1079
1080                 // If the changePage request was sent from a hashChange event, check to see if the
1081                 // page is already within the urlHistory stack. If so, we'll assume the user hit
1082                 // the forward/back button and will try to match the transition accordingly.
1083                 if ( settings.fromHashChange ) {
1084                         urlHistory.directHashChange({
1085                                 currentUrl:     url,
1086                                 isBack:         function() { historyDir = -1; },
1087                                 isForward:      function() { historyDir = 1; }
1088                         });
1089                 }
1090
1091                 // Kill the keyboard.
1092                 // XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
1093                 //            we should be tracking focus with a delegate() handler so we already have
1094                 //            the element in hand at this point.
1095                 // Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
1096                 // is undefined when we are in an IFrame.
1097                 try {
1098                         if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== 'body' ) {
1099                                 $( document.activeElement ).blur();
1100                         } else {
1101                                 $( "input:focus, textarea:focus, select:focus" ).blur();
1102                         }
1103                 } catch( e ) {}
1104
1105                 // Record whether we are at a place in history where a dialog used to be - if so, do not add a new history entry and do not change the hash either
1106                 var alreadyThere = false;
1107
1108                 // If we're displaying the page as a dialog, we don't want the url
1109                 // for the dialog content to be used in the hash. Instead, we want
1110                 // to append the dialogHashKey to the url of the current page.
1111                 if ( isDialog && active ) {
1112                         // on the initial page load active.url is undefined and in that case should
1113                         // be an empty string. Moving the undefined -> empty string back into
1114                         // urlHistory.addNew seemed imprudent given undefined better represents
1115                         // the url state
1116
1117                         // If we are at a place in history that once belonged to a dialog, reuse
1118                         // this state without adding to urlHistory and without modifying the hash.
1119                         // However, if a dialog is already displayed at this point, and we're
1120                         // about to display another dialog, then we must add another hash and
1121                         // history entry on top so that one may navigate back to the original dialog
1122                         if ( active.url.indexOf( dialogHashKey ) > -1 && !$.mobile.activePage.is( ".ui-dialog" ) ) {
1123                                 settings.changeHash = false;
1124                                 alreadyThere = true;
1125                         }
1126
1127                         // Normally, we tack on a dialog hash key, but if this is the location of a stale dialog,
1128                         // we reuse the URL from the entry
1129                         url = ( active.url || "" ) + ( alreadyThere ? "" : dialogHashKey );
1130
1131                         // tack on another dialogHashKey if this is the same as the initial hash
1132                         // this makes sure that a history entry is created for this dialog
1133                         if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
1134                                 url += dialogHashKey;
1135                         }
1136                 }
1137
1138                 // Set the location hash.
1139                 if ( settings.changeHash !== false && url ) {
1140                         //disable hash listening temporarily
1141                         urlHistory.ignoreNextHashChange = true;
1142                         //update hash and history
1143                         path.set( url );
1144                 }
1145
1146                 // if title element wasn't found, try the page div data attr too
1147                 // If this is a deep-link or a reload ( active === undefined ) then just use pageTitle
1148                 var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).getEncodedText();
1149                 if ( !!newPageTitle && pageTitle === document.title ) {
1150                         pageTitle = newPageTitle;
1151                 }
1152                 if ( !toPage.jqmData( "title" ) ) {
1153                         toPage.jqmData( "title", pageTitle );
1154                 }
1155
1156                 // Make sure we have a transition defined.
1157                 settings.transition = settings.transition ||
1158                         ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) ||
1159                         ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
1160
1161                 //add page to history stack if it's not back or forward
1162                 if ( !historyDir ) {
1163                         // Overwrite the current entry if it's a leftover from a dialog
1164                         if ( alreadyThere ) {
1165                                 urlHistory.activeIndex = Math.max( 0, urlHistory.activeIndex - 1 );
1166                         }
1167                         urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role );
1168                 }
1169
1170                 //set page title
1171                 document.title = urlHistory.getActive().title;
1172
1173                 //set "toPage" as activePage
1174                 $.mobile.activePage = toPage;
1175
1176                 // If we're navigating back in the URL history, set reverse accordingly.
1177                 settings.reverse = settings.reverse || historyDir < 0;
1178
1179                 transitionPages( toPage, fromPage, settings.transition, settings.reverse )
1180                         .done(function( name, reverse, $to, $from, alreadyFocused ) {
1181                                 removeActiveLinkClass();
1182
1183                                 //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
1184                                 if ( settings.duplicateCachedPage ) {
1185                                         settings.duplicateCachedPage.remove();
1186                                 }
1187
1188                                 // Send focus to the newly shown page. Moved from promise .done binding in transitionPages
1189                                 // itself to avoid ie bug that reports offsetWidth as > 0 (core check for visibility)
1190                                 // despite visibility: hidden addresses issue #2965
1191                                 // https://github.com/jquery/jquery-mobile/issues/2965
1192                                 if ( !alreadyFocused ) {
1193                                         $.mobile.focusPage( toPage );
1194                                 }
1195
1196                                 releasePageTransitionLock();
1197
1198                                 // Let listeners know we're all done changing the current page.
1199                                 mpc.trigger( "pagechange", triggerData );
1200                         });
1201         };
1202
1203         $.mobile.changePage.defaults = {
1204                 transition: undefined,
1205                 reverse: false,
1206                 changeHash: true,
1207                 fromHashChange: false,
1208                 role: undefined, // By default we rely on the role defined by the @data-role attribute.
1209                 duplicateCachedPage: undefined,
1210                 pageContainer: undefined,
1211                 showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
1212                 dataUrl: undefined,
1213                 fromPage: undefined,
1214                 allowSamePageTransition: false
1215         };
1216
1217 /* Event Bindings - hashchange, submit, and click */
1218         function findClosestLink( ele )
1219         {
1220                 while ( ele ) {
1221                         // Look for the closest element with a nodeName of "a".
1222                         // Note that we are checking if we have a valid nodeName
1223                         // before attempting to access it. This is because the
1224                         // node we get called with could have originated from within
1225                         // an embedded SVG document where some symbol instance elements
1226                         // don't have nodeName defined on them, or strings are of type
1227                         // SVGAnimatedString.
1228                         if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
1229                                 break;
1230                         }
1231                         ele = ele.parentNode;
1232                 }
1233                 return ele;
1234         }
1235
1236         // The base URL for any given element depends on the page it resides in.
1237         function getClosestBaseUrl( ele )
1238         {
1239                 // Find the closest page and extract out its url.
1240                 var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ),
1241                         base = documentBase.hrefNoHash;
1242
1243                 if ( !url || !path.isPath( url ) ) {
1244                         url = base;
1245                 }
1246
1247                 return path.makeUrlAbsolute( url, base);
1248         }
1249
1250         //The following event bindings should be bound after mobileinit has been triggered
1251         //the following deferred is resolved in the init file
1252         $.mobile.navreadyDeferred = $.Deferred();
1253         $.mobile.navreadyDeferred.done(function() {
1254                 //bind to form submit events, handle with Ajax
1255                 $( document ).delegate( "form", "submit", function( event ) {
1256                         var $this = $( this );
1257
1258                         if ( !$.mobile.ajaxEnabled ||
1259                                         // test that the form is, itself, ajax false
1260                                         $this.is( ":jqmData(ajax='false')" ) ||
1261                                         // test that $.mobile.ignoreContentEnabled is set and
1262                                         // the form or one of it's parents is ajax=false
1263                                         !$this.jqmHijackable().length ) {
1264                                 return;
1265                         }
1266
1267                         var type = $this.attr( "method" ),
1268                                 target = $this.attr( "target" ),
1269                                 url = $this.attr( "action" );
1270
1271                         // If no action is specified, browsers default to using the
1272                         // URL of the document containing the form. Since we dynamically
1273                         // pull in pages from external documents, the form should submit
1274                         // to the URL for the source document of the page containing
1275                         // the form.
1276                         if ( !url ) {
1277                                 // Get the @data-url for the page containing the form.
1278                                 url = getClosestBaseUrl( $this );
1279                                 if ( url === documentBase.hrefNoHash ) {
1280                                         // The url we got back matches the document base,
1281                                         // which means the page must be an internal/embedded page,
1282                                         // so default to using the actual document url as a browser
1283                                         // would.
1284                                         url = documentUrl.hrefNoSearch;
1285                                 }
1286                         }
1287
1288                         url = path.makeUrlAbsolute(  url, getClosestBaseUrl( $this ) );
1289
1290                         if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) || target ) {
1291                                 return;
1292                         }
1293
1294                         $.mobile.changePage(
1295                                 url,
1296                                 {
1297                                         type:           type && type.length && type.toLowerCase() || "get",
1298                                         data:           $this.serialize(),
1299                                         transition:     $this.jqmData( "transition" ),
1300                                         reverse:        $this.jqmData( "direction" ) === "reverse",
1301                                         reloadPage:     true
1302                                 }
1303                         );
1304                         event.preventDefault();
1305                 });
1306
1307                 //add active state on vclick
1308                 $( document ).bind( "vclick", function( event ) {
1309                         // if this isn't a left click we don't care. Its important to note
1310                         // that when the virtual event is generated it will create the which attr
1311                         if ( event.which > 1 || !$.mobile.linkBindingEnabled ) {
1312                                 return;
1313                         }
1314
1315                         var link = findClosestLink( event.target );
1316
1317                         // split from the previous return logic to avoid find closest where possible
1318                         // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
1319                         // can be avoided
1320                         if ( !$( link ).jqmHijackable().length ) {
1321                                 return;
1322                         }
1323
1324                         if ( link ) {
1325                                 if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) {
1326                                         removeActiveLinkClass( true );
1327                                         $activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" );
1328                                         $activeClickedLink.addClass( $.mobile.activeBtnClass );
1329                                 }
1330                         }
1331                 });
1332
1333                 // click routing - direct to HTTP or Ajax, accordingly
1334                 $( document ).bind( "click", function( event ) {
1335                         if ( !$.mobile.linkBindingEnabled ) {
1336                                 return;
1337                         }
1338
1339                         var link = findClosestLink( event.target ), $link = $( link ), httpCleanup;
1340
1341                         // If there is no link associated with the click or its not a left
1342                         // click we want to ignore the click
1343                         // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
1344                         // can be avoided
1345                         if ( !link || event.which > 1 || !$link.jqmHijackable().length ) {
1346                                 return;
1347                         }
1348
1349                         //remove active link class if external (then it won't be there if you come back)
1350                         httpCleanup = function() {
1351                                 window.setTimeout(function() { removeActiveLinkClass( true ); }, 200 );
1352                         };
1353
1354                         //if there's a data-rel=back attr, go back in history
1355                         if ( $link.is( ":jqmData(rel='back')" ) ) {
1356                                 $.mobile.back();
1357                                 return false;
1358                         }
1359
1360                         var baseUrl = getClosestBaseUrl( $link ),
1361
1362                                 //get href, if defined, otherwise default to empty hash
1363                                 href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
1364
1365                         //if ajax is disabled, exit early
1366                         if ( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ) {
1367                                 httpCleanup();
1368                                 //use default click handling
1369                                 return;
1370                         }
1371
1372                         // XXX_jblas: Ideally links to application pages should be specified as
1373                         //            an url to the application document with a hash that is either
1374                         //            the site relative path or id to the page. But some of the
1375                         //            internal code that dynamically generates sub-pages for nested
1376                         //            lists and select dialogs, just write a hash in the link they
1377                         //            create. This means the actual URL path is based on whatever
1378                         //            the current value of the base tag is at the time this code
1379                         //            is called. For now we are just assuming that any url with a
1380                         //            hash in it is an application page reference.
1381                         if ( href.search( "#" ) !== -1 ) {
1382                                 href = href.replace( /[^#]*#/, "" );
1383                                 if ( !href ) {
1384                                         //link was an empty hash meant purely
1385                                         //for interaction, so we ignore it.
1386                                         event.preventDefault();
1387                                         return;
1388                                 } else if ( path.isPath( href ) ) {
1389                                         //we have apath so make it the href we want to load.
1390                                         href = path.makeUrlAbsolute( href, baseUrl );
1391                                 } else {
1392                                         //we have a simple id so use the documentUrl as its base.
1393                                         href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
1394                                 }
1395                         }
1396
1397                                 // Should we handle this link, or let the browser deal with it?
1398                         var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ),
1399
1400                                 // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
1401                                 // requests if the document doing the request was loaded via the file:// protocol.
1402                                 // This is usually to allow the application to "phone home" and fetch app specific
1403                                 // data. We normally let the browser handle external/cross-domain urls, but if the
1404                                 // allowCrossDomainPages option is true, we will allow cross-domain http/https
1405                                 // requests to go through our page loading logic.
1406
1407                                 //check for protocol or rel and its not an embedded page
1408                                 //TODO overlap in logic from isExternal, rel=external check should be
1409                                 //     moved into more comprehensive isExternalLink
1410                                 isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) );
1411
1412                         if ( isExternal ) {
1413                                 httpCleanup();
1414                                 //use default click handling
1415                                 return;
1416                         }
1417
1418                         //use ajax
1419                         var transition = $link.jqmData( "transition" ),
1420                                 reverse = $link.jqmData( "direction" ) === "reverse" ||
1421                                                         // deprecated - remove by 1.0
1422                                                         $link.jqmData( "back" ),
1423
1424                                 //this may need to be more specific as we use data-rel more
1425                                 role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
1426
1427                         $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } );
1428                         event.preventDefault();
1429                 });
1430
1431                 //prefetch pages when anchors with data-prefetch are encountered
1432                 $( document ).delegate( ".ui-page", "pageshow.prefetch", function() {
1433                         var urls = [];
1434                         $( this ).find( "a:jqmData(prefetch)" ).each(function() {
1435                                 var $link = $( this ),
1436                                         url = $link.attr( "href" );
1437
1438                                 if ( url && $.inArray( url, urls ) === -1 ) {
1439                                         urls.push( url );
1440
1441                                         $.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ) } );
1442                                 }
1443                         });
1444                 });
1445
1446                 $.mobile._handleHashChange = function( hash ) {
1447                         //find first page via hash
1448                         var to = path.stripHash( hash ),
1449                                 //transition is false if it's the first page, undefined otherwise (and may be overridden by default)
1450                                 transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined,
1451
1452                                 // "navigate" event fired to allow others to take advantage of the more robust hashchange handling
1453                                 navEvent = new $.Event( "navigate" ),
1454
1455                                 // default options for the changPage calls made after examining the current state
1456                                 // of the page and the hash
1457                                 changePageOptions = {
1458                                         transition: transition,
1459                                         changeHash: false,
1460                                         fromHashChange: true
1461                                 };
1462
1463                         if ( 0 === urlHistory.stack.length ) {
1464                                 urlHistory.initialDst = to;
1465                         }
1466
1467                         // We should probably fire the "navigate" event from those places that make calls to _handleHashChange,
1468                         // and have _handleHashChange hook into the "navigate" event instead of triggering it here
1469                         $.mobile.pageContainer.trigger( navEvent );
1470                         if ( navEvent.isDefaultPrevented() ) {
1471                                 return;
1472                         }
1473
1474                         //if listening is disabled (either globally or temporarily), or it's a dialog hash
1475                         if ( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) {
1476                                 urlHistory.ignoreNextHashChange = false;
1477                                 return;
1478                         }
1479
1480                         // special case for dialogs
1481                         if ( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 && urlHistory.initialDst !== to ) {
1482
1483                                 // If current active page is not a dialog skip the dialog and continue
1484                                 // in the same direction
1485                                 if ( !$.mobile.activePage.is( ".ui-dialog" ) ) {
1486                                         //determine if we're heading forward or backward and continue accordingly past
1487                                         //the current dialog
1488                                         urlHistory.directHashChange({
1489                                                 currentUrl: to,
1490                                                 isBack: function() { $.mobile.back(); },
1491                                                 isForward: function() { window.history.forward(); }
1492                                         });
1493
1494                                         // prevent changePage()
1495                                         return;
1496                                 } else {
1497                                         // if the current active page is a dialog and we're navigating
1498                                         // to a dialog use the dialog objected saved in the stack
1499                                         urlHistory.directHashChange({
1500                                                 currentUrl: to,
1501
1502                                                 // regardless of the direction of the history change
1503                                                 // do the following
1504                                                 either: function( isBack ) {
1505                                                         var active = $.mobile.urlHistory.getActive();
1506
1507                                                         to = active.pageUrl;
1508
1509                                                         // make sure to set the role, transition and reversal
1510                                                         // as most of this is lost by the domCache cleaning
1511                                                         $.extend( changePageOptions, {
1512                                                                 role: active.role,
1513                                                                 transition: active.transition,
1514                                                                 reverse: isBack
1515                                                         });
1516                                                 }
1517                                         });
1518                                 }
1519                         }
1520
1521                         //if to is defined, load it
1522                         if ( to ) {
1523                                 // At this point, 'to' can be one of 3 things, a cached page element from
1524                                 // a history stack entry, an id, or site-relative/absolute URL. If 'to' is
1525                                 // an id, we need to resolve it against the documentBase, not the location.href,
1526                                 // since the hashchange could've been the result of a forward/backward navigation
1527                                 // that crosses from an external page/dialog to an internal page/dialog.
1528                                 to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to;
1529
1530                                 // If we're about to go to an initial URL that contains a reference to a non-existent
1531                                 // internal page, go to the first page instead. We know that the initial hash refers to a
1532                                 // non-existent page, because the initial hash did not end up in the initial urlHistory entry
1533                                 if ( to === path.makeUrlAbsolute( '#' + urlHistory.initialDst, documentBase ) &&
1534                                         urlHistory.stack.length && urlHistory.stack[0].url !== urlHistory.initialDst.replace( dialogHashKey, "" ) ) {
1535                                         to = $.mobile.firstPage;
1536                                 }
1537                                 $.mobile.changePage( to, changePageOptions );
1538                         }       else {
1539                                 //there's no hash, go to the first page in the dom
1540                                 $.mobile.changePage( $.mobile.firstPage, changePageOptions );
1541                         }
1542                 };
1543
1544                 //hashchange event handler
1545                 $window.bind( "hashchange", function( e, triggered ) {
1546                         // Firefox auto-escapes the location.hash as for v13 but
1547                         // leaves the href untouched
1548                         $.mobile._handleHashChange( path.parseLocation().hash );
1549                 });
1550
1551                 //set page min-heights to be device specific
1552                 $( document ).bind( "pageshow", resetActivePageHeight );
1553                 $( window ).bind( "throttledresize", resetActivePageHeight );
1554
1555         });//navreadyDeferred done callback
1556
1557 })( jQuery );
1558 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
1559 });
1560 //>>excludeEnd("jqmBuildExclude");