source: Dev/branches/rest-dojo-ui/client/dijit/place.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 12.7 KB
RevLine 
[256]1define([
2        "dojo/_base/array", // array.forEach array.map array.some
3        "dojo/dom-geometry", // domGeometry.getMarginBox domGeometry.position
4        "dojo/dom-style", // domStyle.getComputedStyle
5        "dojo/_base/kernel", // kernel.deprecated
6        "dojo/_base/window", // win.body
7        "dojo/window", // winUtils.getBox
8        "."     // dijit (defining dijit.place to match API doc)
9], function(array, domGeometry, domStyle, kernel, win, winUtils, dijit){
10
11        // module:
12        //              dijit/place
13        // summary:
14        //              Code to place a popup relative to another node
15
16
17        function _place(/*DomNode*/ node, choices, layoutNode, aroundNodeCoords){
18                // summary:
19                //              Given a list of spots to put node, put it at the first spot where it fits,
20                //              of if it doesn't fit anywhere then the place with the least overflow
21                // choices: Array
22                //              Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
23                //              Above example says to put the top-left corner of the node at (10,20)
24                // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
25                //              for things like tooltip, they are displayed differently (and have different dimensions)
26                //              based on their orientation relative to the parent.       This adjusts the popup based on orientation.
27                //              It also passes in the available size for the popup, which is useful for tooltips to
28                //              tell them that their width is limited to a certain amount.       layoutNode() may return a value expressing
29                //              how much the popup had to be modified to fit into the available space.   This is used to determine
30                //              what the best placement is.
31                // aroundNodeCoords: Object
32                //              Size of aroundNode, ex: {w: 200, h: 50}
33
34                // get {x: 10, y: 10, w: 100, h:100} type obj representing position of
35                // viewport over document
36                var view = winUtils.getBox();
37
38                // This won't work if the node is inside a <div style="position: relative">,
39                // so reattach it to win.doc.body.       (Otherwise, the positioning will be wrong
40                // and also it might get cutoff)
41                if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){
42                        win.body().appendChild(node);
43                }
44
45                var best = null;
46                array.some(choices, function(choice){
47                        var corner = choice.corner;
48                        var pos = choice.pos;
49                        var overflow = 0;
50
51                        // calculate amount of space available given specified position of node
52                        var spaceAvailable = {
53                                w: {
54                                        'L': view.l + view.w - pos.x,
55                                        'R': pos.x - view.l,
56                                        'M': view.w
57                                   }[corner.charAt(1)],
58                                h: {
59                                        'T': view.t + view.h - pos.y,
60                                        'B': pos.y - view.t,
61                                        'M': view.h
62                                   }[corner.charAt(0)]
63                        };
64
65                        // configure node to be displayed in given position relative to button
66                        // (need to do this in order to get an accurate size for the node, because
67                        // a tooltip's size changes based on position, due to triangle)
68                        if(layoutNode){
69                                var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
70                                overflow = typeof res == "undefined" ? 0 : res;
71                        }
72
73                        // get node's size
74                        var style = node.style;
75                        var oldDisplay = style.display;
76                        var oldVis = style.visibility;
77                        if(style.display == "none"){
78                                style.visibility = "hidden";
79                                style.display = "";
80                        }
81                        var mb = domGeometry. getMarginBox(node);
82                        style.display = oldDisplay;
83                        style.visibility = oldVis;
84
85                        // coordinates and size of node with specified corner placed at pos,
86                        // and clipped by viewport
87                        var
88                                startXpos = {
89                                        'L': pos.x,
90                                        'R': pos.x - mb.w,
91                                        'M': Math.max(view.l, Math.min(view.l + view.w, pos.x + (mb.w >> 1)) - mb.w) // M orientation is more flexible
92                                }[corner.charAt(1)],
93                                startYpos = {
94                                        'T': pos.y,
95                                        'B': pos.y - mb.h,
96                                        'M': Math.max(view.t, Math.min(view.t + view.h, pos.y + (mb.h >> 1)) - mb.h)
97                                }[corner.charAt(0)],
98                                startX = Math.max(view.l, startXpos),
99                                startY = Math.max(view.t, startYpos),
100                                endX = Math.min(view.l + view.w, startXpos + mb.w),
101                                endY = Math.min(view.t + view.h, startYpos + mb.h),
102                                width = endX - startX,
103                                height = endY - startY;
104
105                        overflow += (mb.w - width) + (mb.h - height);
106
107                        if(best == null || overflow < best.overflow){
108                                best = {
109                                        corner: corner,
110                                        aroundCorner: choice.aroundCorner,
111                                        x: startX,
112                                        y: startY,
113                                        w: width,
114                                        h: height,
115                                        overflow: overflow,
116                                        spaceAvailable: spaceAvailable
117                                };
118                        }
119
120                        return !overflow;
121                });
122
123                // In case the best position is not the last one we checked, need to call
124                // layoutNode() again.
125                if(best.overflow && layoutNode){
126                        layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords);
127                }
128
129                // And then position the node.  Do this last, after the layoutNode() above
130                // has sized the node, due to browser quirks when the viewport is scrolled
131                // (specifically that a Tooltip will shrink to fit as though the window was
132                // scrolled to the left).
133                //
134                // In RTL mode, set style.right rather than style.left so in the common case,
135                // window resizes move the popup along with the aroundNode.
136                var l = domGeometry.isBodyLtr(),
137                        s = node.style;
138                s.top = best.y + "px";
139                s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
140                s[l ? "right" : "left"] = "auto";       // needed for FF or else tooltip goes to far left
141
142                return best;
143        }
144
145        /*=====
146        dijit.place.__Position = function(){
147                // x: Integer
148                //              horizontal coordinate in pixels, relative to document body
149                // y: Integer
150                //              vertical coordinate in pixels, relative to document body
151
152                this.x = x;
153                this.y = y;
154        };
155        =====*/
156
157        /*=====
158        dijit.place.__Rectangle = function(){
159                // x: Integer
160                //              horizontal offset in pixels, relative to document body
161                // y: Integer
162                //              vertical offset in pixels, relative to document body
163                // w: Integer
164                //              width in pixels.   Can also be specified as "width" for backwards-compatibility.
165                // h: Integer
166                //              height in pixels.   Can also be specified as "height" from backwards-compatibility.
167
168                this.x = x;
169                this.y = y;
170                this.w = w;
171                this.h = h;
172        };
173        =====*/
174
175        return (dijit.place = {
176                // summary:
177                //              Code to place a DOMNode relative to another DOMNode.
178                //              Load using require(["dijit/place"], function(place){ ... }).
179
180                at: function(node, pos, corners, padding){
181                        // summary:
182                        //              Positions one of the node's corners at specified position
183                        //              such that node is fully visible in viewport.
184                        // description:
185                        //              NOTE: node is assumed to be absolutely or relatively positioned.
186                        // node: DOMNode
187                        //              The node to position
188                        // pos: dijit.place.__Position
189                        //              Object like {x: 10, y: 20}
190                        // corners: String[]
191                        //              Array of Strings representing order to try corners in, like ["TR", "BL"].
192                        //              Possible values are:
193                        //                      * "BL" - bottom left
194                        //                      * "BR" - bottom right
195                        //                      * "TL" - top left
196                        //                      * "TR" - top right
197                        // padding: dijit.place.__Position?
198                        //              optional param to set padding, to put some buffer around the element you want to position.
199                        // example:
200                        //              Try to place node's top right corner at (10,20).
201                        //              If that makes node go (partially) off screen, then try placing
202                        //              bottom left corner at (10,20).
203                        //      |       place(node, {x: 10, y: 20}, ["TR", "BL"])
204                        var choices = array.map(corners, function(corner){
205                                var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
206                                if(padding){
207                                        c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
208                                        c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
209                                }
210                                return c;
211                        });
212
213                        return _place(node, choices);
214                },
215
216                around: function(
217                        /*DomNode*/             node,
218                        /*DomNode || dijit.place.__Rectangle*/ anchor,
219                        /*String[]*/    positions,
220                        /*Boolean*/             leftToRight,
221                        /*Function?*/   layoutNode){
222
223                        // summary:
224                        //              Position node adjacent or kitty-corner to anchor
225                        //              such that it's fully visible in viewport.
226                        //
227                        // description:
228                        //              Place node such that corner of node touches a corner of
229                        //              aroundNode, and that node is fully visible.
230                        //
231                        // anchor:
232                        //              Either a DOMNode or a __Rectangle (object with x, y, width, height).
233                        //
234                        // positions:
235                        //              Ordered list of positions to try matching up.
236                        //                      * before: places drop down to the left of the anchor node/widget, or to the right in
237                        //                              the case of RTL scripts like Hebrew and Arabic
238                        //                      * after: places drop down to the right of the anchor node/widget, or to the left in
239                        //                              the case of RTL scripts like Hebrew and Arabic
240                        //                      * above: drop down goes above anchor node
241                        //                      * above-alt: same as above except right sides aligned instead of left
242                        //                      * below: drop down goes below anchor node
243                        //                      * below-alt: same as below except right sides aligned instead of left
244                        //
245                        // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
246                        //              For things like tooltip, they are displayed differently (and have different dimensions)
247                        //              based on their orientation relative to the parent.       This adjusts the popup based on orientation.
248                        //
249                        // leftToRight:
250                        //              True if widget is LTR, false if widget is RTL.   Affects the behavior of "above" and "below"
251                        //              positions slightly.
252                        //
253                        // example:
254                        //      |       placeAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
255                        //              This will try to position node such that node's top-left corner is at the same position
256                        //              as the bottom left corner of the aroundNode (ie, put node below
257                        //              aroundNode, with left edges aligned).   If that fails it will try to put
258                        //              the bottom-right corner of node where the top right corner of aroundNode is
259                        //              (ie, put node above aroundNode, with right edges aligned)
260                        //
261
262                        // if around is a DOMNode (or DOMNode id), convert to coordinates
263                        var aroundNodePos = (typeof anchor == "string" || "offsetWidth" in anchor)
264                                ? domGeometry.position(anchor, true)
265                                : anchor;
266
267                        // Adjust anchor positioning for the case that a parent node has overflw hidden, therefore cuasing the anchor not to be completely visible
268                        if(anchor.parentNode){
269                                var parent = anchor.parentNode;
270                                while(parent && parent.nodeType == 1 && parent.nodeName != "BODY"){  //ignoring the body will help performance
271                                        var parentPos = domGeometry.position(parent, true);
272                                        var parentStyleOverflow = domStyle.getComputedStyle(parent).overflow;
273                                        if(parentStyleOverflow == "hidden" || parentStyleOverflow == "auto" || parentStyleOverflow == "scroll"){
274                                                var bottomYCoord = Math.min(aroundNodePos.y + aroundNodePos.h, parentPos.y + parentPos.h);
275                                                var rightXCoord = Math.min(aroundNodePos.x + aroundNodePos.w, parentPos.x + parentPos.w);
276                                                aroundNodePos.x = Math.max(aroundNodePos.x, parentPos.x);
277                                                aroundNodePos.y = Math.max(aroundNodePos.y, parentPos.y);
278                                                aroundNodePos.h = bottomYCoord - aroundNodePos.y;
279                                                aroundNodePos.w = rightXCoord - aroundNodePos.x;
280                                        }       
281                                        parent = parent.parentNode;
282                                }
283                        }                       
284
285                        var x = aroundNodePos.x,
286                                y = aroundNodePos.y,
287                                width = "w" in aroundNodePos ? aroundNodePos.w : (aroundNodePos.w = aroundNodePos.width),
288                                height = "h" in aroundNodePos ? aroundNodePos.h : (kernel.deprecated("place.around: dijit.place.__Rectangle: { x:"+x+", y:"+y+", height:"+aroundNodePos.height+", width:"+width+" } has been deprecated.  Please use { x:"+x+", y:"+y+", h:"+aroundNodePos.height+", w:"+width+" }", "", "2.0"), aroundNodePos.h = aroundNodePos.height);
289
290                        // Convert positions arguments into choices argument for _place()
291                        var choices = [];
292                        function push(aroundCorner, corner){
293                                choices.push({
294                                        aroundCorner: aroundCorner,
295                                        corner: corner,
296                                        pos: {
297                                                x: {
298                                                        'L': x,
299                                                        'R': x + width,
300                                                        'M': x + (width >> 1)
301                                                   }[aroundCorner.charAt(1)],
302                                                y: {
303                                                        'T': y,
304                                                        'B': y + height,
305                                                        'M': y + (height >> 1)
306                                                   }[aroundCorner.charAt(0)]
307                                        }
308                                })
309                        }
310                        array.forEach(positions, function(pos){
311                                var ltr =  leftToRight;
312                                switch(pos){
313                                        case "above-centered":
314                                                push("TM", "BM");
315                                                break;
316                                        case "below-centered":
317                                                push("BM", "TM");
318                                                break;
319                                        case "after":
320                                                ltr = !ltr;
321                                                // fall through
322                                        case "before":
323                                                push(ltr ? "ML" : "MR", ltr ? "MR" : "ML");
324                                                break;
325                                        case "below-alt":
326                                                ltr = !ltr;
327                                                // fall through
328                                        case "below":
329                                                // first try to align left borders, next try to align right borders (or reverse for RTL mode)
330                                                push(ltr ? "BL" : "BR", ltr ? "TL" : "TR");
331                                                push(ltr ? "BR" : "BL", ltr ? "TR" : "TL");
332                                                break;
333                                        case "above-alt":
334                                                ltr = !ltr;
335                                                // fall through
336                                        case "above":
337                                                // first try to align left borders, next try to align right borders (or reverse for RTL mode)
338                                                push(ltr ? "TL" : "TR", ltr ? "BL" : "BR");
339                                                push(ltr ? "TR" : "TL", ltr ? "BR" : "BL");
340                                                break;
341                                        default:
342                                                // To assist dijit/_base/place, accept arguments of type {aroundCorner: "BL", corner: "TL"}.
343                                                // Not meant to be used directly.
344                                                push(pos.aroundCorner, pos.corner);
345                                }
346                        });
347
348                        var position = _place(node, choices, layoutNode, {w: width, h: height});
349                        position.aroundNodePos = aroundNodePos;
350
351                        return position;
352                }
353        });
354});
Note: See TracBrowser for help on using the repository browser.