source: Dev/trunk/src/client/dijit/place.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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