Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / webui / resources / js / cr / ui / position_util.js
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * @fileoverview This file provides utility functions for position popups.
7  */
8
9 cr.exportPath('cr.ui');
10
11 /**
12  * Type def for rects as returned by getBoundingClientRect.
13  * @typedef {{left: number, top: number, width: number, height: number,
14  *            right: number, bottom: number}}
15  */
16 cr.ui.Rect;
17
18 /**
19  * Enum for defining how to anchor a popup to an anchor element.
20  * @enum {number}
21  */
22 cr.ui.AnchorType = {
23   /**
24    * The popup's right edge is aligned with the left edge of the anchor.
25    * The popup's top edge is aligned with the top edge of the anchor.
26    */
27   BEFORE: 1,  // p: right, a: left, p: top, a: top
28
29   /**
30    * The popop's left edge is aligned with the right edge of the anchor.
31    * The popup's top edge is aligned with the top edge of the anchor.
32    */
33   AFTER: 2,  // p: left a: right, p: top, a: top
34
35   /**
36    * The popop's bottom edge is aligned with the top edge of the anchor.
37    * The popup's left edge is aligned with the left edge of the anchor.
38    */
39   ABOVE: 3,  // p: bottom, a: top, p: left, a: left
40
41   /**
42    * The popop's top edge is aligned with the bottom edge of the anchor.
43    * The popup's left edge is aligned with the left edge of the anchor.
44    */
45   BELOW: 4  // p: top, a: bottom, p: left, a: left
46 };
47
48 cr.define('cr.ui', function() {
49   /** @const */
50   var AnchorType = cr.ui.AnchorType;
51
52   /**
53    * Helper function for positionPopupAroundElement and positionPopupAroundRect.
54    * @param {!cr.ui.Rect} anchorRect The rect for the anchor.
55    * @param {!HTMLElement} popupElement The element used for the popup.
56    * @param {cr.ui.AnchorType} type The type of anchoring to do.
57    * @param {boolean=} opt_invertLeftRight Whether to invert the right/left
58    *     alignment.
59    */
60   function positionPopupAroundRect(anchorRect, popupElement, type,
61                                    opt_invertLeftRight) {
62     var popupRect = popupElement.getBoundingClientRect();
63     var availRect;
64     var ownerDoc = popupElement.ownerDocument;
65     var cs = ownerDoc.defaultView.getComputedStyle(popupElement);
66     var docElement = ownerDoc.documentElement;
67
68     if (cs.position == 'fixed') {
69       // For 'fixed' positioned popups, the available rectangle should be based
70       // on the viewport rather than the document.
71       availRect = {
72         height: docElement.clientHeight,
73         width: docElement.clientWidth,
74         top: 0,
75         bottom: docElement.clientHeight,
76         left: 0,
77         right: docElement.clientWidth
78       };
79     } else {
80       availRect = popupElement.offsetParent.getBoundingClientRect();
81     }
82
83     if (cs.direction == 'rtl')
84       opt_invertLeftRight = !opt_invertLeftRight;
85
86     // Flip BEFORE, AFTER based on alignment.
87     if (opt_invertLeftRight) {
88       if (type == AnchorType.BEFORE)
89         type = AnchorType.AFTER;
90       else if (type == AnchorType.AFTER)
91         type = AnchorType.BEFORE;
92     }
93
94     // Flip type based on available size
95     switch (type) {
96       case AnchorType.BELOW:
97         if (anchorRect.bottom + popupRect.height > availRect.height &&
98             popupRect.height <= anchorRect.top) {
99           type = AnchorType.ABOVE;
100         }
101         break;
102       case AnchorType.ABOVE:
103         if (popupRect.height > anchorRect.top &&
104             anchorRect.bottom + popupRect.height <= availRect.height) {
105           type = AnchorType.BELOW;
106         }
107         break;
108       case AnchorType.AFTER:
109         if (anchorRect.right + popupRect.width > availRect.width &&
110             popupRect.width <= anchorRect.left) {
111           type = AnchorType.BEFORE;
112         }
113         break;
114       case AnchorType.BEFORE:
115         if (popupRect.width > anchorRect.left &&
116             anchorRect.right + popupRect.width <= availRect.width) {
117           type = AnchorType.AFTER;
118         }
119         break;
120     }
121     // flipping done
122
123     var style = popupElement.style;
124     // Reset all directions.
125     style.left = style.right = style.top = style.bottom = 'auto';
126
127     // Primary direction
128     switch (type) {
129       case AnchorType.BELOW:
130         if (anchorRect.bottom + popupRect.height <= availRect.height)
131           style.top = anchorRect.bottom + 'px';
132         else
133           style.bottom = '0';
134         break;
135       case AnchorType.ABOVE:
136         if (availRect.height - anchorRect.top >= 0)
137           style.bottom = availRect.height - anchorRect.top + 'px';
138         else
139           style.top = '0';
140         break;
141       case AnchorType.AFTER:
142         if (anchorRect.right + popupRect.width <= availRect.width)
143           style.left = anchorRect.right + 'px';
144         else
145           style.right = '0';
146         break;
147       case AnchorType.BEFORE:
148         if (availRect.width - anchorRect.left >= 0)
149           style.right = availRect.width - anchorRect.left + 'px';
150         else
151           style.left = '0';
152         break;
153     }
154
155     // Secondary direction
156     switch (type) {
157       case AnchorType.BELOW:
158       case AnchorType.ABOVE:
159         if (opt_invertLeftRight) {
160           // align right edges
161           if (anchorRect.right - popupRect.width >= 0) {
162             style.right = availRect.width - anchorRect.right + 'px';
163
164           // align left edges
165           } else if (anchorRect.left + popupRect.width <= availRect.width) {
166             style.left = anchorRect.left + 'px';
167
168           // not enough room on either side
169           } else {
170             style.right = '0';
171           }
172         } else {
173           // align left edges
174           if (anchorRect.left + popupRect.width <= availRect.width) {
175             style.left = anchorRect.left + 'px';
176
177           // align right edges
178           } else if (anchorRect.right - popupRect.width >= 0) {
179             style.right = availRect.width - anchorRect.right + 'px';
180
181           // not enough room on either side
182           } else {
183             style.left = '0';
184           }
185         }
186         break;
187
188       case AnchorType.AFTER:
189       case AnchorType.BEFORE:
190         // align top edges
191         if (anchorRect.top + popupRect.height <= availRect.height) {
192           style.top = anchorRect.top + 'px';
193
194         // align bottom edges
195         } else if (anchorRect.bottom - popupRect.height >= 0) {
196           style.bottom = availRect.height - anchorRect.bottom + 'px';
197
198           // not enough room on either side
199         } else {
200           style.top = '0';
201         }
202         break;
203     }
204   }
205
206   /**
207    * Positions a popup element relative to an anchor element. The popup element
208    * should have position set to absolute and it should be a child of the body
209    * element.
210    * @param {!HTMLElement} anchorElement The element that the popup is anchored
211    *     to.
212    * @param {!HTMLElement} popupElement The popup element we are positioning.
213    * @param {cr.ui.AnchorType} type The type of anchoring we want.
214    * @param {boolean=} opt_invertLeftRight Whether to invert the right/left
215    *     alignment.
216    */
217   function positionPopupAroundElement(anchorElement, popupElement, type,
218                                       opt_invertLeftRight) {
219     var anchorRect = anchorElement.getBoundingClientRect();
220     positionPopupAroundRect(anchorRect, popupElement, type,
221                             !!opt_invertLeftRight);
222   }
223
224   /**
225    * Positions a popup around a point.
226    * @param {number} x The client x position.
227    * @param {number} y The client y position.
228    * @param {!HTMLElement} popupElement The popup element we are positioning.
229    */
230   function positionPopupAtPoint(x, y, popupElement) {
231     var rect = {
232       left: x,
233       top: y,
234       width: 0,
235       height: 0,
236       right: x,
237       bottom: y
238     };
239     positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
240   }
241
242   // Export
243   return {
244     positionPopupAroundElement: positionPopupAroundElement,
245     positionPopupAtPoint: positionPopupAtPoint
246   };
247 });