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