2 * jQuery Mobile Widget @VERSION
4 * This software is licensed under the MIT licence (as defined by the OSI at
5 * http://www.opensource.org/licenses/mit-license.php)
7 * ***************************************************************************
8 * Copyright (C) 2011 by Intel Corporation Ltd.
10 * Permission is hereby granted, free of charge, to any person obtaining a
11 * copy of this software and associated documentation files (the "Software"),
12 * to deal in the Software without restriction, including without limitation
13 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 * and/or sell copies of the Software, and to permit persons to whom the
15 * Software is furnished to do so, subject to the following conditions:
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 * DEALINGS IN THE SOFTWARE.
27 * ***************************************************************************
29 * Authors: Gabriel Schulhof <gabriel.schulhof@intel.com>,
30 * Elliot Smith <elliot.smith@intel.com>
33 // Shows other elements inside a popup window.
35 // To apply, add the attribute data-role="popupwindow" to a <div> element inside
36 // a page. Alternatively, call popupwindow()
37 // on an element, eg :
39 // $("#mypopupwindowContent").popupwindow();
40 // where the html might be :
41 // <div id="mypopupwindowContent"></div>
43 // To trigger the popupwindow to appear, it is necessary to make a call to its
44 // 'open()' method. This is typically done by binding a function to an event
45 // emitted by an input element, such as a the clicked event emitted by a button
46 // element. The open() method takes two arguments, specifying the x and y
47 // screen coordinates of the center of the popup window.
49 // You can associate a button with a popup window like this:
50 // <div id="mypopupContent" style="display: table;" data-role="popupwindow">
52 // <tr> <td>Eenie</td> <td>Meenie</td> <td>Mynie</td> <td>Mo</td> </tr>
53 // <tr> <td>Catch-a</td> <td>Tiger</td> <td>By-the</td> <td>Toe</td> </tr>
54 // <tr> <td>If-he</td> <td>Hollers</td> <td>Let-him</td> <td>Go</td> </tr>
55 // <tr> <td>Eenie</td> <td>Meenie</td> <td>Mynie</td> <td>Mo</td> </tr>
58 // <a href="#myPopupContent" data-rel="popupwindow" data-role="button">Show popup</a>
62 // theme: String; the theme for the popupwindow contents
65 // overlayTheme: String; the theme for the popupwindow
68 // shadow: Boolean; display a shadow around the popupwindow
71 // corners: Boolean; display a shadow around the popupwindow
74 // fade: Boolean; fades the opening and closing of the popupwindow
76 // transition: String; the transition to use when opening or closing
78 // Default: $.mobile.defaultDialogTransition
81 // close: Emitted when the popupwindow is closed.
83 (function( $, undefined ) {
85 $.widget( "tizen.popupwindow", $.tizen.widgetex, {
95 transition: $.mobile.defaultDialogTransition,
96 initSelector: ":jqmData(role='popupwindow')"
101 screen: "#popupwindow-screen",
102 container: "#popupwindow-container"
106 _create: function() {
107 var thisPage = this.element.closest(":jqmData(role='page')"),
110 if (thisPage.length === 0)
111 thisPage = $("body");
113 // Drop a placeholder into the location from which we shall rip out the popup window contents
114 this._ui.placeholder =
115 $("<div><!-- placeholder" +
116 (this.element.attr("id") === undefined
118 : " for " + this.element.attr("id")) + " --></div>")
119 .css("display", "none")
120 .insertBefore(this.element);
123 thisPage.append(this._ui.screen);
124 this._ui.container.insertAfter(this._ui.screen);
125 this._ui.container.append(this.element);
127 // Define instance variables
133 var popup = this.element;
134 var o = this.options;
135 var style = popup.attr( 'data-style' );
136 o.style = style ? style : o.style;
138 popup.addClass( o.style );
139 popup.find( ":jqmData(role='title')" )
140 .wrapAll( "<div class='popup-title'></div>" );
141 popup.find( ":jqmData(role='text')" )
142 .wrapAll( "<div class='popup-text'></div>" );
143 popup.find( ":jqmData(role='button-bg')" )
144 .wrapAll( "<div class='popup-button-bg'></div>" );
145 popup.find( ":jqmData(role='check-bg')" )
146 .wrapAll( "<div class='popup-check-bg'></div>" );
147 popup.find( ":jqmData(role='scroller-bg')" )
148 .wrapAll( "<div class='popup-scroller-bg'></div>" );
149 popup.find( ":jqmData(role='text-bottom-bg')" )
150 .wrapAll( "<div class='popup-text-bottom-bg'></div>" );
151 popup.find( ":jqmData(role='text-left')" )
152 .wrapAll( "<div class='popup-text-left'></div>" );
153 popup.find( ":jqmData(role='text-right')" )
154 .wrapAll( "<div class='popup-text-right'></div>" );
155 popup.find( ":jqmData(role='progress-bg')" )
156 .wrapAll( "<div class='popup-progress-bg'></div>" );
160 // Events on "screen" overlay
161 this._ui.screen.bind( "vclick", function( event ) {
166 _realSetTheme: function(dst, theme) {
167 var classes = (dst.attr("class") || "").split(" "),
172 while (classes.length > 0) {
173 currentTheme = classes.pop();
174 matches = currentTheme.match(/^ui-body-([a-z])$/);
175 if (matches && matches.length > 1) {
176 currentTheme = matches[1];
183 dst.removeClass("ui-body-" + currentTheme);
184 if ((theme || "").match(/[a-z]/))
185 dst.addClass("ui-body-" + theme);
188 _setTheme: function(value) {
189 this._realSetTheme(this.element, value);
190 this.options.theme = value;
191 this.element.attr("data-" + ($.mobile.ns || "") + "theme", value);
194 _setOverlayTheme: function(value) {
195 this._realSetTheme(this._ui.container, value);
196 // The screen must always have some kind of background for fade to work, so, if the theme is being unset,
197 // set the background to "a".
198 this._realSetTheme(this._ui.screen, (value === "" ? "a" : value));
199 this.options.overlayTheme = value;
200 this.element.attr("data-" + ($.mobile.ns || "") + "overlay-theme", value);
203 _setShadow: function(value) {
204 this.options.shadow = value;
205 this.element.attr("data-" + ($.mobile.ns || "") + "shadow", value);
206 this._ui.container[value ? "addClass" : "removeClass"]("ui-overlay-shadow");
209 _setCorners: function(value) {
210 this.options.corners = value;
211 this.element.attr("data-" + ($.mobile.ns || "") + "corners", value);
212 this._ui.container[value ? "addClass" : "removeClass"]("ui-corner-all");
215 _setFade: function(value) {
216 this.options.fade = value;
217 this.element.attr("data-" + ($.mobile.ns || "") + "fade", value);
220 _setTransition: function(value) {
222 .removeClass((this.options.transition || ""))
224 this.options.transition = value;
225 this.element.attr("data-" + ($.mobile.ns || "") + "transition", value);
228 _setDisabled: function(value) {
229 $.Widget.prototype._setOption.call(this, "disabled", value);
234 _placementCoords: function(x, y, cx, cy) {
235 // Try and center the overlay over the given coordinates
237 scrollTop = $(window).scrollTop(),
238 screenHeight = $(window).height(),
239 screenWidth = $(window).width(),
241 maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
242 roomtop = y - scrollTop,
243 roombot = scrollTop + screenHeight - y,
246 if ( roomtop > cy / 2 && roombot > cy / 2 ) {
247 newtop = y - halfheight;
250 // 30px tolerance off the edges
251 newtop = roomtop > roombot ? scrollTop + screenHeight - cy - 30 : scrollTop + 30;
254 // If the menuwidth is smaller than the screen center is
255 if ( cx < maxwidth ) {
256 newleft = ( screenWidth - cx ) / 2;
259 //otherwise insure a >= 30px offset from the left
260 newleft = x - cx / 2;
262 // 10px tolerance off the edges
263 if ( newleft < 10 ) {
267 if ( ( newleft + cx ) > screenWidth ) {
268 newleft = screenWidth - cx - 10;
272 return { x : newleft, y : newtop };
275 destroy: function() {
276 // Put the element back where we ripped it out from
277 this.element.insertBefore(this._ui.placeholder);
280 this._ui.placeholder.remove();
281 this._ui.container.remove();
282 this._ui.screen.remove();
283 this.element.triggerHandler("destroyed");
284 $.Widget.prototype.destroy.call(this);
287 open: function(x_where, y_where) {
288 if (!(this._isOpen || this.options.disabled)) {
290 x = (undefined === x_where ? window.innerWidth / 2 : x_where),
291 y = (undefined === y_where ? window.innerHeight / 2 : y_where),
295 var ctxpopup = this.element.data("ctxpopup");
297 var popupWidth = window.innerWidth * this.options.widthRatio;
298 this._ui.container.css("width", popupWidth);
299 // If the width of the popup exceeds the width of the window, we need to limit the width here,
300 // otherwise outer{Width,Height}(true) below will happily report the unrestricted values, causing
301 // the popup to get placed wrong.
302 if (this._ui.container.outerWidth(true) > $(window).width())
303 this._ui.container.css({"max-width" : $(window).width() - 30});
306 coords = this._placementCoords(x, y,
307 this._ui.container.outerWidth(true),
308 this._ui.container.outerHeight(true));
314 zIndex = parseInt(el.css("z-index"));
316 if (!(el.is(self._ui.container) || el.is(self._ui.screen) || isNaN(zIndex)))
317 zIndexMax = Math.max(zIndexMax, zIndex);
321 .height($(document).height())
322 .removeClass("ui-screen-hidden");
324 if (this.options.fade)
325 this._ui.screen.animate({opacity: 0.5}, "fast");
327 this._ui.screen.css({opacity: 0.0});
329 //Recalculate popup position
330 var menuHeight = this._ui.container.innerHeight(true),
331 menuWidth = this._ui.container.innerWidth(true),
332 scrollTop = $( window ).scrollTop(),
333 screenHeight = window.innerHeight,
334 screenWidth = window.innerWidth;
336 var roomtop = y - scrollTop,
337 roombot = scrollTop + screenHeight - y,
338 halfheight = menuHeight / 2,
339 maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
342 newtop = (screenHeight - menuHeight) / 2 + scrollTop;
344 if ( menuWidth < maxwidth ) {
345 newleft = ( screenWidth - menuWidth ) / 2;
348 //otherwise insure a >= 30px offset from the left
349 newleft = x - menuWidth / 2;
351 // 30px tolerance off the edges
352 if ( newleft < 30 ) {
355 else if ( ( newleft + menuWidth ) > screenWidth ) {
356 newleft = screenWidth - menuWidth - 30;
359 //Recalculate popup position End
366 .removeClass("ui-selectmenu-hidden")
372 .animationComplete(function() {
373 self._ui.screen.height($(document).height());
383 hideScreen = function() {
384 self._ui.screen.addClass("ui-screen-hidden");
385 self._isOpen = false;
386 self.element.trigger("closed");
391 .addClass("reverse out")
392 .animationComplete(function() {
394 .removeClass("reverse out")
395 .addClass("ui-selectmenu-hidden")
396 .removeAttr("style");
399 if (this.options.fade)
400 this._ui.screen.animate({opacity: 0.0}, "fast", hideScreen);
407 $.tizen.popupwindow.bindPopupToButton = function(btn, popup) {
408 if (btn.length === 0 || popup.length === 0) return;
410 var btnVClickHandler = function(e) {
411 // When /this/ button causes a popup, align the popup's theme with that of the button, unless the popup has a theme pre-set
412 if (!popup.jqmData("overlay-theme-set"))
413 popup.popupwindow("option", "overlayTheme", btn.jqmData("theme"));
414 popup.popupwindow("open",
415 btn.offset().left + btn.outerWidth() / 2,
416 btn.offset().top + btn.outerHeight() / 2);
418 // Swallow event, because it might end up getting picked up by the popup window's screen handler, which
419 // will in turn cause the popup window to close - Thanks Sasha!
420 if (e.stopPropagation)
422 if (e.preventDefault)
426 // If the popup has a theme set, prevent it from being clobbered by the associated button
427 if ((popup.popupwindow("option", "overlayTheme") || "").match(/[a-z]/))
428 popup.jqmData("overlay-theme-set", true);
432 "aria-haspopup": true,
433 "aria-owns": btn.attr("href")
436 .bind("vclick", btnVClickHandler);
438 popup.bind("destroyed", function() {
439 btn.unbind("vclick", btnVClickHandler);
443 $(document).bind("pagecreate create", function(e) {
444 $($.tizen.popupwindow.prototype.options.initSelector, e.target)
445 .not(":jqmData(role='none'), :jqmData(role='nojs')")
448 $("a[href^='#']:jqmData(rel='popupwindow')", e.target).each(function() {
449 $.tizen.popupwindow.bindPopupToButton($(this), $($(this).attr("href")));