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