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