jquery-mobile/js/jquery.mobile.popup.js
2011-12-05 22:31:42 +07:00

281 lines
8 KiB
JavaScript

/*
=* jQuery Mobile Framework : "popup" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined) {
$.widget("mobile.popup", $.mobile.widget, {
options: {
theme: null,
overlayTheme: null,
shadow: true,
corners: true,
fade: true,
transition: $.mobile.defaultDialogTransition,
initSelector: ":jqmData(role='popup')"
},
_create: function() {
var widgetOptionNames = {
"overlayTheme" : "data-" + ($.mobile.ns || "") + "overlay-theme",
"shadow" : "data-" + ($.mobile.ns || "") + "shadow",
"corners" : "data-" + ($.mobile.ns || "") + "corners",
"fade" : "data-" + ($.mobile.ns || "") + "fade",
"transition" : "data-" + ($.mobile.ns || "") + "transition",
"theme" : "data-" + ($.mobile.ns || "") + "theme"
},
ui = {
screen : "#ui-popup-screen",
container : "#ui-popup-container"
},
proto = $(
"<div>" +
" <div id='ui-popup-screen' class='ui-selectmenu-screen ui-screen-hidden ui-popup-screen'></div>" +
" <div id='ui-popup-container' class='ui-popup-container ui-selectmenu-hidden'></div>" +
"</div>"
),
thisPage = (this.element.closest(".ui-page") || $("body")),
self = this;
// Assign the relevant parts of the proto
for (var key in ui)
ui[key] = proto.find(ui[key]).removeAttr("id");
// Apply the proto
thisPage.append(ui.screen);
ui.container.insertAfter(ui.screen);
ui.container.append(this.element);
// Define instance variables
$.extend (this, {
_ui : ui,
_isOpen : false
});
// Apply options - data-* options, if present, take precedence over this.options.*
for (var key in this.options)
this._setOption(key,
(widgetOptionNames[key] === undefined || this.element.attr(widgetOptionNames[key]) === undefined)
? this.options[key]
: this.element.attr(widgetOptionNames[key]), true);
ui.screen.bind("vclick", function(e) {
self.close();
});
},
_setTheme: function(dst, theme, unconditional) {
var currentTheme = (dst.attr("class") || "")
.split(" ")
.filter(function(el, idx, ar) {
return el.match(/^ui-body-[a-z]$/);
});
currentTheme = ((currentTheme.length > 0) ? currentTheme[0].match(/^ui-body-([a-z])/)[1] : null);
if (theme !== currentTheme || unconditional) {
dst.removeClass("ui-body-" + currentTheme);
if (theme !== null)
dst.addClass("ui-body-" + theme);
}
},
_set_theme: function(value, unconditional) {
if (value === null)
value = "";
if (value.match(/^[a-z]$/) || value === "") {
this._setTheme(this.element, value, unconditional);
}
},
_set_overlayTheme: function(value, unconditional) {
if (value === null)
value = "";
if (value.match(/^[a-z]$/) || value === "") {
this._setTheme(this._ui.container, value, unconditional);
// The screen must always have some kind of background for fade to work, so, if the theme is being unset,
// set the background to "a".
this._setTheme(this._ui.screen, (value === "" ? "a" : value), unconditional);
}
else
console.log("Warning: " + value + " is not a valid overlay theme! Please specify a single letter a-z or an empty string!");
},
_set_shadow: function(value, unconditional) {
value = this._parseBoolean(value);
if (this._ui.container.hasClass("ui-overlay-shadow") != value || unconditional)
this._ui.container[value ? "addClass" : "removeClass"]("ui-overlay-shadow");
},
_set_corners: function(value, unconditional) {
value = this._parseBoolean(value);
if (this._ui.container.hasClass("ui-corner-all") != value || unconditional)
this._ui.container[value ? "addClass" : "removeClass"]("ui-corner-all");
},
_parseBoolean: function(value) {
if (typeof value === "boolean")
return value;
else {
value = value.toLowerCase();
return (value === "true" || value === "yes" || value === "1");
}
},
_set_fade: function(value, unconditional) {
this.options.fade = this._parseBoolean(value);
},
_set_transition: function(value, unconditional) {
if (this.options.transition != value || unconditional) {
this._ui.container
.removeClass(this.options.transition)
.addClass(value);
this.options.transition = value;
}
},
_setOption: function(key, value, unconditional) {
if (unconditional === undefined)
unconditional = false;
if (this["_set_" + key] !== undefined)
this["_set_" + key](value, unconditional);
},
_placementCoords: function(x, y) {
// Try and center the overlay over the given coordinates
var ret,
menuHeight = this._ui.container.outerHeight(true),
menuWidth = this._ui.container.outerWidth(true),
scrollTop = $( window ).scrollTop(),
screenHeight = window.innerHeight,
screenWidth = window.innerWidth,
halfheight = menuHeight / 2,
maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
roomtop = y - scrollTop,
roombot = scrollTop + screenHeight - y,
newtop, newleft;
if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
newtop = y - halfheight;
}
else {
// 30px tolerance off the edges
newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
}
// If the menuwidth is smaller than the screen center is
if ( menuWidth < maxwidth ) {
newleft = ( screenWidth - menuWidth ) / 2;
}
else {
//otherwise insure a >= 30px offset from the left
newleft = x - menuWidth / 2;
// 10px tolerance off the edges
if ( newleft < 10 ) {
newleft = 10;
}
else
if ( ( newleft + menuWidth ) > screenWidth ) {
newleft = screenWidth - menuWidth - 10;
}
}
return { x : newleft, y : newtop };
},
open: function(x, y) {
if (!this._isOpen) {
var self = this,
coords = this._placementCoords(
(undefined === x ? window.innerWidth / 2 : x),
(undefined === y ? window.innerWidth / 2 : y));
this._ui.screen
.height($(document).height())
.removeClass("ui-screen-hidden");
if (this.options.fade)
this._ui.screen.animate({opacity: 0.5}, "fast");
this._ui.container
.removeClass("ui-selectmenu-hidden")
.css({
left: coords.x,
top: coords.y
})
.addClass("in")
.animationComplete(function() {
self._ui.screen.height($(document).height());
});
this._isOpen = true;
}
},
close: function() {
if (this._isOpen) {
var self = this,
hideScreen = function() {
self._ui.screen.addClass("ui-screen-hidden");
self._isOpen = false;
self.element.trigger("closed");
};
this._ui.container
.removeClass("in")
.addClass("reverse out")
.animationComplete(function() {
self._ui.container
.removeClass("reverse out")
.addClass("ui-selectmenu-hidden")
.removeAttr("style");
});
if (this.options.fade)
this._ui.screen.animate({opacity: 0.0}, "fast", hideScreen);
else
hideScreen();
}
}
});
$(document).bind("pagecreate create", function(e) {
$($.mobile.popup.prototype.options.initSelector, e.target)
.not(":jqmData(role='none'), :jqmData(role='nojs')")
.popup();
$("a[href^='#']:jqmData(rel='popup')").each(function() {
var btn = $(this),
popup = $(btn.attr("href"));
if (popup[0]) {
// If the popup has a theme set, prevent it from being clobbered by the associated button
if ((popup.popup("option", "overlayTheme") || "").match(/[a-z]/))
popup.jqmData("overlay-theme-set", true);
btn
.attr({
"aria-haspopup": true,
"aria-owns": btn.attr("href")
})
.removeAttr("href")
.bind("vclick", function() {
// When /this/ button causes a popup, align the popup's theme with that of the button, unless the popup has a theme pre-set
if (!popup.jqmData("overlay-theme-set"))
popup.popup("option", "overlayTheme", btn.jqmData("theme"))
popup.popup("open",
btn.offset().left + btn.outerWidth() / 2,
btn.offset().top + btn.outerHeight() / 2);
});
}
else
console.log("popup menu " + btn.attr("href") + " not found.");
});
});
})(jQuery);