6df99ed7535546ebb068f69924840dd768bc95bd
[framework/web/web-ui-fw.git] / src / widgets / popupwindow / js / jquery.mobile.tizen.popupwindow.js
1 /*
2  * jQuery Mobile Widget @VERSION
3  *
4  * This software is licensed under the MIT licence (as defined by the OSI at
5  * http://www.opensource.org/licenses/mit-license.php)
6  *
7  * ***************************************************************************
8  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
9  * Copyright (c) 2011 by Intel Corporation Ltd.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  * ***************************************************************************
29  *
30  * Authors: Gabriel Schulhof <gabriel.schulhof@intel.com>,
31  *          Elliot Smith <elliot.smith@intel.com>
32  */
33
34 // Shows other elements inside a popup window.
35 //
36 // To apply, add the attribute data-role="popupwindow" to a <div> element inside
37 // a page. Alternatively, call popupwindow()
38 // on an element, eg :
39 //
40 //     $("#mypopupwindowContent").popupwindow();
41 // where the html might be :
42 //     <div id="mypopupwindowContent"></div>
43 //
44 // To trigger the popupwindow to appear, it is necessary to make a call to its
45 // 'open()' method. This is typically done by binding a function to an event
46 // emitted by an input element, such as a the clicked event emitted by a button
47 // element. The open() method takes two arguments, specifying the x and y
48 // screen coordinates of the center of the popup window.
49
50 // You can associate a button with a popup window like this:
51 //      <div id="mypopupContent" style="display: table;" data-role="popupwindow">
52 //          <table>
53 //              <tr> <td>Eenie</td>   <td>Meenie</td>  <td>Mynie</td>   <td>Mo</td>  </tr>
54 //              <tr> <td>Catch-a</td> <td>Tiger</td>   <td>By-the</td>  <td>Toe</td> </tr>
55 //              <tr> <td>If-he</td>   <td>Hollers</td> <td>Let-him</td> <td>Go</td>  </tr>
56 //              <tr> <td>Eenie</td>   <td>Meenie</td>  <td>Mynie</td>   <td>Mo</td>  </tr>
57 //          </table>
58 //      </div>
59 // <a href="#myPopupContent" data-rel="popupwindow" data-role="button">Show popup</a>
60 //
61 // Options:
62 //
63 //     theme: String; the theme for the popupwindow contents
64 //                   Default: null
65 //
66 //     overlayTheme: String; the theme for the popupwindow
67 //                   Default: null
68 //
69 //     shadow: Boolean; display a shadow around the popupwindow
70 //             Default: true
71 //
72 //     corners: Boolean; display a shadow around the popupwindow
73 //             Default: true
74 //
75 //     fade: Boolean; fades the opening and closing of the popupwindow
76 //
77 //     transition: String; the transition to use when opening or closing
78 //                 a popupwindow
79 //                 Default: $.mobile.defaultDialogTransition
80 //
81 // Events:
82 //     close: Emitted when the popupwindow is closed.
83
84 (function ( $, undefined ) {
85
86         $.widget( "tizen.popupwindow", $.tizen.widgetex, {
87                 options: {
88                         theme: null,
89                         overlayTheme: "s",
90                         style: "custom",
91                         disabled: false,
92                         shadow: true,
93                         corners: true,
94                         fade: true,
95                         widthRatio: 0.8612,
96                         transition: $.mobile.defaultDialogTransition,
97                         initSelector: ":jqmData(role='popupwindow')"
98                 },
99
100                 _htmlProto: {
101                         ui: {
102                                 screen:    "#popupwindow-screen",
103                                 container: "#popupwindow-container"
104                         }
105                 },
106
107                 _create: function () {
108                         var thisPage = this.element.closest(":jqmData(role='page')"),
109                                 self = this,
110                                 popup = this.element,
111                                 o = this.options,
112                                 style = popup.attr( 'data-style' );
113
114                         if (thisPage.length === 0) {
115                                 thisPage = $("body");
116                         }
117
118                         // Drop a placeholder into the location from which we shall rip out the popup window contents
119                         this._ui.placeholder =
120                                         $("<div><!-- placeholder" +
121                                                                         (this.element.attr("id") === undefined
122                                                                          ? ""
123                                                                          : " for " + this.element.attr("id")) + " --></div>")
124                                         .css("display", "none")
125                                         .insertBefore(this.element);
126
127                         // Apply the proto
128                         thisPage.append(this._ui.screen);
129                         this._ui.container.insertAfter(this._ui.screen);
130                         this._ui.container.append(this.element);
131
132                         // Define instance variables
133                         $.extend( self, {
134                                 _isOpen: false
135                         });
136
137                         //Data Style Start
138                         if (style) {
139                                 o.style = style;
140                         }
141
142                         popup.addClass( o.style );
143                         popup.find( ":jqmData(role='title')" )
144                                         .wrapAll( "<div class='popup-title'></div>" );
145                         popup.find( ":jqmData(role='text')" )
146                                         .wrapAll( "<div class='popup-text'></div>" );
147                         popup.find( ":jqmData(role='button-bg')" )
148                                         .wrapAll( "<div class='popup-button-bg'></div>" );
149                         popup.find( ":jqmData(role='check-bg')" )
150                                         .wrapAll( "<div class='popup-check-bg'></div>" );
151                         popup.find( ":jqmData(role='scroller-bg')" )
152                                         .wrapAll( "<div class='popup-scroller-bg'></div>" );
153                         popup.find( ":jqmData(role='text-bottom-bg')" )
154                                         .wrapAll( "<div class='popup-text-bottom-bg'></div>" );
155                         popup.find( ":jqmData(role='text-left')" )
156                                         .wrapAll( "<div class='popup-text-left'></div>" );
157                         popup.find( ":jqmData(role='text-right')" )
158                                         .wrapAll( "<div class='popup-text-right'></div>" );
159                         popup.find( ":jqmData(role='progress-bg')" )
160                                         .wrapAll( "<div class='popup-progress-bg'></div>" );
161                         //Data Style End
162
163                         // Events on "screen" overlay
164                         this._ui.screen.bind( "vclick", function (event) {
165                                 self.close();
166                         });
167                 },
168
169                 _realSetTheme: function (dst, theme) {
170
171                         var classes = (dst.attr("class") || "").split(" "),
172                                 alreadyAdded = true,
173                                 currentTheme = null,
174                                 matches;
175
176                         while (classes.length > 0) {
177                                 currentTheme = classes.pop();
178                                 matches = currentTheme.match(/^ui-body-([a-z])$/);
179                                 if (matches && matches.length > 1) {
180                                         currentTheme = matches[1];
181                                         break;
182                                 } else {
183                                         currentTheme = null;
184                                 }
185                         }
186
187                         dst.removeClass("ui-body-" + currentTheme);
188                         if ((theme || "").match(/[a-z]/)) {
189                                 dst.addClass("ui-body-" + theme);
190                         }
191                 },
192
193                 _setTheme: function (value) {
194                         this._realSetTheme(this.element, value);
195                         this.options.theme = value;
196                         this.element.attr("data-" + ($.mobile.ns || "") + "theme", value);
197                 },
198
199                 _setOverlayTheme: function (value) {
200                         this._realSetTheme(this._ui.container, value);
201                         // The screen must always have some kind of background for fade to work, so, if the theme is being unset,
202         // set the background to "a".
203                         this._realSetTheme(this._ui.screen, (value === "" ? "a" : value));
204                         this.options.overlayTheme = value;
205                         this.element.attr("data-" + ($.mobile.ns || "") + "overlay-theme", value);
206                 },
207
208                 _setShadow: function (value) {
209                         this.options.shadow = value;
210                         this.element.attr("data-" + ($.mobile.ns || "") + "shadow", value);
211                         this._ui.container[value ? "addClass" : "removeClass"]("ui-overlay-shadow");
212                 },
213
214                 _setCorners: function (value) {
215                         this.options.corners = value;
216                         this.element.attr("data-" + ($.mobile.ns || "") + "corners", value);
217                         this._ui.container[value ? "addClass" : "removeClass"]("ui-corner-all");
218                 },
219
220                 _setFade: function (value) {
221                         this.options.fade = value;
222                         this.element.attr("data-" + ($.mobile.ns || "") + "fade", value);
223                 },
224
225                 _setTransition: function (value) {
226                         this._ui.container
227                                 .removeClass((this.options.transition || ""))
228                                 .addClass(value);
229                         this.options.transition = value;
230                         this.element.attr("data-" + ($.mobile.ns || "") + "transition", value);
231                 },
232
233                 _setDisabled: function (value) {
234                         $.Widget.prototype._setOption.call(this, "disabled", value);
235                         if (value) {
236                                 this.close();
237                         }
238                 },
239
240                 _placementCoords: function (x, y, cx, cy) {
241                         // Try and center the overlay over the given coordinates
242                         var ret,
243                                 scrollTop = $(window).scrollTop(),
244                                 screenHeight = $(window).height(),
245                                 screenWidth = $(window).width(),
246                                 halfheight = cy / 2,
247                                 maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
248                                 roomtop = y - scrollTop,
249                                 roombot = scrollTop + screenHeight - y,
250                                 newtop,
251                                 newleft;
252
253                         if ( roomtop > cy / 2 && roombot > cy / 2 ) {
254                                 newtop = y - halfheight;
255                         } else {
256                                 // 30px tolerance off the edges
257                                 newtop = roomtop > roombot ? scrollTop + screenHeight - cy - 30 : scrollTop + 30;
258                         }
259
260                         // If the menuwidth is smaller than the screen center is
261                         if ( cx < maxwidth ) {
262                                 newleft = ( screenWidth - cx ) / 2;
263                         } else {
264                                 //otherwise insure a >= 30px offset from the left
265                                 newleft = x - cx / 2;
266
267                                 // 10px tolerance off the edges
268                                 if ( newleft < 10 ) {
269                                         newleft = 10;
270                                 } else if ( ( newleft + cx ) > screenWidth ) {
271                                         newleft = screenWidth - cx - 10;
272                                 }
273                         }
274
275                         return { x : newleft, y : newtop };
276                 },
277
278                 destroy: function () {
279                 // Put the element back where we ripped it out from
280                         this.element.insertBefore(this._ui.placeholder);
281
282                         // Clean up
283                         this._ui.placeholder.remove();
284                         this._ui.container.remove();
285                         this._ui.screen.remove();
286                         this.element.triggerHandler("destroyed");
287                         $.Widget.prototype.destroy.call(this);
288                 },
289
290                 open: function (x_where, y_where) {
291                         if (!(this._isOpen || this.options.disabled)) {
292                                 var self = this,
293                                         x = (undefined === x_where ? $(window).width()  / 2 : x_where),
294                                         y = (undefined === y_where ? $(window).height() / 2 : y_where),
295                                         coords,
296                                         zIndexMax = 0,
297                                         ctxpopup = this.element.data("ctxpopup"),
298                                         popupWidth,
299                                         menuHeight,
300                                         menuWidth,
301                                         scrollTop,
302                                         screenHeight,
303                                         screenWidth,
304                                         roomtop,
305                                         roombot,
306                                         halfheight,
307                                         maxwidth,
308                                         newtop,
309                                         newleft;
310
311                                 if ( !ctxpopup ) {
312                                         popupWidth = $(window).width() * this.options.widthRatio;
313                                         this._ui.container.css("width", popupWidth);
314                                         // If the width of the popup exceeds the width of the window, we need to limit the width here,
315                                         // otherwise outer{Width,Height}(true) below will happily report the unrestricted values, causing
316                                         // the popup to get placed wrong.
317                                         if (this._ui.container.outerWidth(true) > $(window).width()) {
318                                                 this._ui.container.css({"max-width" : $(window).width() - 30});
319                                         }
320                                 }
321
322                                 coords = this._placementCoords(x, y,
323                                         this._ui.container.outerWidth(true),
324                                         this._ui.container.outerHeight(true));
325
326                                 $(document)
327                                         .find("*")
328                                         .each(function () {
329                                                 var el = $(this),
330                                                         zIndex = parseInt(el.css("z-index"), 10);
331                                                 if (!(el.is(self._ui.container) || el.is(self._ui.screen) || isNaN(zIndex))) {
332                                                         zIndexMax = Math.max(zIndexMax, zIndex);
333                                                 }
334                                         });
335
336                                 this._ui.screen
337                                         .height($(document).height())
338                                         .removeClass("ui-screen-hidden");
339
340                                 if (this.options.fade) {
341                                         this._ui.screen.animate({opacity: 0.5}, "fast");
342                                 } else {
343                                         this._ui.screen.css({opacity: 0.0});
344                                 }
345
346                                 //Recalculate popup position
347                                 menuHeight = this._ui.container.innerHeight(true);
348                                 menuWidth = this._ui.container.innerWidth(true);
349                                 scrollTop = $(window).scrollTop();
350                                 screenHeight = $(window).height();
351                                 screenWidth = $(window).width();
352                                 roomtop = y - scrollTop;
353                                 roombot = scrollTop + screenHeight - y;
354                                 halfheight = menuHeight / 2;
355                                 maxwidth = parseFloat( this._ui.container.css( "max-width" ) );
356                                 newtop = (screenHeight - menuHeight) / 2 + scrollTop;
357
358                                 if ( menuWidth < maxwidth ) {
359                                         newleft = ( screenWidth - menuWidth ) / 2;
360                                 } else {
361                                         //otherwise insure a >= 30px offset from the left
362                                         newleft = x - menuWidth / 2;
363
364                                         // 30px tolerance off the edges
365                                         if ( newleft < 30 ) {
366                                                 newleft = 30;
367                                         } else if ( ( newleft + menuWidth ) > screenWidth ) {
368                                                 newleft = screenWidth - menuWidth - 30;
369                                         }
370                                 }
371                                 //Recalculate popup position End
372                                 if ( ctxpopup ) {
373                                         newtop = coords.y;
374                                         newleft = coords.x;
375                                 }
376
377                                 this._ui.container
378                                         .removeClass("ui-selectmenu-hidden")
379                                         .css({
380                                                 top: newtop,
381                                                 left: newleft
382                                         })
383                                         .addClass("in")
384                                         .animationComplete(function () {
385                                                 self._ui.screen.height($(document).height());
386                                         });
387
388                                 this._isOpen = true;
389                         }
390                 },
391
392                 close: function () {
393                         if (this._isOpen) {
394                                 var self = this,
395                                         hideScreen = function () {
396                                                 self._ui.screen.addClass("ui-screen-hidden");
397                                                 self._isOpen = false;
398                                                 self.element.trigger("closed");
399                                         };
400
401                                 this._ui.container
402                                         .removeClass("in")
403                                         .addClass("reverse out")
404                                         .animationComplete(function () {
405                                                 self._ui.container
406                                                         .removeClass("reverse out")
407                                                         .addClass("ui-selectmenu-hidden")
408                                                         .removeAttr("style");
409                                         });
410
411                                 if (this.options.fade) {
412                                         this._ui.screen.animate({opacity: 0.0}, "fast", hideScreen);
413                                 } else {
414                                         hideScreen();
415                                 }
416                         }
417                 }
418         });
419
420         $.tizen.popupwindow.bindPopupToButton = function (btn, popup) {
421                 if (btn.length === 0 || popup.length === 0) {
422                         return;
423                 }
424
425                 var btnVClickHandler = function (e) {
426                         // When /this/ button causes a popup, align the popup's theme with that of the button, unless the popup has a theme pre-set
427                         if (!popup.jqmData("overlay-theme-set")) {
428                                 popup.popupwindow("option", "overlayTheme", btn.jqmData("theme"));
429                         }
430                         popup.popupwindow("open",
431                                 btn.offset().left + btn.outerWidth()  / 2,
432                                 btn.offset().top  + btn.outerHeight() / 2);
433
434                         // Swallow event, because it might end up getting picked up by the popup window's screen handler, which
435                         // will in turn cause the popup window to close - Thanks Sasha!
436                         if (e.stopPropagation) {
437                                 e.stopPropagation();
438                         }
439                         if (e.preventDefault) {
440                                 e.preventDefault();
441                         }
442                 };
443
444                 // If the popup has a theme set, prevent it from being clobbered by the associated button
445                 if ((popup.popupwindow("option", "overlayTheme") || "").match(/[a-z]/)) {
446                         popup.jqmData("overlay-theme-set", true);
447                 }
448
449                 btn
450                         .attr({
451                                 "aria-haspopup": true,
452                                 "aria-owns": btn.attr("href")
453                         })
454                         .removeAttr("href")
455                         .bind("vclick", btnVClickHandler);
456
457                 popup.bind("destroyed", function () {
458                         btn.unbind("vclick", btnVClickHandler);
459                 });
460         };
461
462         $(document).bind("pagecreate create", function (e) {
463                 $($.tizen.popupwindow.prototype.options.initSelector, e.target)
464                         .not(":jqmData(role='none'), :jqmData(role='nojs')")
465                         .popupwindow();
466
467                 $("a[href^='#']:jqmData(rel='popupwindow')", e.target).each(function () {
468                         $.tizen.popupwindow.bindPopupToButton($(this), $($(this).attr("href")));
469                 });
470         });
471
472 }(jQuery));