tizen beta release
[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) 2011 by Intel Corporation Ltd.
9  *
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:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies or substantial portions of the Software.
19  *
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  * ***************************************************************************
28  *
29  * Authors: Gabriel Schulhof <gabriel.schulhof@intel.com>,
30  *          Elliot Smith <elliot.smith@intel.com>
31  */
32
33 // Shows other elements inside a popup window.
34 //
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 :
38 //
39 //     $("#mypopupwindowContent").popupwindow();
40 // where the html might be :
41 //     <div id="mypopupwindowContent"></div>
42 //
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.
48
49 // You can associate a button with a popup window like this:
50 //      <div id="mypopupContent" style="display: table;" data-role="popupwindow">
51 //          <table>
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>
56 //          </table>
57 //      </div>
58 // <a href="#myPopupContent" data-rel="popupwindow" data-role="button">Show popup</a>
59 //
60 // Options:
61 //
62 //     theme: String; the theme for the popupwindow contents
63 //                   Default: null
64 //
65 //     overlayTheme: String; the theme for the popupwindow
66 //                   Default: null
67 //
68 //     shadow: Boolean; display a shadow around the popupwindow
69 //             Default: true
70 //
71 //     corners: Boolean; display a shadow around the popupwindow
72 //             Default: true
73 //
74 //     fade: Boolean; fades the opening and closing of the popupwindow
75 //
76 //     transition: String; the transition to use when opening or closing
77 //                 a popupwindow
78 //                 Default: $.mobile.defaultDialogTransition
79 //
80 // Events:
81 //     close: Emitted when the popupwindow is closed.
82
83 (function( $, undefined ) {
84
85 $.widget( "tizen.popupwindow", $.tizen.widgetex, {
86     options: {
87         theme: null,
88         overlayTheme: "s",
89         style: "custom",
90         disabled: false,
91         shadow: true,
92         corners: true,
93         fade: true,
94                 widthRatio: 0.8612,
95         transition: $.mobile.defaultDialogTransition,
96         initSelector: ":jqmData(role='popupwindow')"
97     },
98
99     _htmlProto: {
100         ui: {
101             screen:    "#popupwindow-screen",
102             container: "#popupwindow-container"
103         }
104     },
105
106     _create: function() {
107         var thisPage = this.element.closest(":jqmData(role='page')"),
108             self = this;
109
110         if (thisPage.length === 0)
111             thisPage = $("body");
112
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 
117                         ? "" 
118                         : " for " + this.element.attr("id")) + " --></div>")
119                 .css("display", "none")
120                 .insertBefore(this.element);
121
122         // Apply the proto
123         thisPage.append(this._ui.screen);
124         this._ui.container.insertAfter(this._ui.screen);
125         this._ui.container.append(this.element);
126
127         // Define instance variables
128         $.extend( self, {
129             _isOpen: false
130         });
131
132         //Data Style Start
133         var popup = this.element;
134         var o = this.options;
135         var style = popup.attr( 'data-style' );
136         o.style =  style ? style : o.style;
137
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>" );
157         //Data Style End
158
159
160         // Events on "screen" overlay
161         this._ui.screen.bind( "vclick", function( event ) {
162             self.close();
163         });
164     },
165
166     _realSetTheme: function(dst, theme) {
167         var classes = (dst.attr("class") || "").split(" "),
168             alreadyAdded = true,
169             currentTheme = null,
170             matches;
171
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];
177                 break;
178             }
179             else
180                 currentTheme = null;
181         }
182
183         dst.removeClass("ui-body-" + currentTheme);
184         if ((theme || "").match(/[a-z]/))
185             dst.addClass("ui-body-" + theme);
186     },
187
188     _setTheme: function(value) {
189         this._realSetTheme(this.element, value);
190         this.options.theme = value;
191         this.element.attr("data-" + ($.mobile.ns || "") + "theme", value);
192     },
193
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);
201     },
202
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");
207     },
208
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");
213     },
214
215     _setFade: function(value) {
216         this.options.fade = value;
217         this.element.attr("data-" + ($.mobile.ns || "") + "fade", value);
218     },
219
220     _setTransition: function(value) {
221         this._ui.container
222                 .removeClass((this.options.transition || ""))
223                 .addClass(value);
224         this.options.transition = value;
225         this.element.attr("data-" + ($.mobile.ns || "") + "transition", value);
226     },
227
228     _setDisabled: function(value) {
229         $.Widget.prototype._setOption.call(this, "disabled", value);
230         if (value)
231             this.close();
232     },
233
234     _placementCoords: function(x, y, cx, cy) {
235         // Try and center the overlay over the given coordinates
236         var ret,
237             scrollTop = $(window).scrollTop(),
238             screenHeight = $(window).height(),
239             screenWidth = $(window).width(),
240             halfheight = cy / 2,
241             maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
242             roomtop = y - scrollTop,
243             roombot = scrollTop + screenHeight - y,
244             newtop, newleft;
245
246         if ( roomtop > cy / 2 && roombot > cy / 2 ) {
247             newtop = y - halfheight;
248         }
249         else {
250             // 30px tolerance off the edges
251             newtop = roomtop > roombot ? scrollTop + screenHeight - cy - 30 : scrollTop + 30;
252         }
253
254         // If the menuwidth is smaller than the screen center is
255         if ( cx < maxwidth ) {
256             newleft = ( screenWidth - cx ) / 2;
257         }
258         else {
259             //otherwise insure a >= 30px offset from the left
260             newleft = x - cx / 2;
261
262             // 10px tolerance off the edges
263             if ( newleft < 10 ) {
264                 newleft = 10;
265             }
266             else
267             if ( ( newleft + cx ) > screenWidth ) {
268                 newleft = screenWidth - cx - 10;
269             }
270         }
271
272         return { x : newleft, y : newtop };
273     },
274
275     destroy: function() {
276         // Put the element back where we ripped it out from
277         this.element.insertBefore(this._ui.placeholder);
278
279         // Clean up
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);
285     },
286
287     open: function(x_where, y_where) {
288         if (!(this._isOpen || this.options.disabled)) {
289             var self = this,
290                 x = (undefined === x_where ? window.innerWidth  / 2 : x_where),
291                 y = (undefined === y_where ? window.innerHeight / 2 : y_where),
292                 coords,
293                 zIndexMax = 0;
294
295                         var ctxpopup = this.element.data("ctxpopup");
296                         if ( !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});
304                         }
305
306             coords = this._placementCoords(x, y,
307                 this._ui.container.outerWidth(true),
308                 this._ui.container.outerHeight(true));
309
310                         $(document)
311                 .find("*")
312                 .each(function() {
313                     var el = $(this),
314                         zIndex = parseInt(el.css("z-index"));
315
316                     if (!(el.is(self._ui.container) || el.is(self._ui.screen) || isNaN(zIndex)))
317                         zIndexMax = Math.max(zIndexMax, zIndex);
318                 });
319
320             this._ui.screen
321                 .height($(document).height())
322                 .removeClass("ui-screen-hidden");
323
324             if (this.options.fade)
325                 this._ui.screen.animate({opacity: 0.5}, "fast");
326             else
327                 this._ui.screen.css({opacity: 0.0});
328
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;
335
336             var roomtop = y - scrollTop,
337                   roombot = scrollTop + screenHeight - y,
338                   halfheight = menuHeight / 2,
339                   maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
340                   newtop, newleft;
341
342             newtop = (screenHeight - menuHeight) / 2 + scrollTop;
343
344               if ( menuWidth < maxwidth ) {
345                   newleft = ( screenWidth - menuWidth ) / 2;
346               }
347               else {
348                   //otherwise insure a >= 30px offset from the left
349                   newleft = x - menuWidth / 2;
350         
351                   // 30px tolerance off the edges
352                   if ( newleft < 30 ) {
353                       newleft = 30;
354                   }
355                   else if ( ( newleft + menuWidth ) > screenWidth ) {
356                       newleft = screenWidth - menuWidth - 30;
357                   }
358               }
359             //Recalculate popup position End
360                 if ( ctxpopup ) {
361                         newtop = coords.y;
362                         newleft = coords.x;
363                 }
364                 
365             this._ui.container
366                 .removeClass("ui-selectmenu-hidden")
367                 .css({
368                       top: newtop,
369                       left: newleft
370                 })
371                 .addClass("in")
372                 .animationComplete(function() {
373                     self._ui.screen.height($(document).height());
374                 });
375
376             this._isOpen = true;
377         }
378     },
379
380     close: function() {
381         if (this._isOpen) {
382             var self = this,
383                 hideScreen = function() {
384                     self._ui.screen.addClass("ui-screen-hidden");
385                     self._isOpen = false;
386                     self.element.trigger("closed");
387                 };
388
389             this._ui.container
390                 .removeClass("in")
391                 .addClass("reverse out")
392                 .animationComplete(function() {
393                     self._ui.container
394                         .removeClass("reverse out")
395                         .addClass("ui-selectmenu-hidden")
396                         .removeAttr("style");
397                 });
398
399             if (this.options.fade)
400                 this._ui.screen.animate({opacity: 0.0}, "fast", hideScreen);
401             else
402                 hideScreen();
403         }
404     }
405 });
406
407 $.tizen.popupwindow.bindPopupToButton = function(btn, popup) {
408     if (btn.length === 0 || popup.length === 0) return;
409
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);
417
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)
421                 e.stopPropagation();
422             if (e.preventDefault)
423                 e.preventDefault();
424         };
425
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);
429
430     btn
431         .attr({
432             "aria-haspopup": true,
433             "aria-owns": btn.attr("href")
434         })
435         .removeAttr("href")
436         .bind("vclick", btnVClickHandler);
437
438     popup.bind("destroyed", function() {
439         btn.unbind("vclick", btnVClickHandler);
440     });
441 };
442
443 $(document).bind("pagecreate create", function(e) {
444     $($.tizen.popupwindow.prototype.options.initSelector, e.target)
445         .not(":jqmData(role='none'), :jqmData(role='nojs')")
446         .popupwindow();
447
448     $("a[href^='#']:jqmData(rel='popupwindow')", e.target).each(function() {
449        $.tizen.popupwindow.bindPopupToButton($(this), $($(this).attr("href")));
450     });
451 });
452
453 })(jQuery);