--- a/js/jquery.mobile-1.0b1.js +++ b/js/jquery.mobile-1.0b1.js @@ -1,5 +1,5 @@ /*! - * jQuery Mobile v1.0a4 + * jQuery Mobile v1.0b1 * http://jquerymobile.com/ * * Copyright 2010, jQuery Project @@ -396,7 +396,8 @@ $(document).bind("mobileinit.htmlclass", function(){ /* bind to orientationchange and resize to add classes to HTML element for min/max breakpoints and orientation */ - $window.bind("orientationchange.htmlclass resize.htmlclass", function(event){ + var ev = $.support.orientation; + $window.bind("orientationchange.htmlclass throttledResize.htmlclass", function(event){ //add orientation class to HTML element on flip/resize. if(event.orientation){ $html.removeClass( "portrait landscape" ).addClass( event.orientation ); @@ -422,77 +423,78 @@ * Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. * Note: Code is in draft form and is subject to change */ -(function($, undefined ) { - - - -var fakeBody = $( "<body>" ).prependTo( "html" ), - fbCSS = fakeBody[0].style, - vendors = ['webkit','moz','o'], - webos = window.palmGetResource || window.PalmServiceBridge, //only used to rule out scrollTop +( function( $, undefined ) { + +var fakeBody = $( "<body>" ).prependTo( "html" ), + fbCSS = fakeBody[ 0 ].style, + vendors = [ "webkit", "moz", "o" ], + webos = "palmGetResource" in window, //only used to rule out scrollTop bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB -//thx Modernizr -function propExists( prop ){ - var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), - props = (prop + ' ' + vendors.join(uc_prop + ' ') + uc_prop).split(' '); - for(var v in props){ - if( fbCSS[ v ] !== undefined ){ +// thx Modernizr +function propExists( prop ){ + var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), + props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ); + for( var v in props ){ + if( fbCSS[ v ] !== undefined ){ return true; } } -}; - -//test for dynamic-updating base tag support (allows us to avoid href,src attr rewriting) +} + +// test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting ) function baseTagTest(){ - var fauxBase = location.protocol + '//' + location.host + location.pathname + "ui-dir/", - base = $("head base"), + var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/", + base = $( "head base" ), fauxEle = null, - href = ''; - if (!base.length) { - base = fauxEle = $("<base>", {"href": fauxBase}).appendTo("head"); + href = ""; + if ( !base.length ) { + base = fauxEle = $( "<base>", { "href": fauxBase} ).appendTo( "head" ); } else { - href = base.attr("href"); + href = base.attr( "href" ); } - var link = $( "<a href='testurl'></a>" ).prependTo( fakeBody ), - rebase = link[0].href; - base[0].href = href ? href : location.pathname; - if (fauxEle) { + var link = $( "<a href='testurl'></a>" ).prependTo( fakeBody ), + rebase = link[ 0 ].href; + base[ 0 ].href = href ? href : location.pathname; + if ( fauxEle ) { fauxEle.remove(); } - return rebase.indexOf(fauxBase) === 0; -}; - - -//non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683 -//allows for inclusion of IE 6+, including Windows Mobile 7 + return rebase.indexOf( fauxBase ) === 0; +} + + +// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683 +// allows for inclusion of IE 6+, including Windows Mobile 7 $.mobile.browser = {}; -$.mobile.browser.ie = (function() { - var v = 3, div = document.createElement('div'), a = div.all || []; - while (div.innerHTML = '<!--[if gt IE '+(++v)+']><br><![endif]-->', a[0]); +$.mobile.browser.ie = ( function() { + var v = 3, + div = document.createElement( "div" ), + a = div.all || []; + while ( div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->", a[ 0 ] ); return v > 4 ? v : !v; -}()); +}() ); + $.extend( $.support, { orientation: "orientation" in window, touch: "ontouchend" in document, cssTransitions: "WebKitTransitionEvent" in window, pushState: !!history.pushState, - mediaquery: $.mobile.media('only all'), - cssPseudoElement: !!propExists('content'), - boxShadow: !!propExists('boxShadow') && !bb, - scrollTop: ("pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[0]) && !webos, + mediaquery: $.mobile.media( "only all" ), + cssPseudoElement: !!propExists( "content" ), + boxShadow: !!propExists( "boxShadow" ) && !bb, + scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos, dynamicBaseTag: baseTagTest(), - eventCapture: ("addEventListener" in document) // This is a weak test. We may want to beef this up later. -}); + eventCapture: ( "addEventListener" in document ) // This is a weak test. We may want to beef this up later. +} ); fakeBody.remove(); -//for ruling out shadows via css -if( !$.support.boxShadow ){ $('html').addClass('ui-mobile-nosupport-boxshadow'); } - -})( jQuery );/* +// for ruling out shadows via css +if( !$.support.boxShadow ){ $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" ); } + +} )( jQuery );/* * jQuery Mobile Framework : "mouse" plugin * Copyright (c) jQuery Project * Dual licensed under the MIT or GPL Version 2 licenses. @@ -524,12 +526,10 @@ resetTimerID = 0, startX = 0, startY = 0, - startScrollX = 0, - startScrollY = 0, didScroll = false, clickBlockList = [], blockMouseTriggers = false, - scrollTopSupported = $.support.scrollTop, + blockTouchTriggers = false, eventCaptureSupported = $.support.eventCapture, $document = $(document), nextTouchID = 1, @@ -569,10 +569,12 @@ } if (t.search(/^touch/) !== -1){ - var ne = getNativeEvent(oe); - if (typeof ne.touches !== "undefined" && ne.touches[0]){ - var touch = ne.touches[0]; - for (var i = 0; i < touchEventProps.length; i++){ + var ne = getNativeEvent(oe), + t = ne.touches, + ct = ne.changedTouches, + touch = (t && t.length) ? t[0] : ((ct && ct.length) ? ct[0] : undefined); + if (touch){ + for (var i = 0, len = touchEventProps.length; i < len; i++){ var prop = touchEventProps[i]; event[prop] = touch[prop]; } @@ -585,62 +587,38 @@ function getVirtualBindingFlags(element) { var flags = {}; - var $ele = $(element); - while ($ele && $ele.length){ - var b = $ele.data(dataPropertyName); + while (element){ + var b = $.data(element, dataPropertyName); for (var k in b) { if (b[k]){ flags[k] = flags.hasVirtualBinding = true; } } - $ele = $ele.parent(); + element = element.parentNode; } return flags; } function getClosestElementWithVirtualBinding(element, eventType) { - var $ele = $(element); - while ($ele && $ele.length){ - var b = $ele.data(dataPropertyName); + while (element){ + var b = $.data(element, dataPropertyName); if (b && (!eventType || b[eventType])) { - return $ele; - } - $ele = $ele.parent(); + return element; + } + element = element.parentNode; } return null; } function enableTouchBindings() { - if (!activeDocHandlers["touchbindings"]){ - $document.bind("touchend", handleTouchEnd) - - // On touch platforms, touching the screen and then dragging your finger - // causes the window content to scroll after some distance threshold is - // exceeded. On these platforms, a scroll prevents a click event from being - // dispatched, and on some platforms, even the touchend is suppressed. To - // mimic the suppression of the click event, we need to watch for a scroll - // event. Unfortunately, some platforms like iOS don't dispatch scroll - // events until *AFTER* the user lifts their finger (touchend). This means - // we need to watch both scroll and touchmove events to figure out whether - // or not a scroll happenens before the touchend event is fired. - - .bind("touchmove", handleTouchMove) - .bind("scroll", handleScroll); - - activeDocHandlers["touchbindings"] = 1; - } + blockTouchTriggers = false; } function disableTouchBindings() { - if (activeDocHandlers["touchbindings"]){ - $document.unbind("touchmove", handleTouchMove) - .unbind("touchend", handleTouchEnd) - .unbind("scroll", handleScroll); - activeDocHandlers["touchbindings"] = 0; - } + blockTouchTriggers = true; } function enableMouseBindings() @@ -693,7 +671,7 @@ function mouseEventCallback(event) { - var touchID = $(event.target).data(touchTargetPropertyName); + var touchID = $.data(event.target, touchTargetPropertyName); if (!blockMouseTriggers && (!lastTouchID || lastTouchID !== touchID)){ triggerVirtualEvent("v" + event.type, event); } @@ -708,7 +686,7 @@ if (flags.hasVirtualBinding){ lastTouchID = nextTouchID++; - $(target).data(touchTargetPropertyName, lastTouchID); + $.data(target, touchTargetPropertyName, lastTouchID); clearResetTimer(); @@ -719,11 +697,6 @@ startX = t.pageX; startY = t.pageY; - if (scrollTopSupported){ - startScrollX = window.pageXOffset; - startScrollY = window.pageYOffset; - } - triggerVirtualEvent("vmouseover", event, flags); triggerVirtualEvent("vmousedown", event, flags); } @@ -732,6 +705,10 @@ function handleScroll(event) { + if (blockTouchTriggers){ + return; + } + if (!didScroll){ triggerVirtualEvent("vmousecancel", event, getVirtualBindingFlags(event.target)); } @@ -742,12 +719,15 @@ function handleTouchMove(event) { + if (blockTouchTriggers){ + return; + } + var t = getNativeEvent(event).touches[0]; var didCancel = didScroll, moveThreshold = $.vmouse.moveDistanceThreshold; didScroll = didScroll - || (scrollTopSupported && (startScrollX !== window.pageXOffset || startScrollY !== window.pageYOffset)) || (Math.abs(t.pageX - startX) > moveThreshold || Math.abs(t.pageY - startY) > moveThreshold); var flags = getVirtualBindingFlags(event.target); @@ -760,6 +740,10 @@ function handleTouchEnd(event) { + if (blockTouchTriggers){ + return; + } + disableTouchBindings(); var flags = getVirtualBindingFlags(event.target); @@ -784,9 +768,9 @@ startResetTimer(); } -function hasVirtualBindings($ele) +function hasVirtualBindings(ele) { - var bindings = $ele.data(dataPropertyName), k; + var bindings = $.data(ele, dataPropertyName), k; if (bindings){ for (k in bindings){ if (bindings[k]){ @@ -807,16 +791,14 @@ // If this is the first virtual mouse binding for this element, // add a bindings object to its data. - var $this = $(this); - - if (!hasVirtualBindings($this)){ - $this.data(dataPropertyName, {}); + if (!hasVirtualBindings(this)){ + $.data(this, dataPropertyName, {}); } // If setup is called, we know it is the first binding for this // eventType, so initialize the count for the eventType to zero. - var bindings = $this.data(dataPropertyName); + var bindings = $.data(this, dataPropertyName); bindings[eventType] = true; // If this is the first virtual mouse event for this type, @@ -831,7 +813,7 @@ // for elements unless they actually have handlers registered on them. // To get around this, we register dummy handlers on the elements. - $this.bind(realType, dummyMouseHandler); + $(this).bind(realType, dummyMouseHandler); // For now, if event capture is not supported, we rely on mouse handlers. if (eventCaptureSupported){ @@ -840,7 +822,22 @@ activeDocHandlers["touchstart"] = (activeDocHandlers["touchstart"] || 0) + 1; if (activeDocHandlers["touchstart"] === 1) { - $document.bind("touchstart", handleTouchStart); + $document.bind("touchstart", handleTouchStart) + + .bind("touchend", handleTouchEnd) + + // On touch platforms, touching the screen and then dragging your finger + // causes the window content to scroll after some distance threshold is + // exceeded. On these platforms, a scroll prevents a click event from being + // dispatched, and on some platforms, even the touchend is suppressed. To + // mimic the suppression of the click event, we need to watch for a scroll + // event. Unfortunately, some platforms like iOS don't dispatch scroll + // events until *AFTER* the user lifts their finger (touchend). This means + // we need to watch both scroll and touchmove events to figure out whether + // or not a scroll happenens before the touchend event is fired. + + .bind("touchmove", handleTouchMove) + .bind("scroll", handleScroll); } } }, @@ -860,13 +857,24 @@ --activeDocHandlers["touchstart"]; if (!activeDocHandlers["touchstart"]) { - $document.unbind("touchstart", handleTouchStart); + $document.unbind("touchstart", handleTouchStart) + .unbind("touchmove", handleTouchMove) + .unbind("touchend", handleTouchEnd) + .unbind("scroll", handleScroll); } } var $this = $(this), - bindings = $this.data(dataPropertyName); - bindings[eventType] = false; + bindings = $.data(this, dataPropertyName); + + // teardown may be called when an element was + // removed from the DOM. If this is the case, + // jQuery core may have already stripped the element + // of any data bindings so we need to check it before + // using it. + if (bindings){ + bindings[eventType] = false; + } // Unregister the dummy event handler. @@ -875,7 +883,7 @@ // If this is the last virtual mouse binding on the // element, remove the binding data from the element. - if (!hasVirtualBindings($this)){ + if (!hasVirtualBindings(this)){ $this.removeData(dataPropertyName); } } @@ -932,7 +940,7 @@ for (var i = 0; i < cnt; i++) { var o = clickBlockList[i], touchID = 0; - if ((ele === target && Math.abs(o.x - x) < threshold && Math.abs(o.y - y) < threshold) || $(ele).data(touchTargetPropertyName) === o.touchID){ + if ((ele === target && Math.abs(o.x - x) < threshold && Math.abs(o.y - y) < threshold) || $.data(ele, touchTargetPropertyName) === o.touchID){ // XXX: We may want to consider removing matches from the block list // instead of waiting for the reset timer to fire. e.preventDefault(); @@ -945,7 +953,8 @@ } }, true); } -})(jQuery, window, document);/* +})(jQuery, window, document); +/* * jQuery Mobile Framework : events * Copyright (c) jQuery Project * Dual licensed under the MIT or GPL Version 2 licenses. @@ -954,7 +963,7 @@ (function($, undefined ) { // add new event shortcuts -$.each( "touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split( " " ), function( i, name ) { +$.each( "touchstart touchmove touchend orientationchange throttledresize tap taphold swipe swipeleft swiperight scrollstart scrollstop".split( " " ), function( i, name ) { $.fn[ name ] = function( fn ) { return fn ? this.bind( name, fn ) : this.trigger( name ); }; @@ -1028,7 +1037,7 @@ function clearTapHandlers() { touching = false; clearTimeout(timer); - $(this).unbind("vmouseclick", clickHandler).unbind("vmousecancel", clearTapHandlers); + $this.unbind("vclick", clickHandler).unbind("vmousecancel", clearTapHandlers); } function clickHandler(event) { @@ -1129,7 +1138,7 @@ // Because the orientationchange event doesn't exist, simulate the // event by testing window dimensions on resize. - win.bind( "resize", handler ); + win.bind( "throttledresize", handler ); }, teardown: function(){ // If the event is not supported natively, return false so that @@ -1138,7 +1147,7 @@ // Because the orientationchange event doesn't exist, unbind the // resize event handler. - win.unbind( "resize", handler ); + win.unbind( "throttledresize", handler ); }, add: function( handleObj ) { // Save a reference to the bound event handler. @@ -1169,12 +1178,47 @@ // Get the current page orientation. This method is exposed publicly, should it // be needed, as jQuery.event.special.orientationchange.orientation() - special_event.orientation = get_orientation = function() { + $.event.special.orientationchange.orientation = get_orientation = function() { var elem = document.documentElement; return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape"; }; })(jQuery); + + +// throttled resize event +(function(){ + $.event.special.throttledresize = { + setup: function() { + $( this ).bind( "resize", handler ); + }, + teardown: function(){ + $( this ).unbind( "resize", handler ); + } + }; + + var throttle = 250, + handler = function(){ + curr = ( new Date() ).getTime(); + diff = curr - lastCall; + if( diff >= throttle ){ + lastCall = curr; + $( this ).trigger( "throttledresize" ); + } + else{ + if( heldCall ){ + clearTimeout( heldCall ); + } + //promise a held call will still execute + heldCall = setTimeout( handler, throttle - diff ); + } + }, + lastCall = 0, + heldCall, + curr, + diff; +})(); + $.each({ scrollstop: "scrollstart", @@ -1591,7 +1635,7 @@ $.widget( "mobile.page", $.mobile.widget, { options: { backBtnText: "Back", - addBackBtn: true, + addBackBtn: false, backBtnTheme: null, degradeInputs: { color: false, @@ -1814,7 +1858,7 @@ //hash segment before &ui-page= is used to make Ajax request subPageUrlKey: "ui-page", - //anchor links with a data-rel, or pages with a data-role, that match these selectors will be untrackable in history + //anchor links with a data-rel, or pages with a data-role, that match these selectors will be untrackable in history //(no change in URL, not bookmarkable) nonHistorySelectors: "dialog", @@ -1826,32 +1870,30 @@ //automatically handle clicks and form submissions through Ajax, when same-domain ajaxEnabled: true, + + //When enabled, clicks and taps that result in Ajax page changes will happen slightly sooner on touch devices. + //Also, it will prevent the address bar from appearing on platforms like iOS during page transitions. + //This option has no effect on non-touch devices, but enabling it may interfere with jQuery plugins that bind to click events + useFastClick: true, //automatically load and show pages based on location.hash hashListeningEnabled: true, - // TODO: deprecated - remove at 1.0 - //automatically handle link clicks through Ajax, when possible - ajaxLinksEnabled: true, - - // TODO: deprecated - remove at 1.0 - //automatically handle form submissions through Ajax, when possible - ajaxFormsEnabled: true, - - //set default transition - 'none' for no transitions - defaultTransition: "slide", + //set default page transition - 'none' for no transitions + defaultPageTransition: "slide", + + //minimum scroll distance that will be remembered when returning to a page + minScrollBack: screen.height / 2, + + //set default dialog transition - 'none' for no transitions + defaultDialogTransition: "pop", //show loading message during Ajax requests //if false, message will not appear, but loading classes will still be toggled on html el loadingMessage: "loading", - + //error response message - appears when an Ajax page request fails pageLoadErrorMessage: "Error Loading Page", - - //configure meta viewport tag's content attr: - //note: this feature is deprecated in A4 in favor of adding - //the meta viewport element directly in the markup - metaViewportContent: "width=device-width, minimum-scale=1, maximum-scale=1", //support conditions that must be met in order to proceed //default enhanced qualifications are media query support OR IE 7+ @@ -1897,7 +1939,10 @@ //scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value silentScroll: function( ypos ) { - ypos = ypos || 0; + if( $.type( ypos ) !== "number" ){ + ypos = $.mobile.defaultHomeScroll; + } + // prevent scrollstart and scrollstop events $.event.special.scrollstart.enabled = false; @@ -1909,31 +1954,41 @@ setTimeout(function() { $.event.special.scrollstart.enabled = true; }, 150 ); + }, + + // compile the namespace normalization regex once + normalizeRegex: /-([a-z])/g, + + // take a data attribute property, prepend the namespace + // and then camel case the attribute string + nsNormalize: function(prop){ + if(!prop) return; + + return $.camelCase( $.mobile.ns + prop ); } }); //mobile version of data and removeData and hasData methods //ensures all data is set and retrieved using jQuery Mobile's data namespace - $.fn.jqmData = function( prop, value ){ - return this.data( prop ? $.mobile.ns + prop : prop, value ); - }; - - $.jqmData = function( elem, prop, value ){ - return $.data( elem, prop && $.mobile.ns + prop, value ); - }; - - $.fn.jqmRemoveData = function( prop ){ - return this.removeData( $.mobile.ns + prop ); - }; - - $.jqmRemoveData = function( elem, prop ){ - return $.removeData( elem, prop && $.mobile.ns + prop ); - }; - - $.jqmHasData = function( elem, prop ){ - return $.hasData( elem, prop && $.mobile.ns + prop ); - }; - + $.fn.jqmData = function( prop, value ){ + return this.data( prop ? $.mobile.nsNormalize(prop) : prop, value ); + }; + + $.jqmData = function( elem, prop, value ){ + return $.data( elem, $.mobile.nsNormalize(prop), value ); + }; + + $.fn.jqmRemoveData = function( prop ){ + return this.removeData( $.mobile.nsNormalize(prop) ); + }; + + $.jqmRemoveData = function( elem, prop ){ + return $.removeData( elem, $.mobile.nsNormalize(prop) ); + }; + + $.jqmHasData = function( elem, prop ){ + return $.hasData( elem, $.mobile.nsNormalize(prop) ); + }; // Monkey-patching Sizzle to filter the :jqmData selector var oldFind = $.find; @@ -1960,86 +2015,228 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ -(function($, undefined ) { +( function( $, undefined ) { //define vars for interal use - var $window = $(window), - $html = $('html'), - $head = $('head'), + var $window = $( window ), + $html = $( 'html' ), + $head = $( 'head' ), //url path helpers for use in relative url management path = { + // This scary looking regular expression parses an absolute URL or its relative + // variants (protocol, site, document, query, and hash), into the various + // components (protocol, host, path, query, fragment, etc that make up the + // URL as well as some other commonly used sub-parts. When used with RegExp.exec() + // or String.match, it parses the URL into a results array that looks like this: + // + // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content + // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread + // [2]: http://jblas:password@mycompany.com:8080/mail/inbox + // [3]: http://jblas:password@mycompany.com:8080 + // [4]: http: + // [5]: jblas:password@mycompany.com:8080 + // [6]: jblas:password + // [7]: jblas + // [8]: password + // [9]: mycompany.com:8080 + // [10]: mycompany.com + // [11]: 8080 + // [12]: /mail/inbox + // [13]: /mail/ + // [14]: inbox + // [15]: ?msg=1234&type=unread + // [16]: #msg-content + // + urlParseRE: /^(((([^:\/#\?]+:)?(?:\/\/((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?]+)(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, + + //Parse a URL into a structure that allows easy access to + //all of the URL components by name. + parseUrl: function( url ) { + // If we're passed an object, we'll assume that it is + // a parsed url object and just return it back to the caller. + if ( typeof url === "object" ) { + return url; + } + + var u = url || "", + matches = path.urlParseRE.exec( url ), + results; + if ( matches ) { + // Create an object that allows the caller to access the sub-matches + // by name. Note that IE returns an empty string instead of undefined, + // like all other browsers do, so we normalize everything so its consistent + // no matter what browser we're running on. + results = { + href: matches[0] || "", + hrefNoHash: matches[1] || "", + hrefNoSearch: matches[2] || "", + domain: matches[3] || "", + protocol: matches[4] || "", + authority: matches[5] || "", + username: matches[7] || "", + password: matches[8] || "", + host: matches[9] || "", + hostname: matches[10] || "", + port: matches[11] || "", + pathname: matches[12] || "", + directory: matches[13] || "", + filename: matches[14] || "", + search: matches[15] || "", + hash: matches[16] || "" + }; + } + return results || {}; + }, + + //Turn relPath into an asbolute path. absPath is + //an optional absolute path which describes what + //relPath is relative to. + makePathAbsolute: function( relPath, absPath ) { + if ( relPath && relPath.charAt( 0 ) === "/" ) { + return relPath; + } + + relPath = relPath || ""; + absPath = absPath ? absPath.replace( /^\/|\/?[^\/]*$/g, "" ) : ""; + + var absStack = absPath ? absPath.split( "/" ) : [], + relStack = relPath.split( "/" ); + for ( var i = 0; i < relStack.length; i++ ) { + var d = relStack[ i ]; + switch ( d ) { + case ".": + break; + case "..": + if ( absStack.length ) { + absStack.pop(); + } + break; + default: + absStack.push( d ); + break; + } + } + return "/" + absStack.join( "/" ); + }, + + //Returns true if both urls have the same domain. + isSameDomain: function( absUrl1, absUrl2 ) { + return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain; + }, + + //Returns true for any relative variant. + isRelativeUrl: function( url ) { + // All relative Url variants have one thing in common, no protocol. + return path.parseUrl( url ).protocol === ""; + }, + + //Returns true for an absolute url. + isAbsoluteUrl: function( url ) { + return path.parseUrl( url ).protocol !== ""; + }, + + //Turn the specified realtive URL into an absolute one. This function + //can handle all relative variants (protocol, site, document, query, fragment). + makeUrlAbsolute: function( relUrl, absUrl ) { + if ( !path.isRelativeUrl( relUrl ) ) { + return relUrl; + } + + var relObj = path.parseUrl( relUrl ), + absObj = path.parseUrl( absUrl ), + protocol = relObj.protocol || absObj.protocol, + authority = relObj.authority || absObj.authority, + hasPath = relObj.pathname !== "", + pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), + search = relObj.search || ( !hasPath && absObj.search ) || "", + hash = relObj.hash; + + return protocol + "//" + authority + pathname + search + hash; + }, + + //Add search (aka query) params to the specified url. + addSearchParams: function( url, params ) { + var u = path.parseUrl( url ), + p = ( typeof params === "object" ) ? $.param( params ) : params, + s = u.search || "?"; + return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" ); + }, + + convertUrlToDataUrl: function( absUrl ) { + var u = path.parseUrl( absUrl ); + if ( path.isEmbeddedPage( u ) ) { + return u.hash.replace( /^#/, "" ); + } else if ( path.isSameDomain( u, documentBase ) ) { + return u.hrefNoHash.replace( documentBase.domain, "" ); + } + return absUrl; + }, + //get path from current hash, or from a file path - get: function( newPath ){ - if( newPath === undefined ){ + get: function( newPath ) { + if( newPath === undefined ) { newPath = location.hash; } - return path.stripHash( newPath ).replace(/[^\/]*\.[^\/*]+$/, ''); + return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' ); }, //return the substring of a filepath before the sub-page key, for making a server request - getFilePath: function( path ){ + getFilePath: function( path ) { var splitkey = '&' + $.mobile.subPageUrlKey; return path && path.split( splitkey )[0].split( dialogHashKey )[0]; }, //set location hash to path - set: function( path ){ + set: function( path ) { location.hash = path; }, - //location pathname from intial directory request - origin: '', - - setOrigin: function(){ - path.origin = path.get( location.protocol + '//' + location.host + location.pathname ); + //test if a given url (string) is a path + //NOTE might be exceptionally naive + isPath: function( url ) { + return ( /\// ).test( url ); }, - //prefix a relative url with the current path - // TODO rename to reflect conditional functionality - makeAbsolute: function( url ){ - // only create an absolute path when the hash can be used as one - return path.isPath(window.location.hash) ? path.get() + url : url; + //return a url path with the window's location protocol/hostname/pathname removed + clean: function( url ) { + return url.replace( documentBase.domain, "" ); }, - // test if a given url (string) is a path - // NOTE might be exceptionally naive - isPath: function( url ){ - return /\//.test(url); - }, - - //return a url path with the window's location protocol/hostname/pathname removed - clean: function( url ){ - // Replace the protocol, host, and pathname only once at the beginning of the url to avoid - // problems when it's included as a part of a param - // Also, since all urls are absolute in IE, we need to remove the pathname as well. - var leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + location.pathname); - return url.replace(leadingUrlRootRegex, ""); - }, - //just return the url without an initial # - stripHash: function( url ){ + stripHash: function( url ) { return url.replace( /^#/, "" ); }, + //remove the preceding hash, any query params, and dialog notations + cleanHash: function( hash ) { + return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) ); + }, + //check whether a url is referencing the same domain, or an external domain or different protocol //could be mailto, etc - isExternal: function( url ){ - return path.hasProtocol( path.clean( url ) ); + isExternal: function( url ) { + var u = path.parseUrl( url ); + return u.protocol && u.domain !== documentUrl.domain ? true : false; }, - hasProtocol: function( url ){ - return (/^(:?\w+:)/).test( url ); + hasProtocol: function( url ) { + return ( /^(:?\w+:)/ ).test( url ); }, - //check if the url is relative - isRelative: function( url ){ - return (/^[^\/|#]/).test( url ) && !path.hasProtocol( url ); - }, - - isEmbeddedPage: function( url ){ - return (/^#/).test( url ); + isEmbeddedPage: function( url ) { + var u = path.parseUrl( url ); + + //if the path is absolute, then we need to compare the url against + //both the documentUrl and the documentBase. The main reason for this + //is that links embedded within external documents will refer to the + //application document, whereas links embedded within the application + //document will be resolved against the document base. + if ( u.protocol !== "" ) { + return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) ); + } + return (/^#/).test( u.href ); } }, @@ -2056,43 +2253,43 @@ activeIndex: 0, //get active - getActive: function(){ + getActive: function() { return urlHistory.stack[ urlHistory.activeIndex ]; }, - getPrev: function(){ + getPrev: function() { return urlHistory.stack[ urlHistory.activeIndex - 1 ]; }, - getNext: function(){ + getNext: function() { return urlHistory.stack[ urlHistory.activeIndex + 1 ]; }, // addNew is used whenever a new page is added - addNew: function( url, transition, title, storedTo ){ + addNew: function( url, transition, title, storedTo ) { //if there's forward history, wipe it - if( urlHistory.getNext() ){ + if( urlHistory.getNext() ) { urlHistory.clearForward(); } urlHistory.stack.push( {url : url, transition: transition, title: title, page: storedTo } );