--- a/js/jquery.mobile-1.0b2.js +++ b/js/jquery.mobile-1.0b2.js @@ -1,5 +1,5 @@ /*! - * jQuery Mobile v1.0b1 + * jQuery Mobile v1.0b2 * http://jquerymobile.com/ * * Copyright 2010, jQuery Project @@ -274,20 +274,27 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ -(function($, undefined ) { + +(function( $, undefined ) { $.widget( "mobile.widget", { _getCreateOptions: function() { + var elem = this.element, options = {}; + $.each( this.options, function( option ) { + var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) { - return "-" + c.toLowerCase(); - } ) ); + return "-" + c.toLowerCase(); + }) + ); + if ( value !== undefined ) { options[ option ] = value; } }); + return options; } }); @@ -299,14 +306,10 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ -(function($, undefined ) { - -var $window = $(window), - $html = $( "html" ), - - //media-query-like width breakpoints, which are translated to classes on the html element - resolutionBreakpoints = [320,480,768,1024]; - +(function( $, undefined ) { + +var $window = $( window ), + $html = $( "html" ); /* $.mobile.media method: pass a CSS media type or query and get a bool return note: this feature relies on actual media query support for media queries, though types will work most anywhere @@ -323,17 +326,18 @@ return function( query ) { if ( !( query in cache ) ) { - var styleBlock = document.createElement('style'), - cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }"; - //must set type for IE! - styleBlock.type = "text/css"; - if (styleBlock.styleSheet){ - styleBlock.styleSheet.cssText = cssrule; - } - else { - styleBlock.appendChild(document.createTextNode(cssrule)); - } - + var styleBlock = document.createElement( "style" ), + cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }"; + + //must set type for IE! + styleBlock.type = "text/css"; + + if ( styleBlock.styleSheet ){ + styleBlock.styleSheet.cssText = cssrule; + } else { + styleBlock.appendChild( document.createTextNode(cssrule) ); + } + $html.prepend( fakeBody ).prepend( styleBlock ); cache[ query ] = testDiv.css( "position" ) === "absolute"; fakeBody.add( styleBlock ).remove(); @@ -341,122 +345,51 @@ return cache[ query ]; }; })(); - -/* - private function for adding/removing breakpoint classes to HTML element for faux media-query support - It does not require media query support, instead using JS to detect screen width > cross-browser support - This function is called on orientationchange, resize, and mobileinit, and is bound via the 'htmlclass' event namespace -*/ -function detectResolutionBreakpoints(){ - var currWidth = $window.width(), - minPrefix = "min-width-", - maxPrefix = "max-width-", - minBreakpoints = [], - maxBreakpoints = [], - unit = "px", - breakpointClasses; - - $html.removeClass( minPrefix + resolutionBreakpoints.join(unit + " " + minPrefix) + unit + " " + - maxPrefix + resolutionBreakpoints.join( unit + " " + maxPrefix) + unit ); - - $.each(resolutionBreakpoints,function( i, breakPoint ){ - if( currWidth >= breakPoint ){ - minBreakpoints.push( minPrefix + breakPoint + unit ); - } - if( currWidth <= breakPoint ){ - maxBreakpoints.push( maxPrefix + breakPoint + unit ); - } - }); - - if( minBreakpoints.length ){ breakpointClasses = minBreakpoints.join(" "); } - if( maxBreakpoints.length ){ breakpointClasses += " " + maxBreakpoints.join(" "); } - - $html.addClass( breakpointClasses ); -}; - -/* $.mobile.addResolutionBreakpoints method: - pass either a number or an array of numbers and they'll be added to the min/max breakpoint classes - Examples: - $.mobile.addResolutionBreakpoints( 500 ); - $.mobile.addResolutionBreakpoints( [500, 1200] ); -*/ -$.mobile.addResolutionBreakpoints = function( newbps ){ - if( $.type( newbps ) === "array" ){ - resolutionBreakpoints = resolutionBreakpoints.concat( newbps ); - } - else { - resolutionBreakpoints.push( newbps ); - } - resolutionBreakpoints.sort(function(a,b){ return a-b; }); - detectResolutionBreakpoints(); -}; - -/* on mobileinit, add classes to HTML element - and set handlers to update those on orientationchange and resize*/ -$(document).bind("mobileinit.htmlclass", function(){ - /* bind to orientationchange and resize - to add classes to HTML element for min/max breakpoints and orientation */ - 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 ); - } - //add classes to HTML element for min/max breakpoints - detectResolutionBreakpoints(); - }); -}); - -/* Manually trigger an orientationchange event when the dom ready event fires. - This will ensure that any viewport meta tag that may have been injected - has taken effect already, allowing us to properly calculate the width of the - document. -*/ -$(function(){ - //trigger event manually - $window.trigger( "orientationchange.htmlclass" ); -}); })(jQuery);/* * jQuery Mobile Framework : support tests * Copyright (c) jQuery Project * 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 = $( "" ).prependTo( "html" ), + +(function( $, undefined ) { + +var fakeBody = $( "" ).prependTo( "html" ), fbCSS = fakeBody[ 0 ].style, vendors = [ "webkit", "moz", "o" ], - webos = "palmGetResource" in window, //only used to rule out scrollTop + 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 ){ +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 ){ + 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 ) -function baseTagTest(){ +// 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" ), fauxEle = null, - href = ""; + href = "", + link, rebase; + if ( !base.length ) { - base = fauxEle = $( "", { "href": fauxBase} ).appendTo( "head" ); - } - else { + base = fauxEle = $( "", { "href": fauxBase }).appendTo( "head" ); + } else { href = base.attr( "href" ); } - var link = $( "" ).prependTo( fakeBody ), - rebase = link[ 0 ].href; + + link = $( "" ).prependTo( fakeBody ); + rebase = link[ 0 ].href; base[ 0 ].href = href ? href : location.pathname; + if ( fauxEle ) { fauxEle.remove(); } @@ -467,13 +400,15 @@ // 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" ), +$.mobile.browser.ie = (function() { + var v = 3, + div = document.createElement( "div" ), a = div.all || []; - while ( div.innerHTML = "", a[ 0 ] ); - return v > 4 ? v : !v; -}() ); + + while ( div.innerHTML = "", a[ 0 ] ); + + return v > 4 ? v : !v; +})(); $.extend( $.support, { @@ -486,15 +421,51 @@ 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. -} ); + // TODO: This is a weak test. We may want to beef this up later. + eventCapture: "addEventListener" in document +}); fakeBody.remove(); -// for ruling out shadows via css -if( !$.support.boxShadow ){ $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" ); } - -} )( jQuery );/* + +// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian) +// or that generally work better browsing in regular http for full page refreshes (Opera Mini) +// Note: This detection below is used as a last resort. +// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible +var nokiaLTE7_3 = (function(){ + + var ua = window.navigator.userAgent; + + //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older + return ua.indexOf( "Nokia" ) > -1 && + ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) && + ua.indexOf( "AppleWebKit" ) > -1 && + ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ ); +})(); + +$.mobile.ajaxBlacklist = + // BlackBerry browsers, pre-webkit + window.blackberry && !window.WebKitPoint || + // Opera Mini + window.operamini && Object.prototype.toString.call( window.operamini ) === "[object OperaMini]" || + // Symbian webkits pre 7.3 + nokiaLTE7_3; + +// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices +// to render the stylesheets when they're referenced before this script, as we'd recommend doing. +// This simply reappends the CSS in place, which for some reason makes it apply +if ( nokiaLTE7_3 ) { + $(function() { + $( "head link[rel=stylesheet]" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" ); + }); +} + +// 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. @@ -516,12 +487,12 @@ // The current version exposes the following virtual events to jQuery bind methods: // "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" -(function($, window, document, undefined) { +(function( $, window, document, undefined ) { var dataPropertyName = "virtualMouseBindings", touchTargetPropertyName = "virtualTouchID", - virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "), - touchEventProps = "clientX clientY pageX pageY screenX screenY".split(" "), + virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), + touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), activeDocHandlers = {}, resetTimerID = 0, startX = 0, @@ -531,7 +502,7 @@ blockMouseTriggers = false, blockTouchTriggers = false, eventCaptureSupported = $.support.eventCapture, - $document = $(document), + $document = $( document ), nextTouchID = 1, lastTouchID = 0; @@ -541,42 +512,45 @@ resetTimerDuration: 1500 }; -function getNativeEvent(event) -{ - while (event && typeof event.originalEvent !== "undefined") { +function getNativeEvent( event ) { + + while ( event && typeof event.originalEvent !== "undefined" ) { event = event.originalEvent; } return event; } -function createVirtualEvent(event, eventType) -{ - var t = event.type; +function createVirtualEvent( event, eventType ) { + + var t = event.type, + oe, props, ne, prop, ct, touch, i, j; + event = $.Event(event); event.type = eventType; - - var oe = event.originalEvent; - var props = $.event.props; - + + oe = event.originalEvent; + props = $.event.props; + // copy original event properties over to the new event // this would happen if we could call $.event.fix instead of $.Event // but we don't have a way to force an event to be fixed multiple times - if (oe) { - for ( var i = props.length, prop; i; ) { + if ( oe ) { + for ( i = props.length, prop; i; ) { prop = props[ --i ]; - event[prop] = oe[prop]; + event[ prop ] = oe[ prop ]; } } - - if (t.search(/^touch/) !== -1){ - 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]; + + if ( t.search(/^touch/) !== -1 ) { + ne = getNativeEvent( oe ); + t = ne.touches; + ct = ne.changedTouches; + touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined ); + + if ( touch ) { + for ( j = 0, len = touchEventProps.length; j < len; j++){ + prop = touchEventProps[ j ]; + event[ prop ] = touch[ prop ]; } } } @@ -584,14 +558,18 @@ return event; } -function getVirtualBindingFlags(element) -{ - var flags = {}; - while (element){ - var b = $.data(element, dataPropertyName); - for (var k in b) { - if (b[k]){ - flags[k] = flags.hasVirtualBinding = true; +function getVirtualBindingFlags( element ) { + + var flags = {}, + b, k; + + while ( element ) { + + b = $.data( element, dataPropertyName ); + + for ( k in b ) { + if ( b[ k ] ) { + flags[ k ] = flags.hasVirtualBinding = true; } } element = element.parentNode; @@ -599,11 +577,13 @@ return flags; } -function getClosestElementWithVirtualBinding(element, eventType) -{ - while (element){ - var b = $.data(element, dataPropertyName); - if (b && (!eventType || b[eventType])) { +function getClosestElementWithVirtualBinding( element, eventType ) { + var b; + while ( element ) { + + b = $.data( element, dataPropertyName ); + + if ( b && ( !eventType || b[ eventType ] ) ) { return element; } element = element.parentNode; @@ -611,18 +591,15 @@ return null; } -function enableTouchBindings() -{ +function enableTouchBindings() { blockTouchTriggers = false; } -function disableTouchBindings() -{ +function disableTouchBindings() { blockTouchTriggers = true; } -function enableMouseBindings() -{ +function enableMouseBindings() { lastTouchID = 0; clickBlockList.length = 0; blockMouseTriggers = false; @@ -632,148 +609,158 @@ disableTouchBindings(); } -function disableMouseBindings() -{ +function disableMouseBindings() { // When mouse bindings are disabled, our // touch bindings are enabled. enableTouchBindings(); } -function startResetTimer() -{ +function startResetTimer() { clearResetTimer(); resetTimerID = setTimeout(function(){ resetTimerID = 0; enableMouseBindings(); - }, $.vmouse.resetTimerDuration); + }, $.vmouse.resetTimerDuration ); } -function clearResetTimer() -{ - if (resetTimerID){ - clearTimeout(resetTimerID); +function clearResetTimer() { + if ( resetTimerID ){ + clearTimeout( resetTimerID ); resetTimerID = 0; } } -function triggerVirtualEvent(eventType, event, flags) -{ - var defaultPrevented = false; - - if ((flags && flags[eventType]) || (!flags && getClosestElementWithVirtualBinding(event.target, eventType))) { - var ve = createVirtualEvent(event, eventType); - $(event.target).trigger(ve); +function triggerVirtualEvent( eventType, event, flags ) { + var defaultPrevented = false, + ve; + + if ( ( flags && flags[ eventType ] ) || + ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { + + ve = createVirtualEvent( event, eventType ); + + $( event.target).trigger( ve ); + defaultPrevented = ve.isDefaultPrevented(); } return defaultPrevented; } -function mouseEventCallback(event) -{ +function mouseEventCallback( event ) { var touchID = $.data(event.target, touchTargetPropertyName); - if (!blockMouseTriggers && (!lastTouchID || lastTouchID !== touchID)){ - triggerVirtualEvent("v" + event.type, event); + + if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){ + triggerVirtualEvent( "v" + event.type, event ); } } -function handleTouchStart(event) -{ - var touches = getNativeEvent(event).touches; - if (touches && touches.length === 1){ - var target = event.target, - flags = getVirtualBindingFlags(target); - - if (flags.hasVirtualBinding){ +function handleTouchStart( event ) { + + var touches = getNativeEvent( event ).touches, + target, flags; + + if ( touches && touches.length === 1 ) { + + target = event.target; + flags = getVirtualBindingFlags( target ); + + if ( flags.hasVirtualBinding ) { + lastTouchID = nextTouchID++; - $.data(target, touchTargetPropertyName, lastTouchID); - + $.data( target, touchTargetPropertyName, lastTouchID ); + clearResetTimer(); - + disableMouseBindings(); didScroll = false; - - var t = getNativeEvent(event).touches[0]; + + var t = getNativeEvent( event ).touches[ 0 ]; startX = t.pageX; startY = t.pageY; - - triggerVirtualEvent("vmouseover", event, flags); - triggerVirtualEvent("vmousedown", event, flags); + + triggerVirtualEvent( "vmouseover", event, flags ); + triggerVirtualEvent( "vmousedown", event, flags ); } } } -function handleScroll(event) -{ - if (blockTouchTriggers){ +function handleScroll( event ) { + if ( blockTouchTriggers ) { return; } - if (!didScroll){ - triggerVirtualEvent("vmousecancel", event, getVirtualBindingFlags(event.target)); + if ( !didScroll ) { + triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); } didScroll = true; startResetTimer(); } -function handleTouchMove(event) -{ - if (blockTouchTriggers){ +function handleTouchMove( event ) { + if ( blockTouchTriggers ) { return; } - var t = getNativeEvent(event).touches[0]; - - var didCancel = didScroll, + var t = getNativeEvent( event ).touches[ 0 ], + didCancel = didScroll, moveThreshold = $.vmouse.moveDistanceThreshold; - didScroll = didScroll - || (Math.abs(t.pageX - startX) > moveThreshold || Math.abs(t.pageY - startY) > moveThreshold); - - var flags = getVirtualBindingFlags(event.target); - if (didScroll && !didCancel){ - triggerVirtualEvent("vmousecancel", event, flags); + didScroll = didScroll || + ( Math.abs(t.pageX - startX) > moveThreshold || + Math.abs(t.pageY - startY) > moveThreshold ), + flags = getVirtualBindingFlags( event.target ); + + if ( didScroll && !didCancel ) { + triggerVirtualEvent( "vmousecancel", event, flags ); } - triggerVirtualEvent("vmousemove", event, flags); + + triggerVirtualEvent( "vmousemove", event, flags ); startResetTimer(); } -function handleTouchEnd(event) -{ - if (blockTouchTriggers){ +function handleTouchEnd( event ) { + if ( blockTouchTriggers ) { return; } disableTouchBindings(); - var flags = getVirtualBindingFlags(event.target); - triggerVirtualEvent("vmouseup", event, flags); - if (!didScroll){ - if (triggerVirtualEvent("vclick", event, flags)){ + var flags = getVirtualBindingFlags( event.target ), + t; + triggerVirtualEvent( "vmouseup", event, flags ); + + if ( !didScroll ) { + if ( triggerVirtualEvent( "vclick", event, flags ) ) { // The target of the mouse events that follow the touchend // event don't necessarily match the target used during the // touch. This means we need to rely on coordinates for blocking // any click that is generated. - var t = getNativeEvent(event).changedTouches[0]; - clickBlockList.push({ touchID: lastTouchID, x: t.clientX, y: t.clientY }); + t = getNativeEvent( event ).changedTouches[ 0 ]; + clickBlockList.push({ + touchID: lastTouchID, + x: t.clientX, + y: t.clientY + }); // Prevent any mouse events that follow from triggering // virtual event notifications. blockMouseTriggers = true; } } - triggerVirtualEvent("vmouseout", event, flags); + triggerVirtualEvent( "vmouseout", event, flags); didScroll = false; - + startResetTimer(); } -function hasVirtualBindings(ele) -{ - var bindings = $.data(ele, dataPropertyName), k; - if (bindings){ - for (k in bindings){ - if (bindings[k]){ +function hasVirtualBindings( ele ) { + var bindings = $.data( ele, dataPropertyName ), + k; + + if ( bindings ) { + for ( k in bindings ) { + if ( bindings[ k ] ) { return true; } } @@ -783,49 +770,49 @@ function dummyMouseHandler(){} -function getSpecialEventObject(eventType) -{ - var realType = eventType.substr(1); +function getSpecialEventObject( eventType ) { + var realType = eventType.substr( 1 ); + return { - setup: function(data, namespace) { + setup: function( data, namespace ) { // If this is the first virtual mouse binding for this element, // add a bindings object to its data. - if (!hasVirtualBindings(this)){ - $.data(this, 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 = $.data(this, dataPropertyName); - bindings[eventType] = true; + var bindings = $.data( this, dataPropertyName ); + bindings[ eventType ] = true; // If this is the first virtual mouse event for this type, // register a global handler on the document. - activeDocHandlers[eventType] = (activeDocHandlers[eventType] || 0) + 1; - if (activeDocHandlers[eventType] === 1){ - $document.bind(realType, mouseEventCallback); + activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; + + if ( activeDocHandlers[ eventType ] === 1 ) { + $document.bind( realType, mouseEventCallback ); } // Some browsers, like Opera Mini, won't dispatch mouse/click events // 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){ + if ( eventCaptureSupported ) { // If this is the first virtual mouse binding for the document, // register our touchstart handler on the document. - - activeDocHandlers["touchstart"] = (activeDocHandlers["touchstart"] || 0) + 1; - if (activeDocHandlers["touchstart"] === 1) { - $document.bind("touchstart", handleTouchStart) - - .bind("touchend", handleTouchEnd) - + + activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; + + if (activeDocHandlers[ "touchstart" ] === 1) { + $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 @@ -835,56 +822,58 @@ // 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); + + .bind( "touchmove", handleTouchMove ) + .bind( "scroll", handleScroll ); } } }, - teardown: function(data, namespace) { + teardown: function( data, namespace ) { // If this is the last virtual binding for this eventType, // remove its global handler from the document. - --activeDocHandlers[eventType]; - if (!activeDocHandlers[eventType]){ - $document.unbind(realType, mouseEventCallback); - } - - if (eventCaptureSupported){ + --activeDocHandlers[ eventType ]; + + if ( !activeDocHandlers[ eventType ] ) { + $document.unbind( realType, mouseEventCallback ); + } + + if ( eventCaptureSupported ) { // If this is the last virtual mouse binding in existence, // remove our document touchstart listener. - - --activeDocHandlers["touchstart"]; - if (!activeDocHandlers["touchstart"]) { - $document.unbind("touchstart", handleTouchStart) - .unbind("touchmove", handleTouchMove) - .unbind("touchend", handleTouchEnd) - .unbind("scroll", handleScroll); - } - } - - var $this = $(this), - bindings = $.data(this, dataPropertyName); + + --activeDocHandlers[ "touchstart" ]; + + if ( !activeDocHandlers[ "touchstart" ] ) { + $document.unbind( "touchstart", handleTouchStart ) + .unbind( "touchmove", handleTouchMove ) + .unbind( "touchend", handleTouchEnd ) + .unbind( "scroll", handleScroll ); + } + } + + var $this = $( this ), + 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; + if ( bindings ) { + bindings[ eventType ] = false; } // Unregister the dummy event handler. - $this.unbind(realType, dummyMouseHandler); + $this.unbind( realType, dummyMouseHandler ); // If this is the last virtual mouse binding on the // element, remove the binding data from the element. - if (!hasVirtualBindings(this)){ - $this.removeData(dataPropertyName); + if ( !hasVirtualBindings( this ) ) { + $this.removeData( dataPropertyName ); } } }; @@ -892,21 +881,23 @@ // Expose our custom events to the jQuery bind/unbind mechanism. -for (var i = 0; i < virtualEventNames.length; i++){ - $.event.special[virtualEventNames[i]] = getSpecialEventObject(virtualEventNames[i]); +for ( var i = 0; i < virtualEventNames.length; i++ ){ + $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); } // Add a capture click handler to block clicks. // Note that we require event capture support for this so if the device // doesn't support it, we punt for now and rely solely on mouse events. -if (eventCaptureSupported){ - document.addEventListener("click", function(e){ - var cnt = clickBlockList.length; - var target = e.target; - if (cnt) { - var x = e.clientX, - y = e.clientY, - threshold = $.vmouse.clickDistanceThreshold; +if ( eventCaptureSupported ) { + document.addEventListener( "click", function( e ){ + var cnt = clickBlockList.length, + target = e.target, + x, y, ele, i, o, touchID; + + if ( cnt ) { + x = e.clientX; + y = e.clientY; + threshold = $.vmouse.clickDistanceThreshold; // The idea here is to run through the clickBlockList to see if // the current click event is in the proximity of one of our @@ -934,13 +925,16 @@ // mouse/click handler on one of its ancestors, the target will be the // innermost child of the touched element, even if that child is no where // near the point of touch. - - var ele = target; - while (ele) { - 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) || $.data(ele, touchTargetPropertyName) === o.touchID){ + + ele = target; + + while ( ele ) { + for ( i = 0; i < cnt; i++ ) { + o = clickBlockList[ i ]; + touchID = 0; + + 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(); @@ -953,20 +947,23 @@ } }, 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. * http://jquery.org/license */ -(function($, undefined ) { +(function( $, window, undefined ) { // add new event shortcuts -$.each( "touchstart touchmove touchend orientationchange throttledresize 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 ); }; + $.attrFn[ name ] = true; }); @@ -976,8 +973,7 @@ touchStopEvent = supportTouch ? "touchend" : "mouseup", touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; -function triggerCustomEvent(obj, eventType, event) -{ +function triggerCustomEvent( obj, eventType, event ) { var originalType = event.type; event.type = eventType; $.event.handle.call( obj, event ); @@ -986,29 +982,32 @@ // also handles scrollstop $.event.special.scrollstart = { + enabled: true, - + setup: function() { + var thisObject = this, $this = $( thisObject ), scrolling, timer; - + function trigger( event, state ) { scrolling = state; triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event ); } - + // iPhone triggers scroll after a small delay; use touchmove instead $this.bind( scrollEvent, function( event ) { + if ( !$.event.special.scrollstart.enabled ) { return; } - + if ( !scrolling ) { trigger( event, true ); } - + clearTimeout( timer ); timer = setTimeout(function() { trigger( event, false ); @@ -1022,120 +1021,130 @@ setup: function() { var thisObject = this, $this = $( thisObject ); - - $this - .bind("vmousedown", function( event ) { - if ( event.which && event.which !== 1 ) { - return false; - } - - var touching = true, - origTarget = event.target, - origEvent = event.originalEvent, - timer; - - function clearTapHandlers() { - touching = false; - clearTimeout(timer); - $this.unbind("vclick", clickHandler).unbind("vmousecancel", clearTapHandlers); - } - - function clickHandler(event) { - clearTapHandlers(); - - /* ONLY trigger a 'tap' event if the start target is - * the same as the stop target. - */ - if ( origTarget == event.target ) { - triggerCustomEvent( thisObject, "tap", event ); - } - } - - $this.bind("vmousecancel", clearTapHandlers).bind("vclick", clickHandler); - - timer = setTimeout(function() { - if ( touching ) { - triggerCustomEvent( thisObject, "taphold", event ); - } - }, 750 ); - }); + + $this.bind( "vmousedown", function( event ) { + + if ( event.which && event.which !== 1 ) { + return false; + } + + var touching = true, + origTarget = event.target, + origEvent = event.originalEvent, + timer; + + function clearTapHandlers() { + touching = false; + clearTimeout(timer); + + $this.unbind( "vclick", clickHandler ) + .unbind( "vmousecancel", clearTapHandlers ); + } + + function clickHandler(event) { + clearTapHandlers(); + + // ONLY trigger a 'tap' event if the start target is + // the same as the stop target. + if ( origTarget == event.target ) { + triggerCustomEvent( thisObject, "tap", event ); + } + } + + $this.bind( "vmousecancel", clearTapHandlers ) + .bind( "vclick", clickHandler ); + + timer = setTimeout(function() { + if ( touching ) { + triggerCustomEvent( thisObject, "taphold", event ); + } + }, 750 ); + }); } }; // also handles swipeleft, swiperight $.event.special.swipe = { + scrollSupressionThreshold: 10, // More than this horizontal displacement, and we will suppress scrolling. + + durationThreshold: 1000, // More time than this, and it isn't a swipe. + + horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this. + + verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this. + setup: function() { var thisObject = this, $this = $( thisObject ); - - $this - .bind( touchStartEvent, function( event ) { + + $this.bind( touchStartEvent, function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event, + start = { + time: ( new Date() ).getTime(), + coords: [ data.pageX, data.pageY ], + origin: $( event.target ) + }, + stop; + + function moveHandler( event ) { + + if ( !start ) { + return; + } + var data = event.originalEvent.touches ? - event.originalEvent.touches[ 0 ] : - event, - start = { - time: (new Date).getTime(), - coords: [ data.pageX, data.pageY ], - origin: $( event.target ) - }, - stop; - - function moveHandler( event ) { - if ( !start ) { - return; + event.originalEvent.touches[ 0 ] : event; + + stop = { + time: ( new Date() ).getTime(), + coords: [ data.pageX, data.pageY ] + }; + + // prevent scrolling + if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { + event.preventDefault(); + } + } + + $this.bind( touchMoveEvent, moveHandler ) + .one( touchStopEvent, function( event ) { + $this.unbind( touchMoveEvent, moveHandler ); + + if ( start && stop ) { + if ( stop.time - start.time < $.event.special.swipe.durationThreshold && + Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && + Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { + + start.origin.trigger( "swipe" ) + .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" ); + } } - - var data = event.originalEvent.touches ? - event.originalEvent.touches[ 0 ] : - event; - stop = { - time: (new Date).getTime(), - coords: [ data.pageX, data.pageY ] - }; - - // prevent scrolling - if ( Math.abs( start.coords[0] - stop.coords[0] ) > 10 ) { - event.preventDefault(); - } - } - - $this - .bind( touchMoveEvent, moveHandler ) - .one( touchStopEvent, function( event ) { - $this.unbind( touchMoveEvent, moveHandler ); - if ( start && stop ) { - if ( stop.time - start.time < 1000 && - Math.abs( start.coords[0] - stop.coords[0]) > 30 && - Math.abs( start.coords[1] - stop.coords[1]) < 75 ) { - start.origin - .trigger( "swipe" ) - - .trigger( start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight" ); - } - } - start = stop = undefined; - }); - }); + start = stop = undefined; + }); + }); } }; -(function($){ +(function( $, window ) { // "Cowboy" Ben Alman - - var win = $(window), + + var win = $( window ), special_event, get_orientation, last_orientation; - + $.event.special.orientationchange = special_event = { - setup: function(){ + setup: function() { // If the event is supported natively, return false so that jQuery // will bind to the event using DOM methods. - if ( $.support.orientation ) { return false; } - + if ( $.support.orientation ) { + return false; + } + // Get the current orientation to avoid initial double-triggering. last_orientation = get_orientation(); - + // Because the orientationchange event doesn't exist, simulate the // event by testing window dimensions on resize. win.bind( "throttledresize", handler ); @@ -1143,8 +1152,10 @@ teardown: function(){ // If the event is not supported natively, return false so that // jQuery will unbind the event using DOM methods. - if ( $.support.orientation ) { return false; } - + if ( $.support.orientation ) { + return false; + } + // Because the orientationchange event doesn't exist, unbind the // resize event handler. win.unbind( "throttledresize", handler ); @@ -1152,45 +1163,46 @@ add: function( handleObj ) { // Save a reference to the bound event handler. var old_handler = handleObj.handler; - + handleObj.handler = function( event ) { // Modify event object, adding the .orientation property. event.orientation = get_orientation(); - + // Call the originally-bound event handler and return its result. return old_handler.apply( this, arguments ); }; } }; - + // If the event is not supported natively, this handler will be bound to // the window resize event to simulate the orientationchange event. function handler() { // Get the current orientation. var orientation = get_orientation(); - + if ( orientation !== last_orientation ) { // The orientation has changed, so trigger the orientationchange event. last_orientation = orientation; win.trigger( "orientationchange" ); } }; - + // Get the current page orientation. This method is exposed publicly, should it // be needed, as jQuery.event.special.orientationchange.orientation() $.event.special.orientationchange.orientation = get_orientation = function() { var elem = document.documentElement; return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape"; }; - -})(jQuery); + +})( jQuery, window ); // throttled resize event -(function(){ +(function() { + $.event.special.throttledresize = { setup: function() { - $( this ).bind( "resize", handler ); + $( this ).bind( "resize", handler ); }, teardown: function(){ $( this ).unbind( "resize", handler ); @@ -1198,18 +1210,22 @@ }; var throttle = 250, - handler = function(){ + handler = function() { curr = ( new Date() ).getTime(); diff = curr - lastCall; - if( diff >= throttle ){ + + if ( diff >= throttle ) { + lastCall = curr; $( this ).trigger( "throttledresize" ); - } - else{ - if( heldCall ){ + + } else { + + if ( heldCall ) { clearTimeout( heldCall ); } - //promise a held call will still execute + + // Promise a held call will still execute heldCall = setTimeout( handler, throttle - diff ); } }, @@ -1226,6 +1242,7 @@ swipeleft: "swipe", swiperight: "swipe" }, function( event, sourceEvent ) { + $.event.special[ event ] = { setup: function() { $( this ).bind( sourceEvent, $.noop ); @@ -1233,7 +1250,7 @@ }; }); -})( jQuery ); +})( jQuery, this ); /*! * jQuery hashchange event - v1.3 - 7/21/2010 * http://benalman.com/projects/jquery-hashchange-plugin/ @@ -1630,208 +1647,24 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ -(function($, undefined ) { + +(function( $, undefined ) { $.widget( "mobile.page", $.mobile.widget, { options: { - backBtnText: "Back", - addBackBtn: false, - backBtnTheme: null, - degradeInputs: { - color: false, - date: false, - datetime: false, - "datetime-local": false, - email: false, - month: false, - number: false, - range: "number", - search: true, - tel: false, - time: false, - url: false, - week: false - }, - keepNative: null + theme: "c", + domCache: false }, _create: function() { var $elem = this.element, o = this.options; - this.keepNative = ":jqmData(role='none'), :jqmData(role='nojs')" + (o.keepNative ? ", " + o.keepNative : ""); - if ( this._trigger( "beforeCreate" ) === false ) { return; } - //some of the form elements currently rely on the presence of ui-page and ui-content - // classes so we'll handle page and content roles outside of the main role processing - // loop below. - $elem.find( ":jqmData(role='page'), :jqmData(role='content')" ).andSelf().each(function() { - $(this).addClass( "ui-" + $(this).jqmData( "role" ) ); - }); - - $elem.find( ":jqmData(role='nojs')" ).addClass( "ui-nojs" ); - - // pre-find data els - var $dataEls = $elem.find( ":jqmData(role)" ).andSelf().each(function() { - var $this = $( this ), - role = $this.jqmData( "role" ), - theme = $this.jqmData( "theme" ); - - //apply theming and markup modifications to page,header,content,footer - if ( role === "header" || role === "footer" ) { - $this.addClass( "ui-bar-" + (theme || $this.parent( ":jqmData(role='page')" ).jqmData( "theme" ) || "a") ); - - // add ARIA role - $this.attr( "role", role === "header" ? "banner" : "contentinfo" ); - - //right,left buttons - var $headeranchors = $this.children( "a" ), - leftbtn = $headeranchors.hasClass( "ui-btn-left" ), - rightbtn = $headeranchors.hasClass( "ui-btn-right" ); - - if ( !leftbtn ) { - leftbtn = $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length; - } - - if ( !rightbtn ) { - rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length; - } - - // auto-add back btn on pages beyond first view - if ( o.addBackBtn && role === "header" && - $( ".ui-page" ).length > 1 && - $elem.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) && - !leftbtn && $this.jqmData( "backbtn" ) !== false ) { - - var backBtn = $( ""+ o.backBtnText +"" ).prependTo( $this ); - - //if theme is provided, override default inheritance - if( o.backBtnTheme ){ - backBtn.attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme ); - } - } - - //page title - $this.children( "h1, h2, h3, h4, h5, h6" ) - .addClass( "ui-title" ) - //regardless of h element number in src, it becomes h1 for the enhanced page - .attr({ "tabindex": "0", "role": "heading", "aria-level": "1" }); - - } else if ( role === "content" ) { - if ( theme ) { - $this.addClass( "ui-body-" + theme ); - } - - // add ARIA role - $this.attr( "role", "main" ); - - } else if ( role === "page" ) { - $this.addClass( "ui-body-" + (theme || "c") ); - } - - switch(role) { - case "header": - case "footer": - case "page": - case "content": - $this.addClass( "ui-" + role ); - break; - case "collapsible": - case "fieldcontain": - case "navbar": - case "listview": - case "dialog": - $this[ role ](); - break; - } - }); - - //enhance form controls - this._enhanceControls(); - - //links in bars, or those with data-role become buttons - $elem.find( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a" ) - .not( ".ui-btn" ) - .not(this.keepNative) - .buttonMarkup(); - - $elem - .find(":jqmData(role='controlgroup')") - .controlgroup(); - - //links within content areas - $elem.find( "a:not(.ui-btn):not(.ui-link-inherit)" ) - .not(this.keepNative) - .addClass( "ui-link" ); - - //fix toolbars - $elem.fixHeaderFooter(); - }, - - _typeAttributeRegex: /\s+type=["']?\w+['"]?/, - - _enhanceControls: function() { - var o = this.options, self = this; - - // degrade inputs to avoid poorly implemented native functionality - this.element.find( "input" ).not(this.keepNative).each(function() { - var type = this.getAttribute( "type" ), - optType = o.degradeInputs[ type ] || "text"; - - if ( o.degradeInputs[ type ] ) { - $( this ).replaceWith( - $( "
" ).html( $(this).clone() ).html() - .replace( self._typeAttributeRegex, " type=\""+ optType +"\" data-" + $.mobile.ns + "type=\""+type+"\" " ) ); - } - }); - - // We re-find form elements since the degredation code above - // may have injected new elements. We cache the non-native control - // query to reduce the number of times we search through the entire page. - - var allControls = this.element.find("input, textarea, select, button"), - nonNativeControls = allControls.not(this.keepNative); - - // XXX: Temporary workaround for issue 785. Turn off autocorrect and - // autocomplete since the popup they use can't be dismissed by - // the user. Note that we test for the presence of the feature - // by looking for the autocorrect property on the input element. - - var textInputs = allControls.filter( "input[type=text]" ); - if (textInputs.length && typeof textInputs[0].autocorrect !== "undefined") { - textInputs.each(function(){ - // Set the attribute instead of the property just in case there - // is code that attempts to make modifications via HTML. - this.setAttribute("autocorrect", "off"); - this.setAttribute("autocomplete", "off"); - }); - } - - // enchance form controls - nonNativeControls - .filter( "[type='radio'], [type='checkbox']" ) - .checkboxradio(); - - nonNativeControls - .filter( "button, [type='button'], [type='submit'], [type='reset'], [type='image']" ) - .button(); - - nonNativeControls - .filter( "input, textarea" ) - .not( "[type='radio'], [type='checkbox'], [type='button'], [type='submit'], [type='reset'], [type='image'], [type='hidden']" ) - .textinput(); - - nonNativeControls - .filter( "input, select" ) - .filter( ":jqmData(role='slider'), :jqmData(type='range')" ) - .slider(); - - nonNativeControls - .filter( "select:not(:jqmData(role='slider'))" ) - .selectmenu(); + $elem.addClass( "ui-page ui-body-" + o.theme ); } }); @@ -1847,61 +1680,55 @@ (function( $, window, undefined ) { - //jQuery.mobile configurable options + // jQuery.mobile configurable options $.extend( $.mobile, { - //namespace used framework-wide for data-attrs. Default is no namespace + // Namespace used framework-wide for data-attrs. Default is no namespace ns: "", - //define the url parameter used for referencing widget-generated sub-pages. - //Translates to to example.html&ui-page=subpageIdentifier - //hash segment before &ui-page= is used to make Ajax request + // Define the url parameter used for referencing widget-generated sub-pages. + // Translates to to example.html&ui-page=subpageIdentifier + // 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 - //(no change in URL, not bookmarkable) - nonHistorySelectors: "dialog", - - //class assigned to page currently in view, and during transitions + // Class assigned to page currently in view, and during transitions activePageClass: "ui-page-active", - //class used for "active" button state, from CSS framework + // Class used for "active" button state, from CSS framework activeBtnClass: "ui-btn-active", - //automatically handle clicks and form submissions through Ajax, when same-domain + // Automatically handle clicks and form submissions through Ajax, when same-domain ajaxEnabled: true, + + // Automatically load and show pages based on location.hash + hashListeningEnabled: true, + + // 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", - //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, - - //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", - - //support conditions that must be met in order to proceed - //default enhanced qualifications are media query support OR IE 7+ + //automatically initialize the DOM when it's ready + autoInitializePage: true, + + // Support conditions that must be met in order to proceed + // default enhanced qualifications are media query support OR IE 7+ gradeA: function(){ return $.support.mediaquery || $.mobile.browser.ie && $.mobile.browser.ie >= 7; }, - //TODO might be useful upstream in jquery itself ? + // TODO might be useful upstream in jquery itself ? keyCode: { ALT: 18, BACKSPACE: 8, @@ -1937,9 +1764,9 @@ WINDOWS: 91 // COMMAND }, - //scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value + // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value silentScroll: function( ypos ) { - if( $.type( ypos ) !== "number" ){ + if ( $.type( ypos ) !== "number" ) { ypos = $.mobile.defaultHomeScroll; } @@ -1948,53 +1775,52 @@ setTimeout(function() { window.scrollTo( 0, ypos ); - $(document).trigger( "silentscroll", { x: 0, y: ypos }); - },20); + $( document ).trigger( "silentscroll", { x: 0, y: ypos }); + }, 20 ); 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 + // Take a data attribute property, prepend the namespace // and then camel case the attribute string - nsNormalize: function(prop){ - if(!prop) return; + 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.nsNormalize(prop) : prop, value ); + // 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.nsNormalize( prop ) : prop, value ); }; - $.jqmData = function( elem, prop, value ){ - return $.data( elem, $.mobile.nsNormalize(prop), value ); + $.jqmData = function( elem, prop, value ) { + return $.data( elem, $.mobile.nsNormalize( prop ), value ); }; - $.fn.jqmRemoveData = function( prop ){ - return this.removeData( $.mobile.nsNormalize(prop) ); + $.fn.jqmRemoveData = function( prop ) { + return this.removeData( $.mobile.nsNormalize( prop ) ); }; - $.jqmRemoveData = function( elem, prop ){ - return $.removeData( elem, $.mobile.nsNormalize(prop) ); + $.jqmRemoveData = function( elem, prop ) { + return $.removeData( elem, $.mobile.nsNormalize( prop ) ); }; - $.jqmHasData = function( elem, prop ){ - return $.hasData( 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; $.find = function( selector, context, ret, extra ) { - selector = selector.replace(/:jqmData\(([^)]*)\)/g, "[data-" + ($.mobile.ns || "") + "$1]"); + selector = selector.replace(/:jqmData\(([^)]*)\)/g, "[data-" + ( $.mobile.ns || "" ) + "$1]"); return oldFind.call( this, selector, context, ret, extra ); }; @@ -2006,7 +1832,7 @@ }; $.find.matchesSelector = function( node, expr ) { - return $.find( expr, null, null, [node] ).length > 0; + return $.find( expr, null, null, [ node ] ).length > 0; }; })( jQuery, this ); /* @@ -2056,7 +1882,7 @@ 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" ) { + if ( $.type( url ) === "object" ) { return url; } @@ -2097,10 +1923,10 @@ if ( relPath && relPath.charAt( 0 ) === "/" ) { return relPath; } - + relPath = relPath || ""; - absPath = absPath ? absPath.replace( /^\/|\/?[^\/]*$/g, "" ) : ""; - + absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : ""; + var absStack = absPath ? absPath.split( "/" ) : [], relStack = relPath.split( "/" ); for ( var i = 0; i < relStack.length; i++ ) { @@ -2143,7 +1969,7 @@ if ( !path.isRelativeUrl( relUrl ) ) { return relUrl; } - + var relObj = path.parseUrl( relUrl ), absObj = path.parseUrl( absUrl ), protocol = relObj.protocol || absObj.protocol, @@ -2152,7 +1978,7 @@ pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), search = relObj.search || ( !hasPath && absObj.search ) || "", hash = relObj.hash; - + return protocol + "//" + authority + pathname + search + hash; }, @@ -2167,7 +1993,9 @@ convertUrlToDataUrl: function( absUrl ) { var u = path.parseUrl( absUrl ); if ( path.isEmbeddedPage( u ) ) { - return u.hash.replace( /^#/, "" ); + // For embedded pages, remove the dialog hash key as in getFilePath(), + // otherwise the Data Url won't match the id of the embedded Page. + return u.hash.split( dialogHashKey )[0].replace( /^#/, "" ); } else if ( path.isSameDomain( u, documentBase ) ) { return u.hrefNoHash.replace( documentBase.domain, "" ); } @@ -2246,7 +2074,8 @@ //urlHistory is purely here to make guesses at whether the back or forward button was clicked //and provide an appropriate transition urlHistory = { - //array of pages that are visited during a single page load. each has a url and optional transition + // Array of pages that are visited during a single page load. + // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs) stack: [], //maintain an index number for the active page in the stack @@ -2266,13 +2095,13 @@ }, // addNew is used whenever a new page is added - addNew: function( url, transition, title, storedTo ) { + addNew: function( url, transition, title, pageUrl ) { //if there's forward history, wipe it if( urlHistory.getNext() ) { urlHistory.clearForward(); } - urlHistory.stack.push( {url : url, transition: transition, title: title, page: storedTo } ); + urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl } ); urlHistory.activeIndex = urlHistory.stack.length - 1; }, @@ -2396,29 +2225,29 @@ //function for transitioning between two existing pages function transitionPages( toPage, fromPage, transition, reverse ) { - + //get current scroll distance var currScroll = $.support.scrollTop ? $window.scrollTop() : true, toScroll = toPage.data( "lastScroll" ) || $.mobile.defaultHomeScroll, screenHeight = getScreenHeight(); - + //if scrolled down, scroll to top if( currScroll ){ window.scrollTo( 0, $.mobile.defaultHomeScroll ); } - + //if the Y location we're scrolling to is less than 10px, let it go for sake of smoothness if( toScroll < $.mobile.minScrollBack ){ toScroll = 0; } - + if( fromPage ) { //set as data for returning to that spot fromPage .height( screenHeight + currScroll ) .jqmData( "lastScroll", currScroll ) .jqmData( "lastClicked", $activeClickedLink ); - + //trigger before show/hide events fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } ); } @@ -2438,28 +2267,28 @@ promise.done(function() { //reset toPage height bac toPage.height( "" ); - + //jump to top or prev scroll, sometimes on iOS the page has not rendered yet. if( toScroll ){ $.mobile.silentScroll( toScroll ); $( document ).one( "silentscroll", function() { reFocus( toPage ); } ); } else{ - reFocus( toPage ); + reFocus( toPage ); } //trigger show/hide events if( fromPage ) { fromPage.height("").data( "page" )._trigger( "hide", null, { nextPage: toPage } ); } - + //trigger pageshow, define prevPage as either fromPage or empty jQuery obj toPage.data( "page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } ); }); return promise; } - + //simply set the active page's minimum height to screen height, depending on orientation function getScreenHeight(){ var orientation = jQuery.event.special.orientationchange.orientation(), @@ -2471,7 +2300,7 @@ return pageMin; } - + //simply set the active page's minimum height to screen height, depending on orientation function resetActivePageHeight(){ $( "." + $.mobile.activePageClass ).css( "min-height", getScreenHeight() ); @@ -2570,9 +2399,15 @@ // page is loaded off the network. dupCachedPage = null, + // determine the current base url + findBaseWithDefault = function(){ + var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) ); + return closestBase || documentBase.hrefNoHash; + }, + // The absolute version of the URL passed into the function. This // version of the URL may contain dialog/subpage params in it. - absUrl = path.makeUrlAbsolute( url, documentBase.hrefNoHash ); + absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() ); // If the caller provided data, and we're using "get" request, @@ -2618,19 +2453,36 @@ } if ( settings.showLoadMsg ) { - $.mobile.showPageLoadingMsg(); - } - - // Load the new page. - $.ajax({ - url: fileUrl, - type: settings.type, - data: settings.data, - dataType: "html", - success: function( html ) { - //pre-parse html to check for a data-url, - //use it as the new fileUrl, base path, etc - var all = $( "
" ), + + // This configurable timeout allows cached pages a brief delay to load without showing a message + var loadMsgDelay = setTimeout(function(){ + $.mobile.showPageLoadingMsg(); + }, settings.loadMsgDelay ), + + // Shared logic for clearing timeout and removing message. + hideMsg = function(){ + + // Stop message show timer + clearTimeout( loadMsgDelay ); + + // Hide loading message + $.mobile.hidePageLoadingMsg(); + }; + } + + if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) { + deferred.reject( absUrl, options ); + } else { + // Load the new page. + $.ajax({ + url: fileUrl, + type: settings.type, + data: settings.data, + dataType: "html", + success: function( html ) { + //pre-parse html to check for a data-url, + //use it as the new fileUrl, base path, etc + var all = $( "
" ), //page title regexp newPageTitle = html.match( /]*>([^<]*)/ ) && RegExp.$1, @@ -2640,90 +2492,114 @@ dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" ); - // data-url must be provided for the base tag so resource requests can be directed to the - // correct url. loading into a temprorary element makes these requests immediately - if( pageElemRegex.test( html ) - && RegExp.$1 - && dataUrlRegex.test( RegExp.$1 ) - && RegExp.$1 ) { - url = fileUrl = path.getFilePath( RegExp.$1 ); - } - - if ( base ) { - base.set( fileUrl ); - } - - //workaround to allow scripts to execute when included in page divs - all.get( 0 ).innerHTML = html; - page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first(); - - if ( newPageTitle && !page.jqmData( "title" ) ) { - page.jqmData( "title", newPageTitle ); - } - - //rewrite src and href attrs to use a base url - if( !$.support.dynamicBaseTag ) { - var newPath = path.get( fileUrl ); - page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() { - var thisAttr = $( this ).is( '[href]' ) ? 'href' : - $(this).is('[src]') ? 'src' : 'action', - thisUrl = $( this ).attr( thisAttr ); - - // XXX_jblas: We need to fix this so that it removes the document - // base URL, and then prepends with the new page URL. - //if full path exists and is same, chop it - helps IE out - thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); - - if( !/^(\w+:|#|\/)/.test( thisUrl ) ) { - $( this ).attr( thisAttr, newPath + thisUrl ); + // data-url must be provided for the base tag so resource requests can be directed to the + // correct url. loading into a temprorary element makes these requests immediately + if( pageElemRegex.test( html ) + && RegExp.$1 + && dataUrlRegex.test( RegExp.$1 ) + && RegExp.$1 ) { + url = fileUrl = path.getFilePath( RegExp.$1 ); + } + else{ + + } + + if ( base ) { + base.set( fileUrl ); + } + + //workaround to allow scripts to execute when included in page divs + all.get( 0 ).innerHTML = html; + page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first(); + + //if page elem couldn't be found, create one and insert the body element's contents + if( !page.length ){ + page = $( "
" + html.split( /<\/?body[^>]*>/gmi )[1] + "
" ); + } + + if ( newPageTitle && !page.jqmData( "title" ) ) { + page.jqmData( "title", newPageTitle ); + } + + //rewrite src and href attrs to use a base url + if( !$.support.dynamicBaseTag ) { + var newPath = path.get( fileUrl ); + page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() { + var thisAttr = $( this ).is( '[href]' ) ? 'href' : + $(this).is('[src]') ? 'src' : 'action', + thisUrl = $( this ).attr( thisAttr ); + + // XXX_jblas: We need to fix this so that it removes the document + // base URL, and then prepends with the new page URL. + //if full path exists and is same, chop it - helps IE out + thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); + + if( !/^(\w+:|#|\/)/.test( thisUrl ) ) { + $( this ).attr( thisAttr, newPath + thisUrl ); + } + }); + } + + //append to page and enhance + page + .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) ) + .appendTo( settings.pageContainer ); + + // wait for page creation to leverage options defined on widget + page.one('pagecreate', function(){ + + // when dom caching is not enabled bind to remove the page on hide + if( !page.data("page").options.domCache ){ + page.bind( "pagehide.remove", function(){ + $(this).remove(); + }); } }); - } - - //append to page and enhance - page - .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) ) - .appendTo( settings.pageContainer ); - - enhancePage( page, settings.role ); - - // Enhancing the page may result in new dialogs/sub pages being inserted - // into the DOM. If the original absUrl refers to a sub-page, that is the - // real page we are interested in. - if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) { - page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" ); - } - - // Remove loading message. - if ( settings.showLoadMsg ) { - $.mobile.hidePageLoadingMsg(); - } - - deferred.resolve( absUrl, options, page, dupCachedPage ); - }, - error: function() { - //set base back to current path - if( base ) { - base.set( path.get() ); - } - - // Remove loading message. -