tizen beta release
[platform/framework/web/web-ui-fw.git] / src / widgets / popupwindow_ctxpopup / js / jquery.mobile.tizen.ctxpopup.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  */
31
32 // This widget is implemented in an extremely ugly way. It should derive from $.tizen.popupwindow, but it doesn't
33 // because there's a bug in jquery.ui.widget.js which was fixed in jquery-ui commit
34 // b9153258b0f0edbff49496ed16d2aa93bec07d95. Once a version of jquery-ui containing that commit is released
35 // (probably >= 1.9m5), and jQuery Mobile picks up the widget from there, this widget needs to be rewritten properly.
36 // The problem is that, when a widget inherits from a superclass and declares an object in its prototype identical in key
37 // to one in the superclass, upon calling $.widget the object is overwritten in both the prototype of the superclass and
38 // the prototype of the subclass. The prototype of the superclass should remain unchanged.
39
40 (function($, undefined) {
41     $.widget("tizen.ctxpopup", $.tizen.widgetex, {
42         options: $.extend({}, $.tizen.popupwindow.prototype.options, {
43             initSelector: ":not(:not(" + $.tizen.popupwindow.prototype.options.initSelector + ")):not(:not(:jqmData(show-arrow='true'), :jqmData(show-arrow)))"
44         }),
45
46         _htmlProto: {
47             ui: {
48                 outer     : "#outer",
49                 container : "#container", // the key has to have the name "container"
50                 arrow     : {
51                     all : ":jqmData(role='triangle')",
52                     l   : "#left",
53                     t   : "#top",
54                     r   : "#right",
55                     b   : "#bottom"
56                 }
57             }
58         },
59
60         _create: function(){
61             if (!this.element.data("popupwindow"))
62                 this.element.popupwindow();
63             this.element.data("popupwindow")
64                     ._ui.container
65                         .removeClass("ui-popupwindow-padding")
66                         .append(this._ui.outer);
67             this._ui.outer.trigger("create"); // Creates the triangle widgets
68             this._ui.container
69                 .addClass("ui-popupwindow-padding")
70                 .append(this.element);
71         },
72
73         _setOption: function(key, value) {
74             $.tizen.popupwindow.prototype._setOption.apply(this.element.data("popupwindow"), arguments);
75             this.options[key] = value;
76         },
77     });
78
79 var origOpen = $.tizen.popupwindow.prototype.open,
80     orig_setOption = $.tizen.popupwindow.prototype._setOption,
81     orig_placementCoords = $.tizen.popupwindow.prototype._placementCoords;
82
83 $.tizen.popupwindow.prototype._setOption = function(key, value) {
84     var ctxpopup = this.element.data("ctxpopup"),
85         needsApplying = true;
86     if (ctxpopup) {
87         if ("shadow" === key || "overlayTheme" === key || "corners" === key) {
88             var origContainer = this._ui.container;
89
90             this._ui.container = ctxpopup._ui.container;
91             orig_setOption.apply(this, arguments);
92             this._ui.container = origContainer;
93             needsApplying = false;
94         }
95         ctxpopup.options[key] = value;
96     }
97
98     if (needsApplying)
99         orig_setOption.apply(this, arguments);
100 };
101
102 $.tizen.popupwindow.prototype._placementCoords = function(x, y, cx, cy) {
103     var ctxpopup = this.element.data("ctxpopup"),
104         self = this;
105
106     if (ctxpopup) {
107         var coords = {}, minDiff, minDiffIdx;
108
109         // Returns:
110         // {
111         //    absDiff: int
112         //    triangleOffset: int
113         //    actual: { x: int, y: int }
114         // }
115         function getCoords(arrow, x_factor, y_factor) {
116             // Unhide the arrow we want to test to take it into account
117             ctxpopup._ui.arrow.all.hide();
118             ctxpopup._ui.arrow[arrow].show();
119
120             var isHorizontal = ("b" === arrow || "t" === arrow),
121                 // Names of keys used in calculations depend on whether things are horizontal or not
122                 coord = (isHorizontal
123                     ? {point: "x", size: "cx", beg: "left", outerSize: "outerWidth",  niceSize: "width",  triangleSize : "height"}
124                     : {point: "y", size: "cy", beg: "top",  outerSize: "outerHeight", niceSize: "height", triangleSize : "width"}),
125                 size = {
126                     cx : self._ui.container.width(),
127                     cy : self._ui.container.height()
128                 },
129                 halfSize = {
130                     cx : size.cx / 2,
131                     cy : size.cy / 2
132                 },
133                 desired = { 
134                     "x" : x + halfSize.cx * x_factor,
135                     "y" : y + halfSize.cy * y_factor
136                 },
137                 orig = orig_placementCoords.call(self, desired.x, desired.y, size.cx, size.cy),
138
139                 // The triangleOffset must be clamped to the range described below:
140                 //
141                 //                          +-------...
142                 //                          |   /\
143                 //                          |  /  \
144                 //                   ----+--+-,-----...
145                 //lowerDiff       -->____|  |/ <-- possible rounded corner
146                 //triangle size   -->    | /|
147                 //                   ____|/ |
148                 //                    ^  |\ | <-- lowest possible offset for triangle
149                 // actual range of    |  | \| 
150                 // arrow offset       |  |  | 
151                 // values due to      |  .  . Payload table cell looks like
152                 // possible rounded   |  .  . a popup window, and it may have
153                 // corners and arrow  |  .  . arbitrary things like borders,
154                 // triangle size -    |  |  | shadows, and rounded corners.
155                 // our clamp range    |  | /|
156                 //                   _v__|/ |
157                 //triangle size   -->    |\ | <-- highest possible offset for triangle
158                 //                   ____| \|
159                 //upperDiff       -->    |  |\ <-- possible rounded corner
160                 //                   ----+--+-'-----...
161                 //                          |  \  /
162                 //                          |   \/
163                 //                          +-------...
164                 //
165                 // We calculate lowerDiff and upperDiff by considering the offset and width of the payload (this.element)
166                 // versus the offset and width of the element enclosing the triangle, because the payload is inside
167                 // whatever decorations (such as borders, shadow, rounded corners) and thus can give a reliable indication
168                 // of the thickness of the combined decorations
169
170                 arrowBeg = ctxpopup._ui.arrow[arrow].offset()[coord.beg],
171                 arrowSize = ctxpopup._ui.arrow[arrow][coord.outerSize](true),
172                 payloadBeg = self.element.offset()[coord.beg],
173                 payloadSize = self.element[coord.outerSize](true),
174                 triangleSize = ctxpopup._ui.arrow[arrow][coord.triangleSize](),
175                 triangleOffset = 
176                     Math.max(
177                         triangleSize // triangle size
178                             + Math.max(0, payloadBeg - arrowBeg), // lowerDiff
179                         Math.min(
180                             arrowSize // bottom
181                                 - triangleSize // triangle size
182                                 - Math.max(0, arrowBeg + arrowSize - (payloadBeg + payloadSize)), // upperDiff
183                             arrowSize / 2 // arrow unrestricted offset
184                                 + desired[coord.point]
185                                 - orig[coord.point]
186                                 - halfSize[coord.size])),
187                 // Triangle points here
188                 final = {
189                     "x": orig.x + ( isHorizontal ? triangleOffset : 0) + ("r" === arrow ? size.cx : 0),
190                     "y": orig.y + (!isHorizontal ? triangleOffset : 0) + ("b" === arrow ? size.cy : 0)
191                 },
192                 ret = {
193                     actual         : orig,
194                     triangleOffset : triangleOffset,
195                     absDiff        : Math.abs(x - final.x) + Math.abs(y - final.y)
196                 };
197
198             // Hide it back
199             ctxpopup._ui.arrow[arrow].hide();
200
201             return ret;
202         }
203
204         coords = {
205             l : getCoords("l",  1,  0),
206             r : getCoords("r", -1,  0),
207             t : getCoords("t",  0,  1),
208             b : getCoords("b",  0, -1)
209         };
210
211         $.each(coords, function(key, value) {
212             if (minDiff === undefined || value.absDiff < minDiff) {
213                 minDiff = value.absDiff;
214                 minDiffIdx = key;
215             }
216         });
217
218         // Side-effect: show the appropriate arrow and move it to the right offset
219         ctxpopup._ui.arrow[minDiffIdx]
220             .show()
221             .triangle("option", "offset", coords[minDiffIdx].triangleOffset);
222         return coords[minDiffIdx].actual;
223     }
224     else
225         return orig_placementCoords.call(this, x, y, cx, cy);
226 };
227
228 $.tizen.popupwindow.prototype.open = function(x, y) {
229     var ctxpopup = this.element.data("ctxpopup");
230
231     if (ctxpopup) {
232         this._setShadow(false);
233         this._setCorners(false);
234         this._setOverlayTheme(null);
235         this._setOption("overlayTheme", ctxpopup.options.overlayTheme);
236         ctxpopup._ui.arrow.all.triangle("option", "color", ctxpopup._ui.container.css("background-color"));
237
238                 // temporary
239                 $('.ui-popupwindow').css('background', 'none');
240     }
241
242     origOpen.call(this, x, y);
243 };
244
245 //auto self-init widgets
246 $( document ).bind( "pagecreate create", function( e ){
247     var ctxpopups = $($.tizen.ctxpopup.prototype.options.initSelector, e.target);
248     $.tizen.ctxpopup.prototype.enhanceWithin( e.target );
249
250 });
251
252 })(jQuery);