/*!
* jQuery UI Widget 1.0b3pre
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Widget
*/
(function($, undefined) {
// jQuery 1.4+
if ($.cleanData) {
var _cleanData = $.cleanData;
$.cleanData = function(elems) {
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
$(elem).triggerHandler("remove");
}
_cleanData(elems);
};
} else {
var _remove = $.fn.remove;
$.fn.remove = function(selector, keepData) {
return this.each(function() {
if (!keepData) {
if (!selector || $.filter(selector, [ this ]).length) {
$("*", this).add([ this ]).each(function() {
$(this).triggerHandler("remove");
});
}
}
return _remove.call($(this), selector, keepData);
});
};
}
$.widget = function(name, base, prototype) {
var namespace = name.split(".")[ 0 ],
fullName;
name = name.split(".")[ 1 ];
fullName = namespace + "-" + name;
if (!prototype) {
prototype = base;
base = $.Widget;
}
// create selector for plugin
$.expr[ ":" ][ fullName ] = function(elem) {
return !!$.data(elem, name);
};
$[ namespace ] = $[ namespace ] || {};
$[ namespace ][ name ] = function(options, element) {
// allow instantiation without initializing for simple inheritance
if (arguments.length) {
this._createWidget(options, element);
}
};
var basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
// $.each( basePrototype, function( key, val ) {
// if ( $.isPlainObject(val) ) {
// basePrototype[ key ] = $.extend( {}, val );
// }
// });
basePrototype.options = $.extend(true, {}, basePrototype.options);
$[ namespace ][ name ].prototype = $.extend(true, basePrototype, {
namespace: namespace,
widgetName: name,
widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
widgetBaseClass: fullName
}, prototype);
$.widget.bridge(name, $[ namespace ][ name ]);
};
$.widget.bridge = function(name, object) {
$.fn[ name ] = function(options) {
var isMethodCall = typeof options === "string",
args = Array.prototype.slice.call(arguments, 1),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.extend.apply(null, [ true, options ].concat(args)) :
options;
// prevent calls to internal methods
if (isMethodCall && options.charAt(0) === "_") {
return returnValue;
}
if (isMethodCall) {
this.each(function() {
var instance = $.data(this, name);
if (!instance) {
throw "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'";
}
if (!$.isFunction(instance[options])) {
throw "no such method '" + options + "' for " + name + " widget instance";
}
var methodValue = instance[ options ].apply(instance, args);
if (methodValue !== instance && methodValue !== undefined) {
returnValue = methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data(this, name);
if (instance) {
instance.option(options || {})._init();
} else {
$.data(this, name, new object(options, this));
}
});
}
return returnValue;
};
};
$.Widget = function(options, element) {
// allow instantiation without initializing for simple inheritance
if (arguments.length) {
this._createWidget(options, element);
}
};
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
options: {
disabled: false
},
_createWidget: function(options, element) {
// $.widget.bridge stores the plugin instance, but we do it anyway
// so that it's stored even before the _create function runs
$.data(element, this.widgetName, this);
this.element = $(element);
this.options = $.extend(true, {},
this.options,
this._getCreateOptions(),
options);
var self = this;
this.element.bind("remove." + this.widgetName, function() {
self.destroy();
});
this._create();
this._trigger("create");
this._init();
},
_getCreateOptions: function() {
var options = {};
if ($.metadata) {
options = $.metadata.get(element)[ this.widgetName ];
}
return options;
},
_create: function() {
},
_init: function() {
},
destroy: function() {
this.element
.unbind("." + this.widgetName)
.removeData(this.widgetName);
this.widget()
.unbind("." + this.widgetName)
.removeAttr("aria-disabled")
.removeClass(
this.widgetBaseClass + "-disabled " +
"ui-state-disabled");
},
widget: function() {
return this.element;
},
option: function(key, value) {
var options = key;
if (arguments.length === 0) {
// don't return a reference to the internal hash
return $.extend({}, this.options);
}
if (typeof key === "string") {
if (value === undefined) {
return this.options[ key ];
}
options = {};
options[ key ] = value;
}
this._setOptions(options);
return this;
},
_setOptions: function(options) {
var self = this;
$.each(options, function(key, value) {
self._setOption(key, value);
});
return this;
},
_setOption: function(key, value) {
this.options[ key ] = value;
if (key === "disabled") {
this.widget()
[ value ? "addClass" : "removeClass"](
this.widgetBaseClass + "-disabled" + " " +
"ui-state-disabled")
.attr("aria-disabled", value);
}
return this;
},
enable: function() {
return this._setOption("disabled", false);
},
disable: function() {
return this._setOption("disabled", true);
},
_trigger: function(type, event, data) {
var callback = this.options[ type ];
event = $.Event(event);
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
data = data || {};
// 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 (event.originalEvent) {
for (var i = $.event.props.length, prop; i;) {
prop = $.event.props[ --i ];
event[ prop ] = event.originalEvent[ prop ];
}
}
this.element.trigger(event, data);
return !( $.isFunction(callback) &&
callback.call(this.element[0], event, data) === false ||
event.isDefaultPrevented() );
}
};
})(jQuery);
/*
* jQuery Mobile Framework : widget factory extentions for mobile
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(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();
})
);
if (value !== undefined) {
options[ option ] = value;
}
});
return options;
}
});
})(jQuery);
/*
* jQuery Mobile Framework : resolution and CSS media query related helpers and behavior
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(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
examples:
$.mobile.media('screen') //>> tests for screen media type
$.mobile.media('screen and (min-width: 480px)') //>> tests for screen media type with window width > 480px
$.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') //>> tests for webkit 2x pixel ratio (iPhone 4)
*/
$.mobile.media = (function() {
// TODO: use window.matchMedia once at least one UA implements it
var cache = {},
testDiv = $("
"),
fakeBody = $("").append(testDiv);
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));
}
$html.prepend(fakeBody).prepend(styleBlock);
cache[ query ] = testDiv.css("position") === "absolute";
fakeBody.add(styleBlock).remove();
}
return cache[ query ];
};
})();
})(jQuery);
/*
* jQuery Mobile Framework : support tests
* Copyright (c) jQuery Project
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
*/
(function($, undefined) {
var fakeBody = $("").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[ props[ v ] ] !== undefined) {
return true;
}
}
}
// 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 = "",
link, rebase;
if (!base.length) {
base = fauxEle = $("
", { "href": fauxBase }).appendTo("head");
} else {
href = base.attr("href");
}
link = $("
").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
$.mobile.browser = {};
$.mobile.browser.ie = (function() {
var v = 3,
div = document.createElement("div"),
a = div.all || [];
while (div.innerHTML = "",a[ 0 ]);
return v > 4 ? v : !v;
})();
$.extend($.support, {
orientation: "orientation" in window,
touch: "ontouchend" in document,
cssTransitions: "WebKitTransitionEvent" in window,
pushState: "pushState" in history && "replaceState" in history,
mediaquery: $.mobile.media("only all"),
cssPseudoElement: !!propExists("content"),
touchOverflow: !!propExists("overflowScrolling"),
boxShadow: !!propExists("boxShadow") && !bb,
scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos,
dynamicBaseTag: baseTagTest(),
// TODO: This is a weak test. We may want to beef this up later.
eventCapture: "addEventListener" in document
});
fakeBody.remove();
// $.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.
* http://jquery.org/license
*/
// This plugin is an experiment for abstracting away the touch and mouse
// events so that developers don't have to worry about which method of input
// the device their document is loaded on supports.
//
// The idea here is to allow the developer to register listeners for the
// basic mouse events, such as mousedown, mousemove, mouseup, and click,
// and the plugin will take care of registering the correct listeners
// behind the scenes to invoke the listener at the fastest possible time
// for that device, while still retaining the order of event firing in
// the traditional mouse environment, should multiple handlers be registered
// on the same element for different events.
//
// The current version exposes the following virtual events to jQuery bind methods:
// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
(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(" "),
activeDocHandlers = {},
resetTimerID = 0,
startX = 0,
startY = 0,
didScroll = false,
clickBlockList = [],
blockMouseTriggers = false,
blockTouchTriggers = false,
eventCaptureSupported = $.support.eventCapture,
$document = $(document),
nextTouchID = 1,
lastTouchID = 0;
$.vmouse = {
moveDistanceThreshold: 10,
clickDistanceThreshold: 10,
resetTimerDuration: 1500
};
function getNativeEvent(event) {
while (event && typeof event.originalEvent !== "undefined") {
event = event.originalEvent;
}
return event;
}
function createVirtualEvent(event, eventType) {
var t = event.type,
oe, props, ne, prop, ct, touch, i, j;
event = $.Event(event);
event.type = eventType;
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 (i = props.length,prop; i;) {
prop = props[ --i ];
event[ prop ] = oe[ 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 ];
}
}
}
return event;
}
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;
}
return flags;
}
function getClosestElementWithVirtualBinding(element, eventType) {
var b;
while (element) {
b = $.data(element, dataPropertyName);
if (b && ( !eventType || b[ eventType ] )) {
return element;
}
element = element.parentNode;
}
return null;
}
function enableTouchBindings() {
blockTouchTriggers = false;
}
function disableTouchBindings() {
blockTouchTriggers = true;
}
function enableMouseBindings() {
lastTouchID = 0;
clickBlockList.length = 0;
blockMouseTriggers = false;
// When mouse bindings are enabled, our
// touch bindings are disabled.
disableTouchBindings();
}
function disableMouseBindings() {
// When mouse bindings are disabled, our
// touch bindings are enabled.
enableTouchBindings();
}
function startResetTimer() {
clearResetTimer();
resetTimerID = setTimeout(function() {
resetTimerID = 0;
enableMouseBindings();
}, $.vmouse.resetTimerDuration);
}
function clearResetTimer() {
if (resetTimerID) {
clearTimeout(resetTimerID);
resetTimerID = 0;
}
}
function triggerVirtualEvent(eventType, event, flags) {
var ve;
if (( flags && flags[ eventType ] ) ||
( !flags && getClosestElementWithVirtualBinding(event.target, eventType) )) {
ve = createVirtualEvent(event, eventType);
$(event.target).trigger(ve);
}
return ve;
}
function mouseEventCallback(event) {
var touchID = $.data(event.target, touchTargetPropertyName);
if (!blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID )) {
var ve = triggerVirtualEvent("v" + event.type, event);
if (ve) {
if (ve.isDefaultPrevented()) {
event.preventDefault();
}
if (ve.isPropagationStopped()) {
event.stopPropagation();
}
if (ve.isImmediatePropagationStopped()) {
event.stopImmediatePropagation();
}
}
}
}
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);
clearResetTimer();
disableMouseBindings();
didScroll = false;
var t = getNativeEvent(event).touches[ 0 ];
startX = t.pageX;
startY = t.pageY;
triggerVirtualEvent("vmouseover", event, flags);
triggerVirtualEvent("vmousedown", event, flags);
}
}
}
function handleScroll(event) {
if (blockTouchTriggers) {
return;
}
if (!didScroll) {
triggerVirtualEvent("vmousecancel", event, getVirtualBindingFlags(event.target));
}
didScroll = true;
startResetTimer();
}
function handleTouchMove(event) {
if (blockTouchTriggers) {
return;
}
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 ),
flags = getVirtualBindingFlags(event.target);
if (didScroll && !didCancel) {
triggerVirtualEvent("vmousecancel", event, flags);
}
triggerVirtualEvent("vmousemove", event, flags);
startResetTimer();
}
function handleTouchEnd(event) {
if (blockTouchTriggers) {
return;
}
disableTouchBindings();
var flags = getVirtualBindingFlags(event.target),
t;
triggerVirtualEvent("vmouseup", event, flags);
if (!didScroll) {
var ve = triggerVirtualEvent("vclick", event, flags);
if (ve && ve.isDefaultPrevented()) {
// 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.
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);
didScroll = false;
startResetTimer();
}
function hasVirtualBindings(ele) {
var bindings = $.data(ele, dataPropertyName),
k;
if (bindings) {
for (k in bindings) {
if (bindings[ k ]) {
return true;
}
}
}
return false;
}
function dummyMouseHandler() {
}
function getSpecialEventObject(eventType) {
var realType = eventType.substr(1);
return {
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 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;
// 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);
}
// 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);
// For now, if event capture is not supported, we rely on mouse handlers.
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)
// 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);
}
}
},
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) {
// 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);
// 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.
$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);
}
}
};
}
// 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 ]);
}
// 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,
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
// vclick events that had preventDefault() called on it. If we find
// one, then we block the click.
//
// Why do we have to rely on proximity?
//
// Because the target of the touch event that triggered the vclick
// can be different from the target of the click event synthesized
// by the browser. The target of a mouse/click event that is syntehsized
// from a touch event seems to be implementation specific. For example,
// some browsers will fire mouse/click events for a link that is near
// a touch event, even though the target of the touchstart/touchend event
// says the user touched outside the link. Also, it seems that with most
// browsers, the target of the mouse/click event is not calculated until the
// time it is dispatched, so if you replace an element that you touched
// with another element, the target of the mouse/click will be the new
// element underneath that point.
//
// Aside from proximity, we also check to see if the target and any
// of its ancestors were the ones that blocked a click. This is necessary
// because of the strange mouse/click target calculation done in the
// Android 2.1 browser, where if you click on an element, and there is a
// 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.
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();
e.stopPropagation();
return;
}
}
ele = ele.parentNode;
}
}
}, true);
}
})(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($, window, undefined) {
// add new event shortcuts
$.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;
});
var supportTouch = $.support.touch,
scrollEvent = "touchmove scroll",
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
function triggerCustomEvent(obj, eventType, event) {
var originalType = event.type;
event.type = eventType;
$.event.handle.call(obj, event);
event.type = originalType;
}
// 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);
}, 50);
});
}
};
// also handles taphold
$.event.special.tap = {
setup: function() {
var thisObject = this,
$this = $(thisObject);
$this.bind("vmousedown", function(event) {
if (event.which && event.which !== 1) {
return false;
}
var origTarget = event.target,
origEvent = event.originalEvent,
timer;
function clearTapTimer() {
clearTimeout(timer);
}
function clearTapHandlers() {
clearTapTimer();
$this.unbind("vclick", clickHandler)
.unbind("vmouseup", clearTapTimer)
.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("vmouseup", clearTapTimer)
.bind("vclick", clickHandler);
timer = setTimeout(function() {
triggerCustomEvent(thisObject, "taphold", $.Event("taphold"));
}, 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) {
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;
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");
}
}
start = stop = undefined;
});
});
}
};
(function($, window) {
// "Cowboy" Ben Alman
var win = $(window),
special_event,
get_orientation,
last_orientation;
$.event.special.orientationchange = special_event = {
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;
}
// 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);
},
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;
}
// Because the orientationchange event doesn't exist, unbind the
// resize event handler.
win.unbind("throttledresize", handler);
},
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, window);
// 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",
taphold: "tap",
swipeleft: "swipe",
swiperight: "swipe"
}, function(event, sourceEvent) {
$.event.special[ event ] = {
setup: function() {
$(this).bind(sourceEvent, $.noop);
}
};
});
})(jQuery, this);
/*!
* jQuery hashchange event - v1.3 - 7/21/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
// Script: jQuery hashchange event
//
// *Version: 1.3, Last updated: 7/21/2010*
//
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
// GitHub - http://github.com/cowboy/jquery-hashchange/
// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
//
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
//
// About: Known issues
//
// While this jQuery hashchange event implementation is quite stable and
// robust, there are a few unfortunate browser bugs surrounding expected
// hashchange event-based behaviors, independent of any JavaScript
// window.onhashchange abstraction. See the following examples for more
// information:
//
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
//
// Also note that should a browser natively support the window.onhashchange
// event, but not report that it does, the fallback polling loop will be used.
//
// About: Release History
//
// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
// "removable" for mobile-only development. Added IE6/7 document.title
// support. Attempted to make Iframe as hidden as possible by using
// techniques from http://www.paciellogroup.com/blog/?p=604. Added
// support for the "shortcut" format $(window).hashchange( fn ) and
// $(window).hashchange() like jQuery provides for built-in events.
// Renamed jQuery.hashchangeDelay to
and
// lowered its default value to 50. Added
// and properties plus document-domain.html
// file to address access denied issues when setting document.domain in
// IE6/7.
// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
// from a page on another domain would cause an error in Safari 4. Also,
// IE6/7 Iframe is now inserted after the body (this actually works),
// which prevents the page from scrolling when the event is first bound.
// Event can also now be bound before DOM ready, but it won't be usable
// before then in IE6/7.
// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
// where browser version is incorrectly reported as 8.0, despite
// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
// window.onhashchange functionality into a separate plugin for users
// who want just the basic event & back button support, without all the
// extra awesomeness that BBQ provides. This plugin will be included as
// part of jQuery BBQ, but also be available separately.
(function($, window, undefined) {
'$:nomunge'; // Used by YUI compressor.
// Reused string.
var str_hashchange = 'hashchange',
// Method / object references.
doc = document,
fake_onhashchange,
special = $.event.special,
// Does the browser support window.onhashchange? Note that IE8 running in
// IE7 compatibility mode reports true for 'onhashchange' in window, even
// though the event isn't supported, so also test document.documentMode.
doc_mode = doc.documentMode,
supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
// Get location.hash (or what you'd expect location.hash to be) sans any
// leading #. Thanks for making this necessary, Firefox!
function get_fragment(url) {
url = url || location.href;
return '#' + url.replace(/^[^#]*#?(.*)$/, '$1');
}
;
// Method: jQuery.fn.hashchange
//
// Bind a handler to the window.onhashchange event or trigger all bound
// window.onhashchange event handlers. This behavior is consistent with
// jQuery's built-in event handlers.
//
// Usage:
//
// > jQuery(window).hashchange( [ handler ] );
//
// Arguments:
//
// handler - (Function) Optional handler to be bound to the hashchange
// event. This is a "shortcut" for the more verbose form:
// jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
// all bound window.onhashchange event handlers will be triggered. This
// is a shortcut for the more verbose
// jQuery(window).trigger( 'hashchange' ). These forms are described in
// the section.
//
// Returns:
//
// (jQuery) The initial jQuery collection of elements.
// Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
// $(elem).hashchange() for triggering, like jQuery does for built-in events.
$.fn[ str_hashchange ] = function(fn) {
return fn ? this.bind(str_hashchange, fn) : this.trigger(str_hashchange);
};
// Property: jQuery.fn.hashchange.delay
//
// The numeric interval (in milliseconds) at which the
// polling loop executes. Defaults to 50.
// Property: jQuery.fn.hashchange.domain
//
// If you're setting document.domain in your JavaScript, and you want hash
// history to work in IE6/7, not only must this property be set, but you must
// also set document.domain BEFORE jQuery is loaded into the page. This
// property is only applicable if you are supporting IE6/7 (or IE8 operating
// in "IE7 compatibility" mode).
//
// In addition, the property must be set to the
// path of the included "document-domain.html" file, which can be renamed or
// modified if necessary (note that the document.domain specified must be the
// same in both your main JavaScript as well as in this file).
//
// Usage:
//
// jQuery.fn.hashchange.domain = document.domain;
// Property: jQuery.fn.hashchange.src
//
// If, for some reason, you need to specify an Iframe src file (for example,
// when setting document.domain as in ), you can
// do so using this property. Note that when using this property, history
// won't be recorded in IE6/7 until the Iframe src file loads. This property
// is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
// compatibility" mode).
//
// Usage:
//
// jQuery.fn.hashchange.src = 'path/to/file.html';
$.fn[ str_hashchange ].delay = 50;
/*
$.fn[ str_hashchange ].domain = null;
$.fn[ str_hashchange ].src = null;
*/
// Event: hashchange event
//
// Fired when location.hash changes. In browsers that support it, the native
// HTML5 window.onhashchange event is used, otherwise a polling loop is
// initialized, running every milliseconds to
// see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
// compatibility" mode), a hidden Iframe is created to allow the back button
// and hash-based history to work.
//
// Usage as described in :
//
// > // Bind an event handler.
// > jQuery(window).hashchange( function(e) {
// > var hash = location.hash;
// > ...
// > });
// >
// > // Manually trigger the event handler.
// > jQuery(window).hashchange();
//
// A more verbose usage that allows for event namespacing:
//
// > // Bind an event handler.
// > jQuery(window).bind( 'hashchange', function(e) {
// > var hash = location.hash;
// > ...
// > });
// >
// > // Manually trigger the event handler.
// > jQuery(window).trigger( 'hashchange' );
//
// Additional Notes:
//
// * The polling loop and Iframe are not created until at least one handler
// is actually bound to the 'hashchange' event.
// * If you need the bound handler(s) to execute immediately, in cases where
// a location.hash exists on page load, via bookmark or page refresh for
// example, use jQuery(window).hashchange() or the more verbose
// jQuery(window).trigger( 'hashchange' ).
// * The event can be bound before DOM ready, but since it won't be usable
// before then in IE6/7 (due to the necessary Iframe), recommended usage is
// to bind it inside a DOM ready handler.
// Override existing $.event.special.hashchange methods (allowing this plugin
// to be defined after jQuery BBQ in BBQ's source code).
special[ str_hashchange ] = $.extend(special[ str_hashchange ], {
// Called only when the first 'hashchange' event is bound to window.
setup: function() {
// If window.onhashchange is supported natively, there's nothing to do..
if (supports_onhashchange) {
return false;
}
// Otherwise, we need to create our own. And we don't want to call this
// until the user binds to the event, just in case they never do, since it
// will create a polling loop and possibly even a hidden Iframe.
$(fake_onhashchange.start);
},
// Called only when the last 'hashchange' event is unbound from window.
teardown: function() {
// If window.onhashchange is supported natively, there's nothing to do..
if (supports_onhashchange) {
return false;
}
// Otherwise, we need to stop ours (if possible).
$(fake_onhashchange.stop);
}
});
// fake_onhashchange does all the work of triggering the window.onhashchange
// event for browsers that don't natively support it, including creating a
// polling loop to watch for hash changes and in IE 6/7 creating a hidden
// Iframe to enable back and forward.
fake_onhashchange = (function() {
var self = {},
timeout_id,
// Remember the initial hash so it doesn't get triggered immediately.
last_hash = get_fragment(),
fn_retval = function(val) {
return val;
},
history_set = fn_retval,
history_get = fn_retval;
// Start the polling loop.
self.start = function() {
timeout_id || poll();
};
// Stop the polling loop.
self.stop = function() {
timeout_id && clearTimeout(timeout_id);
timeout_id = undefined;
};
// This polling loop checks every $.fn.hashchange.delay milliseconds to see
// if location.hash has changed, and triggers the 'hashchange' event on
// window when necessary.
function poll() {
var hash = get_fragment(),
history_hash = history_get(last_hash);
if (hash !== last_hash) {
history_set(last_hash = hash, history_hash);
$(window).trigger(str_hashchange);
} else if (history_hash !== last_hash) {
location.href = location.href.replace(/#.*/, '') + history_hash;
}
timeout_id = setTimeout(poll, $.fn[ str_hashchange ].delay);
}
;
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
$.browser.msie && !supports_onhashchange && (function() {
// Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
// when running in "IE7 compatibility" mode.
var iframe,
iframe_src;
// When the event is bound and polling starts in IE 6/7, create a hidden
// Iframe for history handling.
self.start = function() {
if (!iframe) {
iframe_src = $.fn[ str_hashchange ].src;
iframe_src = iframe_src && iframe_src + get_fragment();
// Create hidden Iframe. Attempt to make Iframe as hidden as possible
// by using techniques from http://www.paciellogroup.com/blog/?p=604.
iframe = $('').hide()
// When Iframe has completely loaded, initialize the history and
// start polling.
.one('load', function() {
iframe_src || history_set(get_fragment());
poll();
})
// Load Iframe src if specified, otherwise nothing.
.attr('src', iframe_src || 'javascript:0')
// Append Iframe after the end of the body to prevent unnecessary
// initial page scrolling (yes, this works).
.insertAfter('body')[0].contentWindow;
// Whenever `document.title` changes, update the Iframe's title to
// prettify the back/next history menu entries. Since IE sometimes
// errors with "Unspecified error" the very first time this is set
// (yes, very useful) wrap this with a try/catch block.
doc.onpropertychange = function() {
try {
if (event.propertyName === 'title') {
iframe.document.title = doc.title;
}
} catch(e) {
}
};
}
};
// Override the "stop" method since an IE6/7 Iframe was created. Even
// if there are no longer any bound event handlers, the polling loop
// is still necessary for back/next to work at all!
self.stop = fn_retval;
// Get history by looking at the hidden Iframe's location.hash.
history_get = function() {
return get_fragment(iframe.location.href);
};
// Set a new history item by opening and then closing the Iframe
// document, *then* setting its location.hash. If document.domain has
// been set, update that as well.
history_set = function(hash, history_hash) {
var iframe_doc = iframe.document,
domain = $.fn[ str_hashchange ].domain;
if (hash !== history_hash) {
// Update Iframe with any initial `document.title` that might be set.
iframe_doc.title = doc.title;
// Opening the Iframe's document after it has been closed is what
// actually adds a history entry.
iframe_doc.open();
// Set document.domain for the Iframe document as well, if necessary.
domain && iframe_doc.write('');
iframe_doc.close();
// Update the Iframe's hash, for great justice.
iframe.location.hash = hash;
}
};
})();
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return self;
})();
})(jQuery, this);
/*
* jQuery Mobile Framework : "page" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.widget("mobile.page", $.mobile.widget, {
options: {
theme: "c",
domCache: false
},
_create: function() {
this._trigger("beforecreate");
this.element
.attr("tabindex", "0")
.addClass("ui-page ui-body-" + this.options.theme);
}
});
})(jQuery);
/*!
* jQuery Mobile v1.0b3pre
* http://jquerymobile.com/
*
* Copyright 2010, jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, window, undefined) {
// jQuery.mobile configurable options
$.extend($.mobile, {
// 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
subPageUrlKey: "ui-page",
// Class assigned to page currently in view, and during transitions
activePageClass: "ui-page-active",
// Class used for "active" button state, from CSS framework
activeBtnClass: "ui-btn-active",
// 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: 250,
// 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",
//automatically initialize the DOM when it's ready
autoInitializePage: true,
pushStateEnabled: 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 ?
keyCode: {
ALT: 18,
BACKSPACE: 8,
CAPS_LOCK: 20,
COMMA: 188,
COMMAND: 91,
COMMAND_LEFT: 91, // COMMAND
COMMAND_RIGHT: 93,
CONTROL: 17,
DELETE: 46,
DOWN: 40,
END: 35,
ENTER: 13,
ESCAPE: 27,
HOME: 36,
INSERT: 45,
LEFT: 37,
MENU: 93, // COMMAND_RIGHT
NUMPAD_ADD: 107,
NUMPAD_DECIMAL: 110,
NUMPAD_DIVIDE: 111,
NUMPAD_ENTER: 108,
NUMPAD_MULTIPLY: 106,
NUMPAD_SUBTRACT: 109,
PAGE_DOWN: 34,
PAGE_UP: 33,
PERIOD: 190,
RIGHT: 39,
SHIFT: 16,
SPACE: 32,
TAB: 9,
UP: 38,
WINDOWS: 91 // COMMAND
},
// Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
silentScroll: function(ypos) {
if ($.type(ypos) !== "number") {
ypos = $.mobile.defaultHomeScroll;
}
// prevent scrollstart and scrollstop events
$.event.special.scrollstart.enabled = false;
setTimeout(function() {
window.scrollTo(0, ypos);
$(document).trigger("silentscroll", { x: 0, y: ypos });
}, 20);
setTimeout(function() {
$.event.special.scrollstart.enabled = true;
}, 150);
},
// 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.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;
$.find = function(selector, context, ret, extra) {
selector = selector.replace(/:jqmData\(([^)]*)\)/g, "[data-" + ( $.mobile.ns || "" ) + "$1]");
return oldFind.call(this, selector, context, ret, extra);
};
$.extend($.find, oldFind);
$.find.matches = function(expr, set) {
return $.find(expr, null, null, set);
};
$.find.matchesSelector = function(node, expr) {
return $.find(expr, null, null, [ node ]).length > 0;
};
})(jQuery, this);
/*
* jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt,
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
( function($, undefined) {
//define vars for interal use
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 ($.type(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)) {
// 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, "");
}
return absUrl;
},
//get path from current hash, or from a file path
get: function(newPath) {
if (newPath === undefined) {
newPath = location.hash;
}
return path.stripHash(newPath).replace(/[^\/]*\.[^\/*]+$/, '');
},
//return the substring of a filepath before the sub-page key, for making a server request
getFilePath: function(path) {
var splitkey = '&' + $.mobile.subPageUrlKey;
return path && path.split(splitkey)[0].split(dialogHashKey)[0];
},
//set location hash to path
set: function(path) {
location.hash = path;
},
//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) {
return url.replace(documentBase.domain, "");
},
//just return the url without an initial #
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) {
var u = path.parseUrl(url);
return u.protocol && u.domain !== documentUrl.domain ? true : false;
},
hasProtocol: function(url) {
return ( /^(:?\w+:)/ ).test(url);
},
//check if the specified url refers to the first page in the main application document.
isFirstPageUrl: function(url) {
// We only deal with absolute paths.
var u = path.parseUrl(path.makeUrlAbsolute(url, documentBase)),
// Does the url have the same path as the document?
samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ),
// Get the first page element.
fp = $.mobile.firstPage,
// Get the id of the first page element if it has one.
fpId = fp && fp[0] ? fp[0].id : undefined;
// The url refers to the first page if the path matches the document and
// it either has no hash value, or the hash is exactly equal to the id of the
// first page element.
return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace(/^#/, "") === fpId ) );
},
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);
}
},
//will be defined when a link is clicked and given an active class
$activeClickedLink = null,
//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, 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
activeIndex: 0,
//get active
getActive: function() {
return urlHistory.stack[ urlHistory.activeIndex ];
},
getPrev: function() {
return urlHistory.stack[ urlHistory.activeIndex - 1 ];
},
getNext: function() {
return urlHistory.stack[ urlHistory.activeIndex + 1 ];
},
// addNew is used whenever a new page is added
addNew: function(url, transition, title, pageUrl, role) {
//if there's forward history, wipe it
if (urlHistory.getNext()) {
urlHistory.clearForward();
}
urlHistory.stack.push({url : url, transition: transition, title: title, pageUrl: pageUrl, role: role });
urlHistory.activeIndex = urlHistory.stack.length - 1;
},
//wipe urls ahead of active index
clearForward: function() {
urlHistory.stack = urlHistory.stack.slice(0, urlHistory.activeIndex + 1);
},
directHashChange: function(opts) {
var back , forward, newActiveIndex, prev = this.getActive();
// check if url isp in history and if it's ahead or behind current page
$.each(urlHistory.stack, function(i, historyEntry) {
//if the url is in the stack, it's a forward or a back
if (opts.currentUrl === historyEntry.url) {
//define back and forward by whether url is older or newer than current page
back = i < urlHistory.activeIndex;
forward = !back;
newActiveIndex = i;
}
});
// save new page index, null check to prevent falsey 0 result
this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
if (back) {
( opts.either || opts.isBack )(true);
} else if (forward) {
( opts.either || opts.isForward )(false);
}
},
//disable hashchange event listener internally to ignore one change
//toggled internally when location.hash is updated to match the url of a successful page load
ignoreNextHashChange: false
},
//define first selector to receive focus when a page is shown
focusable = "[tabindex],a,button:visible,select:visible,input",
//queue to hold simultanious page transitions
pageTransitionQueue = [],
//indicates whether or not page is in process of transitioning
isPageTransitioning = false,
//nonsense hash change key for dialogs, so they create a history entry
dialogHashKey = "&ui-state=dialog",
//existing base tag?
$base = $head.children("base"),
//tuck away the original document URL minus any fragment.
documentUrl = path.parseUrl(location.href),
//if the document has an embedded base tag, documentBase is set to its
//initial value. If a base tag does not exist, then we default to the documentUrl.
documentBase = $base.length ? path.parseUrl(path.makeUrlAbsolute($base.attr("href"), documentUrl.href)) : documentUrl,
//cache the comparison once.
documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash );
//base element management, defined depending on dynamic base tag support
var base = $.support.dynamicBaseTag ? {
//define base element, for use in routing asset urls that are referenced in Ajax-requested markup
element: ( $base.length ? $base : $("", { href: documentBase.hrefNoHash }).prependTo($head) ),
//set the generated BASE element's href attribute to a new page's base path
set: function(href) {
base.element.attr("href", path.makeUrlAbsolute(href, documentBase));
},
//set the generated BASE element's href attribute to a new page's base path
reset: function() {
base.element.attr("href", documentBase.hrefNoHash);
}
} : undefined;
/*
internal utility functions
--------------------------------------*/
//direct focus to the page title, or otherwise first focusable element
function reFocus(page) {
var pageTitle = page.find(".ui-title:eq(0)");
if (pageTitle.length) {
pageTitle.focus();
}
else {
page.focus();
}
}
//remove active classes after page transition or error
function removeActiveLinkClass(forceRemoval) {
if (!!$activeClickedLink && ( !$activeClickedLink.closest('.ui-page-active').length || forceRemoval )) {
$activeClickedLink.removeClass($.mobile.activeBtnClass);
}
$activeClickedLink = null;
}
function releasePageTransitionLock() {
isPageTransitioning = false;
if (pageTransitionQueue.length > 0) {
$.mobile.changePage.apply(null, pageTransitionQueue.pop());
}
}
// Save the last scroll distance per page, before it is hidden
var getLastScroll = (function(lastScrollEnabled) {
return function() {
if (!lastScrollEnabled) {
lastScrollEnabled = true;
return;
}
lastScrollEnabled = false;
var active = $.mobile.urlHistory.getActive(),
activePage = $(".ui-page-active"),
scrollElem = $(window),
touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled;
if (touchOverflow) {
scrollElem = activePage.is(".ui-native-fixed") ? activePage.find(".ui-content") : activePage;
}
if (active) {
var lastScroll = scrollElem.scrollTop();
// Set active page's lastScroll prop.
// If the Y location we're scrolling to is less than minScrollBack, let it go.
active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll;
}
};
})(true);
// to get last scroll, we need to get scrolltop before the page change
// using beforechangepage or popstate/hashchange (whichever comes first)
$(document).bind("beforechangepage", getLastScroll);
$(window).bind($.support.pushState ? "popstate" : "hashchange", getLastScroll);
// Make the iOS clock quick-scroll work again if we're using native overflow scrolling
/*
if( $.support.touchOverflow ){
if( $.mobile.touchOverflowEnabled ){
$( window ).bind( "scrollstop", function(){
if( $( this ).scrollTop() === 0 ){
$.mobile.activePage.scrollTop( 0 );
}
});
}
}
*/
//function for transitioning between two existing pages
function transitionPages(toPage, fromPage, transition, reverse) {
//get current scroll distance
var active = $.mobile.urlHistory.getActive(),
touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled,
toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ),
screenHeight = getScreenHeight();
// Scroll to top, hide addr bar
window.scrollTo(0, $.mobile.defaultHomeScroll);
if (fromPage) {
//trigger before show/hide events
fromPage.data("page")._trigger("beforehide", null, { nextPage: toPage });
}
if (!touchOverflow) {
toPage.height(screenHeight + toScroll);
}
toPage.data("page")._trigger("beforeshow", null, { prevPage: fromPage || $("") });
//clear page loader
$.mobile.hidePageLoadingMsg();
if (touchOverflow && toScroll) {
toPage.addClass("ui-mobile-pre-transition");
// Send focus to page as it is now display: block
reFocus(toPage);
//set page's scrollTop to remembered distance
if (toPage.is(".ui-native-fixed")) {
toPage.find(".ui-content").scrollTop(toScroll);
}
else {
toPage.scrollTop(toScroll);
}
}
//find the transition handler for the specified transition. If there
//isn't one in our transitionHandlers dictionary, use the default one.
//call the handler immediately to kick-off the transition.
var th = $.mobile.transitionHandlers[transition || "none"] || $.mobile.defaultTransitionHandler,
promise = th(transition, reverse, toPage, fromPage);
promise.done(function() {
//reset toPage height back
if (!touchOverflow) {
toPage.height("");
// Send focus to the newly shown page
reFocus(toPage);
}
// Jump to top or prev scroll, sometimes on iOS the page has not rendered yet.
if (!touchOverflow) {
$.mobile.silentScroll(toScroll);
}
//trigger show/hide events
if (fromPage) {
if (!touchOverflow) {
fromPage.height("");
}
fromPage.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(),
port = orientation === "portrait",
winMin = port ? 480 : 320,
screenHeight = port ? screen.availHeight : screen.availWidth,
winHeight = Math.max(winMin, $(window).height()),
pageMin = Math.min(screenHeight, winHeight);
return pageMin;
}
$.mobile.getScreenHeight = getScreenHeight;
//simply set the active page's minimum height to screen height, depending on orientation
function resetActivePageHeight() {
$("." + $.mobile.activePageClass).css("min-height", getScreenHeight());
}
//shared page enhancements
function enhancePage($page, role) {
// If a role was specified, make sure the data-role attribute
// on the page element is in sync.
if (role) {
$page.attr("data-" + $.mobile.ns + "role", role);
}
//run page plugin
$page.page();
}
/* exposed $.mobile methods */
//animation complete callback
$.fn.animationComplete = function(callback) {
if ($.support.cssTransitions) {
return $(this).one('webkitAnimationEnd', callback);
}
else {
// defer execution for consistency between webkit/non webkit
setTimeout(callback, 0);
return $(this);
}
};
//update location.hash, with or without triggering hashchange event
//TODO - deprecate this one at 1.0
$.mobile.updateHash = path.set;
//expose path object on $.mobile
$.mobile.path = path;
//expose base object on $.mobile
$.mobile.base = base;
//url stack, useful when plugins need to be aware of previous pages viewed
//TODO: deprecate this one at 1.0
$.mobile.urlstack = urlHistory.stack;
//history stack
$.mobile.urlHistory = urlHistory;
$.mobile.dialogHashKey = dialogHashKey;
//default non-animation transition handler
$.mobile.noneTransitionHandler = function(name, reverse, $toPage, $fromPage) {
if ($fromPage) {
$fromPage.removeClass($.mobile.activePageClass);
}
$toPage.addClass($.mobile.activePageClass);
return $.Deferred().resolve(name, reverse, $toPage, $fromPage).promise();
};
//default handler for unknown transitions
$.mobile.defaultTransitionHandler = $.mobile.noneTransitionHandler;
//transition handler dictionary for 3rd party transitions
$.mobile.transitionHandlers = {
none: $.mobile.defaultTransitionHandler
};
//enable cross-domain page support
$.mobile.allowCrossDomainPages = false;
//return the original document url
$.mobile.getDocumentUrl = function(asParsedObject) {
return asParsedObject ? $.extend({}, documentUrl) : documentUrl.href;
};
//return the original document base url
$.mobile.getDocumentBase = function(asParsedObject) {
return asParsedObject ? $.extend({}, documentBase) : documentBase.href;
};
// Load a page into the DOM.
$.mobile.loadPage = function(url, options) {
// This function uses deferred notifications to let callers
// know when the page is done loading, or if an error has occurred.
var deferred = $.Deferred(),
// The default loadPage options with overrides specified by
// the caller.
settings = $.extend({}, $.mobile.loadPage.defaults, options),
// The DOM element for the page after it has been loaded.
page = null,
// If the reloadPage option is true, and the page is already
// in the DOM, dupCachedPage will be set to the page element
// so that it can be removed after the new version of the
// 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, findBaseWithDefault());
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if (settings.data && settings.type === "get") {
absUrl = path.addSearchParams(absUrl, settings.data);
settings.data = undefined;
}
// The absolute version of the URL minus any dialog/subpage params.
// In otherwords the real URL of the page to be loaded.
var fileUrl = path.getFilePath(absUrl),
// The version of the Url actually stored in the data-url attribute of
// the page. For embedded pages, it is just the id of the page. For pages
// within the same domain as the document base, it is the site relative
// path. For cross-domain pages (Phone Gap only) the entire absolute Url
// used to load the page.
dataUrl = path.convertUrlToDataUrl(absUrl);
// Make sure we have a pageContainer to work with.
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
// Check to see if the page already exists in the DOM.
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
// If we failed to find a page in the DOM, check the URL to see if it
// refers to the first page in the application.
if (page.length === 0 && $.mobile.firstPage && path.isFirstPageUrl(absUrl)) {
page = $($.mobile.firstPage);
}
// Reset base to the default document base.
if (base) {
base.reset();
}
// If the page we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Otherwise, track the
// existing page as a duplicated.
if (page.length) {
if (!settings.reloadPage) {
enhancePage(page, settings.role);
deferred.resolve(absUrl, options, page);
return deferred.promise();
}
dupCachedPage = page;
}
if (settings.showLoadMsg) {
// 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,
// TODO handle dialogs again
pageElemRegex = new RegExp(".*(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>).*"),
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);
}
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();
});
}
});
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 + "')");
}
//bind pageHide to removePage after it's hidden, if the page options specify to do so
// Remove loading message.
if (settings.showLoadMsg) {
hideMsg();
}
deferred.resolve(absUrl, options, page, dupCachedPage);
},
error: function() {
//set base back to current path
if (base) {
base.set(path.get());
}
// Remove loading message.
if (settings.showLoadMsg) {
// Remove loading message.
hideMsg();
//show error message
$("" + $.mobile.pageLoadErrorMessage + "
")
.css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
.appendTo(settings.pageContainer)
.delay(800)
.fadeOut(400, function() {
$(this).remove();
});
}
deferred.reject(absUrl, options);
}
});
}
return deferred.promise();
};
$.mobile.loadPage.defaults = {
type: "get",
data: undefined,
reloadPage: false,
role: undefined, // By default we rely on the role defined by the @data-role attribute.
showLoadMsg: false,
pageContainer: undefined,
loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message.
};
// Show a specific page in the page container.
$.mobile.changePage = function(toPage, options) {
// XXX: REMOVE_BEFORE_SHIPPING_1.0
// This is temporary code that makes changePage() compatible with previous alpha versions.
if (typeof options !== "object") {
var opts = null;
// Map old-style call signature for form submit to the new options object format.
if (typeof toPage === "object" && toPage.url && toPage.type) {
opts = {
type: toPage.type,
data: toPage.data,
forcePageLoad: true
};
toPage = toPage.url;
}
// The arguments passed into the function need to be re-mapped
// to the new options object format.
var len = arguments.length;
if (len > 1) {
var argNames = [ "transition", "reverse", "changeHash", "fromHashChange" ], i;
for (i = 1; i < len; i++) {
var a = arguments[ i ];
if (typeof a !== "undefined") {
opts = opts || {};
opts[ argNames[ i - 1 ] ] = a;
}
}
}
// If an options object was created, then we know changePage() was called
// with an old signature.
if (opts) {
return $.mobile.changePage(toPage, opts);
}
}
// XXX: REMOVE_BEFORE_SHIPPING_1.0
// If we are in the midst of a transition, queue the current request.
// We'll call changePage() once we're done with the current transition to
// service the request.
if (isPageTransitioning) {
pageTransitionQueue.unshift(arguments);
return;
}
var settings = $.extend({}, $.mobile.changePage.defaults, options);
// Make sure we have a pageContainer to work with.
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
// Make sure we have a fromPage.
settings.fromPage = settings.fromPage || $.mobile.activePage;
var mpc = settings.pageContainer,
pbcEvent = new $.Event("pagebeforechange"),
triggerData = { toPage: toPage, options: settings };
// Let listeners know we're about to change the current page.
mpc.trigger(pbcEvent, triggerData);
mpc.trigger("beforechangepage", triggerData); // XXX: DEPRECATED for 1.0
// If the default behavior is prevented, stop here!
if (pbcEvent.isDefaultPrevented()) {
return;
}
// We allow "pagebeforechange" observers to modify the toPage in the trigger
// data to allow for redirects. Make sure our toPage is updated.
toPage = triggerData.toPage;
// Set the isPageTransitioning flag to prevent any requests from
// entering this method while we are in the midst of loading a page
// or transitioning.
isPageTransitioning = true;
// If the caller passed us a url, call loadPage()
// to make sure it is loaded into the DOM. We'll listen
// to the promise object it returns so we know when
// it is done loading or if an error ocurred.
if (typeof toPage == "string") {
$.mobile.loadPage(toPage, settings)
.done(function(url, options, newPage, dupCachedPage) {
isPageTransitioning = false;
options.duplicateCachedPage = dupCachedPage;
$.mobile.changePage(newPage, options);
})
.fail(function(url, options) {
// XXX_jblas: Fire off changepagefailed notificaiton.
isPageTransitioning = false;
//clear out the active button state
removeActiveLinkClass(true);
//release transition lock so navigation is free again
releasePageTransitionLock();
settings.pageContainer.trigger("pagechangefailed", triggerData);
settings.pageContainer.trigger("changepagefailed", triggerData); // XXX: DEPRECATED for 1.0
});
return;
}
// The caller passed us a real page DOM element. Update our
// internal state and then trigger a transition to the page.
var fromPage = settings.fromPage,
url = ( settings.dataUrl && path.convertUrlToDataUrl(settings.dataUrl) ) || toPage.jqmData("url"),
// The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path
pageUrl = url,
fileUrl = path.getFilePath(url),
active = urlHistory.getActive(),
activeIsInitialPage = urlHistory.activeIndex === 0,
historyDir = 0,
pageTitle = document.title,
isDialog = settings.role === "dialog" || toPage.jqmData("role") === "dialog";
// If we are trying to transition to the same page that we are currently on ignore the request.
// an illegal same page request is defined by the current page being the same as the url, as long as there's history
// and toPage is not an array or object (those are allowed to be "same")
//
// XXX_jblas: We need to remove this at some point when we allow for transitions
// to the same page.
if (fromPage && fromPage[0] === toPage[0]) {
isPageTransitioning = false;
mpc.trigger("pagechange", triggerData);
mpc.trigger("changepage", triggerData); // XXX: DEPRECATED for 1.0
return;
}
// We need to make sure the page we are given has already been enhanced.
enhancePage(toPage, settings.role);
// If the changePage request was sent from a hashChange event, check to see if the
// page is already within the urlHistory stack. If so, we'll assume the user hit
// the forward/back button and will try to match the transition accordingly.
if (settings.fromHashChange) {
urlHistory.directHashChange({
currentUrl: url,
isBack: function() {
historyDir = -1;
},
isForward: function() {
historyDir = 1;
}
});
}
// Kill the keyboard.
// XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
// we should be tracking focus with a live() handler so we already have
// the element in hand at this point.
// Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
// is undefined when we are in an IFrame.
try {
$(document.activeElement || "").add("input:focus, textarea:focus, select:focus").blur();
} catch(e) {
}
// If we're displaying the page as a dialog, we don't want the url
// for the dialog content to be used in the hash. Instead, we want
// to append the dialogHashKey to the url of the current page.
if (isDialog && active) {
// on the initial page load active.url is undefined and in that case should
// be an empty string. Moving the undefined -> empty string back into
// urlHistory.addNew seemed imprudent given undefined better represents
// the url state
url = ( active.url || "" ) + dialogHashKey;
}
// Set the location hash.
if (settings.changeHash !== false && url) {
//disable hash listening temporarily
urlHistory.ignoreNextHashChange = true;
//update hash and history
path.set(url);
}
//if title element wasn't found, try the page div data attr too
var newPageTitle = toPage.jqmData("title") || toPage.children(":jqmData(role='header')").find(".ui-title").text();
if (!!newPageTitle && pageTitle == document.title) {
pageTitle = newPageTitle;
}
//add page to history stack if it's not back or forward
if (!historyDir) {
urlHistory.addNew(url, settings.transition, pageTitle, pageUrl, settings.role);
}
//set page title
document.title = urlHistory.getActive().title;
//set "toPage" as activePage
$.mobile.activePage = toPage;
// Make sure we have a transition defined.
settings.transition = settings.transition
|| ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined )
|| ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
// If we're navigating back in the URL history, set reverse accordingly.
settings.reverse = settings.reverse || historyDir < 0;
transitionPages(toPage, fromPage, settings.transition, settings.reverse)
.done(function() {
removeActiveLinkClass();
//if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
if (settings.duplicateCachedPage) {
settings.duplicateCachedPage.remove();
}
//remove initial build class (only present on first pageshow)
$html.removeClass("ui-mobile-rendering");
releasePageTransitionLock();
// Let listeners know we're all done changing the current page.
mpc.trigger("pagechange", triggerData);
mpc.trigger("changepage", triggerData); // XXX: DEPRECATED for 1.0
});
};
$.mobile.changePage.defaults = {
transition: undefined,
reverse: false,
changeHash: true,
fromHashChange: false,
role: undefined, // By default we rely on the role defined by the @data-role attribute.
duplicateCachedPage: undefined,
pageContainer: undefined,
showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
dataUrl: undefined,
fromPage: undefined
};
/* Event Bindings - hashchange, submit, and click */
function findClosestLink(ele) {
while (ele) {
if (ele.nodeName.toLowerCase() == "a") {
break;
}
ele = ele.parentNode;
}
return ele;
}
// The base URL for any given element depends on the page it resides in.
function getClosestBaseUrl(ele) {
// Find the closest page and extract out its url.
var url = $(ele).closest(".ui-page").jqmData("url"),
base = documentBase.hrefNoHash;
if (!url || !path.isPath(url)) {
url = base;
}
return path.makeUrlAbsolute(url, base);
}
//The following event bindings should be bound after mobileinit has been triggered
//the following function is called in the init file
$.mobile._registerInternalEvents = function() {
//bind to form submit events, handle with Ajax
$("form").live('submit', function(event) {
var $this = $(this);
if (!$.mobile.ajaxEnabled ||
$this.is(":jqmData(ajax='false')")) {
return;
}
var type = $this.attr("method"),
target = $this.attr("target"),
url = $this.attr("action");
// If no action is specified, browsers default to using the
// URL of the document containing the form. Since we dynamically
// pull in pages from external documents, the form should submit
// to the URL for the source document of the page containing
// the form.
if (!url) {
// Get the @data-url for the page containing the form.
url = getClosestBaseUrl($this);
if (url === documentBase.hrefNoHash) {
// The url we got back matches the document base,
// which means the page must be an internal/embedded page,
// so default to using the actual document url as a browser
// would.
url = documentUrl.hrefNoSearch;
}
}
url = path.makeUrlAbsolute(url, getClosestBaseUrl($this));
//external submits use regular HTTP
if (path.isExternal(url) || target) {
return;
}
$.mobile.changePage(
url,
{
type: type && type.length && type.toLowerCase() || "get",
data: $this.serialize(),
transition: $this.jqmData("transition"),
direction: $this.jqmData("direction"),
reloadPage: true
}
);
event.preventDefault();
});
//add active state on vclick
$(document).bind("vclick", function(event) {
var link = findClosestLink(event.target);
if (link) {
if (path.parseUrl(link.getAttribute("href") || "#").hash !== "#") {
removeActiveLinkClass(true);
$activeClickedLink = $(link).closest(".ui-btn").not(".ui-disabled");
$activeClickedLink.addClass($.mobile.activeBtnClass);
$("." + $.mobile.activePageClass + " .ui-btn").not(link).blur();
}
}
});
// click routing - direct to HTTP or Ajax, accordingly
$(document).bind("click", function(event) {
var link = findClosestLink(event.target);
if (!link) {
return;
}
var $link = $(link),
//remove active link class if external (then it won't be there if you come back)
httpCleanup = function() {
window.setTimeout(function() {
removeActiveLinkClass(true);
}, 200);
};
//if there's a data-rel=back attr, go back in history
if ($link.is(":jqmData(rel='back')")) {
window.history.back();
return false;
}
//if ajax is disabled, exit early
if (!$.mobile.ajaxEnabled) {
httpCleanup();
//use default click handling
return;
}
var baseUrl = getClosestBaseUrl($link),
//get href, if defined, otherwise default to empty hash
href = path.makeUrlAbsolute($link.attr("href") || "#", baseUrl);
// XXX_jblas: Ideally links to application pages should be specified as
// an url to the application document with a hash that is either
// the site relative path or id to the page. But some of the
// internal code that dynamically generates sub-pages for nested
// lists and select dialogs, just write a hash in the link they
// create. This means the actual URL path is based on whatever
// the current value of the base tag is at the time this code
// is called. For now we are just assuming that any url with a
// hash in it is an application page reference.
if (href.search("#") != -1) {
href = href.replace(/[^#]*#/, "");
if (!href) {
//link was an empty hash meant purely
//for interaction, so we ignore it.
event.preventDefault();
return;
} else if (path.isPath(href)) {
//we have apath so make it the href we want to load.
href = path.makeUrlAbsolute(href, baseUrl);
} else {
//we have a simple id so use the documentUrl as its base.
href = path.makeUrlAbsolute("#" + href, documentUrl.hrefNoHash);
}
}
// Should we handle this link, or let the browser deal with it?
var useDefaultUrlHandling = $link.is("[rel='external']") || $link.is(":jqmData(ajax='false')") || $link.is("[target]"),
// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
// requests if the document doing the request was loaded via the file:// protocol.
// This is usually to allow the application to "phone home" and fetch app specific
// data. We normally let the browser handle external/cross-domain urls, but if the
// allowCrossDomainPages option is true, we will allow cross-domain http/https
// requests to go through our page loading logic.
isCrossDomainPageLoad = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search(/^https?:/) != -1 ),
//check for protocol or rel and its not an embedded page
//TODO overlap in logic from isExternal, rel=external check should be
// moved into more comprehensive isExternalLink
isExternal = useDefaultUrlHandling || ( path.isExternal(href) && !isCrossDomainPageLoad );
if (isExternal) {
httpCleanup();
//use default click handling
return;
}
//use ajax
var transition = $link.jqmData("transition"),
direction = $link.jqmData("direction"),
reverse = ( direction && direction === "reverse" ) ||
// deprecated - remove by 1.0
$link.jqmData("back"),
//this may need to be more specific as we use data-rel more
role = $link.attr("data-" + $.mobile.ns + "rel") || undefined;
$.mobile.changePage(href, { transition: transition, reverse: reverse, role: role });
event.preventDefault();
});
//prefetch pages when anchors with data-prefetch are encountered
$(".ui-page").live("pageshow.prefetch", function() {
var urls = [];
$(this).find("a:jqmData(prefetch)").each(function() {
var url = $(this).attr("href");
if (url && $.inArray(url, urls) === -1) {
urls.push(url);
$.mobile.loadPage(url);
}
});
});
$.mobile._handleHashChange = function(hash) {
//find first page via hash
var to = path.stripHash(hash),
//transition is false if it's the first page, undefined otherwise (and may be overridden by default)
transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined,
// default options for the changPage calls made after examining the current state
// of the page and the hash
changePageOptions = {
transition: transition,
changeHash: false,
fromHashChange: true
};
//if listening is disabled (either globally or temporarily), or it's a dialog hash
if (!$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange) {
urlHistory.ignoreNextHashChange = false;
return;
}
// special case for dialogs
if (urlHistory.stack.length > 1 && to.indexOf(dialogHashKey) > -1) {
// If current active page is not a dialog skip the dialog and continue
// in the same direction
if (!$.mobile.activePage.is(".ui-dialog")) {
//determine if we're heading forward or backward and continue accordingly past
//the current dialog
urlHistory.directHashChange({
currentUrl: to,
isBack: function() {
window.history.back();
},
isForward: function() {
window.history.forward();
}
});
// prevent changepage
return;
} else {
// if the current active page is a dialog and we're navigating
// to a dialog use the dialog objected saved in the stack
urlHistory.directHashChange({
currentUrl: to,
// regardless of the direction of the history change
// do the following
either: function(isBack) {
var active = $.mobile.urlHistory.getActive();
to = active.pageUrl;
// make sure to set the role, transition and reversal
// as most of this is lost by the domCache cleaning
$.extend(changePageOptions, {
role: active.role,
transition: active.transition,
reverse: isBack
});
}
});
}
}
//if to is defined, load it
if (to) {
to = ( typeof to === "string" && !path.isPath(to) ) ? ( '#' + to ) : to;
$.mobile.changePage(to, changePageOptions);
} else {
//there's no hash, go to the first page in the dom
$.mobile.changePage($.mobile.firstPage, changePageOptions);
}
};
//hashchange event handler
$window.bind("hashchange", function(e, triggered) {
$.mobile._handleHashChange(location.hash);
});
//set page min-heights to be device specific
$(document).bind("pageshow", resetActivePageHeight);
$(window).bind("throttledresize", resetActivePageHeight);
};//_registerInternalEvents callback
})(jQuery);
/*
* jQuery Mobile Framework : history.pushState support, layered on top of hashchange
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
( function($, window) {
// For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents
// Scope self to pushStateHandler so we can reference it sanely within the
// methods handed off as event handlers
var pushStateHandler = {},
self = pushStateHandler,
$win = $(window),
url = $.mobile.path.parseUrl(location.href);
$.extend(pushStateHandler, {
// TODO move to a path helper, this is rather common functionality
initialFilePath: (function() {
return url.pathname + url.search;
})(),
initialHref: url.hrefNoHash,
// Flag for tracking if a Hashchange naturally occurs after each popstate + replace
hashchangeFired: false,
state: function() {
return {
hash: location.hash || "#" + self.initialFilePath,
title: document.title,
// persist across refresh
initialHref: self.initialHref
};
},
resetUIKeys: function(url) {
var dialog = $.mobile.dialogHashKey,
subkey = "&" + $.mobile.subPageUrlKey,
dialogIndex = url.indexOf(dialog);
if (dialogIndex > -1) {
url = url.slice(0, dialogIndex) + "#" + url.slice(dialogIndex);
} else if (url.indexOf(subkey) > -1) {
url = url.split(subkey).join("#" + subkey);
}
return url;
},
// on hash change we want to clean up the url
// NOTE this takes place *after* the vanilla navigation hash change
// handling has taken place and set the state of the DOM
onHashChange: function(e) {
var href, state;
self.hashchangeFired = true;
// only replaceState when the hash doesn't represent an embeded page
if ($.mobile.path.isPath(location.hash)) {
// propulate the hash when its not available
state = self.state();
// make the hash abolute with the current href
href = $.mobile.path.makeUrlAbsolute(state.hash.replace("#", ""), location.href);
href = self.resetUIKeys(href);
// replace the current url with the new href and store the state
history.replaceState(state, document.title, href);
}
},
// on popstate (ie back or forward) we need to replace the hash that was there previously
// cleaned up by the additional hash handling
onPopState: function(e) {
var poppedState = e.originalEvent.state, holdnexthashchange = false;
// if there's no state its not a popstate we care about, ie chrome's initial popstate
// or forward popstate
if (poppedState) {
// disable any hashchange triggered by the browser
$.mobile.urlHistory.ignoreNextHashChange = true;
// defer our manual hashchange until after the browser fired
// version has come and gone
setTimeout(function() {
// make sure that the manual hash handling takes place
$.mobile.urlHistory.ignoreNextHashChange = false;
// change the page based on the hash
$.mobile._handleHashChange(poppedState.hash);
}, 100);
}
},
init: function() {
$win.bind("hashchange", self.onHashChange);
// Handle popstate events the occur through history changes
$win.bind("popstate", self.onPopState);
// if there's no hash, we need to replacestate for returning to home
if (location.hash === "") {
history.replaceState(self.state(), document.title, location.href);
}
}
});
$(function() {
if ($.mobile.pushStateEnabled && $.support.pushState) {
pushStateHandler.init();
}
});
})(jQuery, this);
/*!
* jQuery Mobile v1.0b3pre
* http://jquerymobile.com/
*
* Copyright 2010, jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, window, undefined) {
function css3TransitionHandler(name, reverse, $to, $from) {
var deferred = new $.Deferred(),
reverseClass = reverse ? " reverse" : "",
viewportClass = "ui-mobile-viewport-transitioning viewport-" + name,
doneFunc = function() {
$to.add($from).removeClass("out in reverse " + name);
if ($from) {
$from.removeClass($.mobile.activePageClass);
}
$to.parent().removeClass(viewportClass);
deferred.resolve(name, reverse, $to, $from);
};
$to.animationComplete(doneFunc);
$to.parent().addClass(viewportClass);
if ($from) {
$from.addClass(name + " out" + reverseClass);
}
$to.addClass($.mobile.activePageClass + " " + name + " in" + reverseClass);
return deferred.promise();
}
// Make our transition handler public.
$.mobile.css3TransitionHandler = css3TransitionHandler;
// If the default transition handler is the 'none' handler, replace it with our handler.
if ($.mobile.defaultTransitionHandler === $.mobile.noneTransitionHandler) {
$.mobile.defaultTransitionHandler = css3TransitionHandler;
}
})(jQuery, this);
/*
* jQuery Mobile Framework : "degradeInputs" plugin - degrades inputs to another type after custom enhancements are made.
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.mobile.page.prototype.options.degradeInputs = {
color: false,
date: false,
datetime: false,
"datetime-local": false,
email: false,
month: false,
number: false,
range: "number",
search: "text",
tel: false,
time: false,
url: false,
week: false
};
$.mobile.page.prototype.options.keepNative = ":jqmData(role='none'), :jqmData(role='nojs')";
//auto self-init widgets
$(document).bind("pagecreate enhance", function(e) {
var page = $(e.target).data("page"),
o = page.options;
// degrade inputs to avoid poorly implemented native functionality
$(e.target).find("input").not(o.keepNative).each(function() {
var $this = $(this),
type = this.getAttribute("type"),
optType = o.degradeInputs[ type ] || "text";
if (o.degradeInputs[ type ]) {
$this.replaceWith(
$("").html($this.clone()).html()
.replace(/\s+type=["']?\w+['"]?/, " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\" ")
);
}
});
});
})(jQuery);
/*
* jQuery Mobile Framework : "dialog" plugin.
* Copyright (c) jQuery Project
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
*/
(function($, window, undefined) {
$.widget("mobile.dialog", $.mobile.widget, {
options: {
closeBtnText : "Close",
theme : "a",
initSelector : ":jqmData(role='dialog')"
},
_create: function() {
var $el = this.element,
pageTheme = $el.attr("class").match(/ui-body-[a-z]/);
if (pageTheme.length) {
$el.removeClass(pageTheme[ 0 ]);
}
$el.addClass("ui-body-" + this.options.theme);
// Class the markup for dialog styling
// Set aria role
$el.attr("role", "dialog")
.addClass("ui-dialog")
.find(":jqmData(role='header')")
.addClass("ui-corner-top ui-overlay-shadow")
.prepend("
" + this.options.closeBtnText + "")
.end()
.find(":jqmData(role='content'),:jqmData(role='footer')")
.last()
.addClass("ui-corner-bottom ui-overlay-shadow");
/* bind events
- clicks and submits should use the closing transition that the dialog opened with
unless a data-transition is specified on the link/form
- if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally
*/
$el.bind("vclick submit", function(event) {
var $target = $(event.target).closest(event.type === "vclick" ? "a" : "form"),
active;
if ($target.length && !$target.jqmData("transition")) {
active = $.mobile.urlHistory.getActive() || {};
$target.attr("data-" + $.mobile.ns + "transition", ( active.transition || $.mobile.defaultDialogTransition ))
.attr("data-" + $.mobile.ns + "direction", "reverse");
}
})
.bind("pagehide", function() {
$(this).find("." + $.mobile.activeBtnClass).removeClass($.mobile.activeBtnClass);
});
},
// Close method goes back in history
close: function() {
window.history.back();
}
});
//auto self-init widgets
$($.mobile.dialog.prototype.options.initSelector).live("pagecreate", function() {
$(this).dialog();
});
})(jQuery, this);
/*
* jQuery Mobile Framework : This plugin handles theming and layout of headers, footers, and content areas
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.mobile.page.prototype.options.backBtnText = "Back";
$.mobile.page.prototype.options.addBackBtn = false;
$.mobile.page.prototype.options.backBtnTheme = null;
$.mobile.page.prototype.options.headerTheme = "a";
$.mobile.page.prototype.options.footerTheme = "a";
$.mobile.page.prototype.options.contentTheme = null;
$(":jqmData(role='page'), :jqmData(role='dialog')").live("pagecreate", function(e) {
var $page = $(this),
o = $page.data("page").options,
pageTheme = o.theme;
$(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", this).each(function() {
var $this = $(this),
role = $this.jqmData("role"),
theme = $this.jqmData("theme"),
$headeranchors,
leftbtn,
rightbtn,
backBtn;
$this.addClass("ui-" + role);
//apply theming and markup modifications to page,header,content,footer
if (role === "header" || role === "footer") {
var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme;
//add theme class
$this.addClass("ui-bar-" + thisTheme);
// Add ARIA role
$this.attr("role", role === "header" ? "banner" : "contentinfo");
// Right,left buttons
$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 &&
$this.jqmData("url") !== $.mobile.path.stripHash(location.hash) &&
!leftbtn) {
backBtn = $("
" + o.backBtnText + "").prependTo($this);
// If theme is provided, override default inheritance
backBtn.attr("data-" + $.mobile.ns + "theme", o.backBtnTheme || thisTheme);
}
// 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 || o.contentTheme) {
$this.addClass("ui-body-" + ( theme || o.contentTheme ));
}
// Add ARIA role
$this.attr("role", "main");
}
});
});
})(jQuery);
/*
* jQuery Mobile Framework : "collapsible" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.widget("mobile.collapsible", $.mobile.widget, {
options: {
expandCueText: " click to expand contents",
collapseCueText: " click to collapse contents",
collapsed: true,
heading: ">:header,>legend",
theme: null,
iconTheme: "d",
initSelector: ":jqmData(role='collapsible')"
},
_create: function() {
var $el = this.element,
o = this.options,
collapsibleContain = $el.addClass("ui-collapsible-contain"),
collapsibleHeading = $el.find(o.heading).eq(0),
collapsibleContent = collapsibleContain.wrapInner("
").find(".ui-collapsible-content"),
collapsibleParent = $el.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set");
// Replace collapsibleHeading if it's a legend
if (collapsibleHeading.is("legend")) {
collapsibleHeading = $("
" + collapsibleHeading.html() + "
").insertBefore(collapsibleHeading);
collapsibleHeading.next().remove();
}
collapsibleHeading
//drop heading in before content
.insertBefore(collapsibleContent)
//modify markup & attributes
.addClass("ui-collapsible-heading")
.append("
")
.wrapInner("
")
.find("a:eq(0)")
.buttonMarkup({
shadow: !collapsibleParent.length,
corners: false,
iconPos: "left",
icon: "plus",
theme: o.theme
})
.find(".ui-icon")
.removeAttr("class")
.buttonMarkup({
shadow: true,
corners: true,
iconPos: "notext",
icon: "plus",
theme: o.iconTheme
});
if (!collapsibleParent.length) {
collapsibleHeading
.find("a:eq(0)")
.addClass("ui-corner-all")
.find(".ui-btn-inner")
.addClass("ui-corner-all");
} else {
if (collapsibleContain.jqmData("collapsible-last")) {
collapsibleHeading
.find("a:eq(0), .ui-btn-inner")
.addClass("ui-corner-bottom");
}
}
//events
collapsibleContain
.bind("collapse", function(event) {
if (! event.isDefaultPrevented() &&
$(event.target).closest(".ui-collapsible-contain").is(collapsibleContain)) {
event.preventDefault();
collapsibleHeading
.addClass("ui-collapsible-heading-collapsed")
.find(".ui-collapsible-heading-status")
.text(o.expandCueText)
.end()
.find(".ui-icon")
.removeClass("ui-icon-minus")
.addClass("ui-icon-plus");
collapsibleContent.addClass("ui-collapsible-content-collapsed").attr("aria-hidden", true);
if (collapsibleContain.jqmData("collapsible-last")) {
collapsibleHeading
.find("a:eq(0), .ui-btn-inner")
.addClass("ui-corner-bottom");
}
}
})
.bind("expand", function(event) {
if (!event.isDefaultPrevented()) {
event.preventDefault();
collapsibleHeading
.removeClass("ui-collapsible-heading-collapsed")
.find(".ui-collapsible-heading-status").text(o.collapseCueText);
collapsibleHeading.find(".ui-icon").removeClass("ui-icon-plus").addClass("ui-icon-minus");
collapsibleContent.removeClass("ui-collapsible-content-collapsed").attr("aria-hidden", false);
if (collapsibleContain.jqmData("collapsible-last")) {
collapsibleHeading
.find("a:eq(0), .ui-btn-inner")
.removeClass("ui-corner-bottom");
}
}
})
.trigger(o.collapsed ? "collapse" : "expand");
// Close others in a set
if (collapsibleParent.length && !collapsibleParent.jqmData("collapsiblebound")) {
collapsibleParent
.jqmData("collapsiblebound", true)
.bind("expand", function(event) {
$(event.target)
.closest(".ui-collapsible-contain")
.siblings(".ui-collapsible-contain")
.trigger("collapse");
});
var set = collapsibleParent.children(":jqmData(role='collapsible')");
set.first()
.find("a:eq(0)")
.addClass("ui-corner-top")
.find(".ui-btn-inner")
.addClass("ui-corner-top");
set.last().jqmData("collapsible-last", true);
}
collapsibleHeading
.bind("vclick", function(event) {
var type = collapsibleHeading.is(".ui-collapsible-heading-collapsed") ?
"expand" : "collapse";
collapsibleContain.trigger(type);
event.preventDefault();
});
}
});
//auto self-init widgets
$(document).bind("pagecreate create", function(e) {
$($.mobile.collapsible.prototype.options.initSelector, e.target).collapsible();
});
})(jQuery);
/*
* jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.fn.fieldcontain = function(options) {
return this.addClass("ui-field-contain ui-body ui-br");
};
//auto self-init widgets
$(document).bind("pagecreate create", function(e) {
$(":jqmData(role='fieldcontain')", e.target).fieldcontain();
});
})(jQuery);
/*
* jQuery Mobile Framework : plugin for creating CSS grids
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.fn.grid = function(options) {
return this.each(function() {
var $this = $(this),
o = $.extend({
grid: null
}, options),
$kids = $this.children(),
gridCols = {solo:1, a:2, b:3, c:4, d:5},
grid = o.grid,
iterator;
if (!grid) {
if ($kids.length <= 5) {
for (var letter in gridCols) {
if (gridCols[ letter ] === $kids.length) {
grid = letter;
}
}
} else {
grid = "a";
}
}
iterator = gridCols[grid];
$this.addClass("ui-grid-" + grid);
$kids.filter(":nth-child(" + iterator + "n+1)").addClass("ui-block-a");
if (iterator > 1) {
$kids.filter(":nth-child(" + iterator + "n+2)").addClass("ui-block-b");
}
if (iterator > 2) {
$kids.filter(":nth-child(3n+3)").addClass("ui-block-c");
}
if (iterator > 3) {
$kids.filter(":nth-child(4n+4)").addClass("ui-block-d");
}
if (iterator > 4) {
$kids.filter(":nth-child(5n+5)").addClass("ui-block-e");
}
});
};
})(jQuery);
/*
* jQuery Mobile Framework : "navbar" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.widget("mobile.navbar", $.mobile.widget, {
options: {
iconpos: "top",
grid: null,
initSelector: ":jqmData(role='navbar')"
},
_create: function() {
var $navbar = this.element,
$navbtns = $navbar.find("a"),
iconpos = $navbtns.filter(":jqmData(icon)").length ?
this.options.iconpos : undefined;
$navbar.addClass("ui-navbar")
.attr("role", "navigation")
.find("ul")
.grid({ grid: this.options.grid });
if (!iconpos) {
$navbar.addClass("ui-navbar-noicons");
}
$navbtns.buttonMarkup({
corners: false,
shadow: false,
iconpos: iconpos
});
$navbar.delegate("a", "vclick", function(event) {
$navbtns.not(".ui-state-persist").removeClass($.mobile.activeBtnClass);
$(this).addClass($.mobile.activeBtnClass);
});
}
});
//auto self-init widgets
$(document).bind("pagecreate create", function(e) {
$($.mobile.navbar.prototype.options.initSelector, e.target).navbar();
});
})(jQuery);
/*
* jQuery Mobile Framework : "listview" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
//Keeps track of the number of lists per page UID
//This allows support for multiple nested list in the same page
//https://github.com/jquery/jquery-mobile/issues/1617
var listCountPerPage = {};
$.widget("mobile.listview", $.mobile.widget, {
options: {
theme: "c",
countTheme: "c",
headerTheme: "b",
dividerTheme: "b",
splitIcon: "arrow-r",
splitTheme: "b",
inset: false,
initSelector: ":jqmData(role='listview')"
},
_create: function() {
var t = this;
// create listview markup
t.element.addClass(function(i, orig) {
return orig + " ui-listview " + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" );
});
t.refresh(true);
},
_itemApply: function($list, item) {
var $countli = item.find(".ui-li-count");
if ($countli.length) {
item.addClass("ui-li-has-count");
}
$countli.addClass("ui-btn-up-" + ( $list.jqmData("counttheme") || this.options.countTheme ) + " ui-btn-corner-all");
// TODO class has to be defined in markup
item.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end()
.find("p, dl").addClass("ui-li-desc").end()
.find(">img:eq(0), .ui-link-inherit>img:eq(0)").addClass("ui-li-thumb").each(
function() {
item.addClass($(this).is(".ui-li-icon") ? "ui-li-has-icon" : "ui-li-has-thumb");
}).end()
.find(".ui-li-aside").each(function() {
var $this = $(this);
$this.prependTo($this.parent()); //shift aside to front for css float
});
},
_removeCorners: function(li, which) {
var top = "ui-corner-top ui-corner-tr ui-corner-tl",
bot = "ui-corner-bottom ui-corner-br ui-corner-bl";
li = li.add(li.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb"));
if (which === "top") {
li.removeClass(top);
} else if (which === "bottom") {
li.removeClass(bot);
} else {
li.removeClass(top + " " + bot);
}
},
_refreshCorners: function(create) {
var $li,
$visibleli,
$topli,
$bottomli;
if (this.options.inset) {
$li = this.element.children("li");
// at create time the li are not visible yet so we need to rely on .ui-screen-hidden
$visibleli = create ? $li.not(".ui-screen-hidden") : $li.filter(":visible");
this._removeCorners($li);
// Select the first visible li element
$topli = $visibleli.first()
.addClass("ui-corner-top");
$topli.add($topli.find(".ui-btn-inner"))
.find(".ui-li-link-alt")
.addClass("ui-corner-tr")
.end()
.find(".ui-li-thumb")
.addClass("ui-corner-tl");
// Select the last visible li element
$bottomli = $visibleli.last()
.addClass("ui-corner-bottom");
$bottomli.add($bottomli.find(".ui-btn-inner"))
.find(".ui-li-link-alt")
.addClass("ui-corner-br")
.end()
.find(".ui-li-thumb")
.addClass("ui-corner-bl");
}
},
refresh: function(create) {
this.parentPage = this.element.closest(".ui-page");
this._createSubPages();
var o = this.options,
$list = this.element,
self = this,
dividertheme = $list.jqmData("dividertheme") || o.dividerTheme,
listsplittheme = $list.jqmData("splittheme"),
listspliticon = $list.jqmData("spliticon"),
li = $list.children("li"),
counter = $.support.cssPseudoElement || !$.nodeName($list[ 0 ], "ol") ? 0 : 1,
item, itemClass, itemTheme,
a, last, splittheme, countParent, icon;
if (counter) {
$list.find(".ui-li-dec").remove();
}
for (var pos = 0, numli = li.length; pos < numli; pos++) {
item = li.eq(pos);
itemClass = "ui-li";
// If we're creating the element, we update it regardless
if (create || !item.hasClass("ui-li")) {
itemTheme = item.jqmData("theme") || o.theme;
a = item.children("a");
if (a.length) {
icon = item.jqmData("icon");
item.buttonMarkup({
wrapperEls: "div",
shadow: false,
corners: false,
iconpos: "right",
icon: a.length > 1 || icon === false ? false : icon || "arrow-r",
theme: itemTheme
});
if (( icon != false ) && ( a.length == 1 )) {
item.addClass("ui-li-has-arrow");
}
a.first().addClass("ui-link-inherit");
if (a.length > 1) {
itemClass += " ui-li-has-alt";
last = a.last();
splittheme = listsplittheme || last.jqmData("theme") || o.splitTheme;
last.appendTo(item)
.attr("title", last.text())
.addClass("ui-li-link-alt")
.empty()
.buttonMarkup({
shadow: false,
corners: false,
theme: itemTheme,
icon: false,
iconpos: false
})
.find(".ui-btn-inner")
.append(
$("
").buttonMarkup({
shadow: true,
corners: true,
theme: splittheme,
iconpos: "notext",
icon: listspliticon || last.jqmData("icon") || o.splitIcon
})
);
}
} else if (item.jqmData("role") === "list-divider") {
itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
item.attr("role", "heading");
//reset counter when a divider heading is encountered
if (counter) {
counter = 1;
}
} else {
itemClass += " ui-li-static ui-body-" + itemTheme;
}
}
if (counter && itemClass.indexOf("ui-li-divider") < 0) {
countParent = item.is(".ui-li-static:first") ? item : item.find(".ui-link-inherit");
countParent.addClass("ui-li-jsnumbering")
.prepend("
" + (counter++) + ". ");
}
item.add(item.children(".ui-btn-inner")).addClass(itemClass);
self._itemApply($list, item);
}
this._refreshCorners(create);
},
//create a string for ID/subpage url creation
_idStringEscape: function(str) {
return str.replace(/[^a-zA-Z0-9]/g, '-');
},
_createSubPages: function() {
var parentList = this.element,
parentPage = parentList.closest(".ui-page"),
parentUrl = parentPage.jqmData("url"),
parentId = parentUrl || parentPage[ 0 ][ $.expando ],
parentListId = parentList.attr("id"),
o = this.options,
dns = "data-" + $.mobile.ns,
self = this,
persistentFooterID = parentPage.find(":jqmData(role='footer')").jqmData("id"),
hasSubPages;
if (typeof listCountPerPage[ parentId ] === "undefined") {
listCountPerPage[ parentId ] = -1;
}
parentListId = parentListId || ++listCountPerPage[ parentId ];
$(parentList.find("li>ul, li>ol").toArray().reverse()).each(
function(i) {
var self = this,
list = $(this),
listId = list.attr("id") || parentListId + "-" + i,
parent = list.parent(),
nodeEls = $(list.prevAll().toArray().reverse()),
nodeEls = nodeEls.length ? nodeEls : $("
" + $.trim(parent.contents()[ 0 ].nodeValue) + ""),
title = nodeEls.first().text(),//url limits to first 30 chars of text
id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
theme = list.jqmData("theme") || o.theme,
countTheme = list.jqmData("counttheme") || parentList.jqmData("counttheme") || o.countTheme,
newPage, anchor;
//define hasSubPages for use in later removal
hasSubPages = true;
newPage = list.detach()
.wrap("
")
.parent()
.before("
")
.after(persistentFooterID ? $("
") : "")
.parent()
.appendTo($.mobile.pageContainer);
newPage.page();
anchor = parent.find('a:first');
if (!anchor.length) {
anchor = $("
").html(nodeEls || title).prependTo(parent.empty());
}
anchor.attr("href", "#" + id);
}).listview();
//on pagehide, remove any nested pages along with the parent page, as long as they aren't active
if (hasSubPages && parentPage.data("page").options.domCache === false) {
var newRemove = function(e, ui) {
var nextPage = ui.nextPage, npURL;
if (ui.nextPage) {
npURL = nextPage.jqmData("url");
if (npURL.indexOf(parentUrl + "&" + $.mobile.subPageUrlKey) !== 0) {
self.childPages().remove();
parentPage.remove();
}
}
};
// unbind the original page remove and replace with our specialized version
parentPage
.unbind("pagehide.remove")
.bind("pagehide.remove", newRemove);
}
},
// TODO sort out a better way to track sub pages of the listview this is brittle
childPages: function() {
var parentUrl = this.parentPage.jqmData("url");
return $(":jqmData(url^='" + parentUrl + "&" + $.mobile.subPageUrlKey + "')");
}
});
//auto self-init widgets
$(document).bind("pagecreate create", function(e) {
$($.mobile.listview.prototype.options.initSelector, e.target).listview();
});
})(jQuery);
/*
* jQuery Mobile Framework : "listview" filter extension
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.mobile.listview.prototype.options.filter = false;
$.mobile.listview.prototype.options.filterPlaceholder = "Filter items...";
$.mobile.listview.prototype.options.filterTheme = "c";
$.mobile.listview.prototype.options.filterCallback = function(text, searchValue) {
return text.toLowerCase().indexOf(searchValue) === -1;
};
$(":jqmData(role='listview')").live("listviewcreate", function() {
var list = $(this),
listview = list.data("listview");
if (!listview.options.filter) {
return;
}
var wrapper = $("