Changed var Foo = function() to function Foo(), added missing JSDoc
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLab / WCF / UI / Alignment.js
1 "use strict";
2
3 /**
4 * Utility class to align elements relatively to another.
5 *
6 * @author Alexander Ebert
7 * @copyright 2001-2015 WoltLab GmbH
8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
9 * @module WoltLab/WCF/UI/Alignment
10 */
11 define(['Core', 'DOM/Util'], function(Core, DOMUtil) {
12 /**
13 * @constructor
14 */
15 function UIAlignment() {};
16 UIAlignment.prototype = {
17 /**
18 * Sets the alignment for target element relatively to the reference element.
19 *
20 * @param {Element} el target element
21 * @param {Element} ref reference element
22 * @param {object<string, *>} options list of options to alter the behavior
23 */
24 set: function(el, ref, options) {
25 options = Core.extend({
26 // offset to reference element
27 verticalOffset: 7,
28
29 // align the pointer element, expects .pointer as a direct child of given element
30 pointer: false,
31
32 // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
33 pointerClassNames: [],
34
35 // alternate element used to calculate dimensions
36 refDimensionsElement: null,
37
38 // preferred alignment, possible values: left/right and top/bottom
39 horizontal: 'left',
40 vertical: 'bottom',
41
42 // allow flipping over axis, possible values: both, horizontal, vertical and none
43 allowFlip: 'both'
44 }, options);
45
46 if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== 2) options.pointerClassNames = [];
47 if (options.horizontal !== 'right') options.horizontal = 'left';
48 if (options.vertical !== 'bottom') options.horizontal = 'top';
49 if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1) options.allowFlip = 'both';
50
51 // place element in the upper left corner to prevent calculation issues due to possible scrollbars
52 DOMUtil.setStyles(el, {
53 bottom: 'auto',
54 left: '0px',
55 right: 'auto',
56 top: '0px'
57 });
58
59 var elDimensions = DOMUtil.outerDimensions(el);
60 var refDimensions = DOMUtil.outerDimensions((options.refDimensionsElement instanceof Element ? options.refDimensionsElement : ref));
61 var refOffsets = DOMUtil.offset(ref);
62 var windowHeight = window.innerHeight;
63 var windowWidth = window.innerWidth;
64
65 // in rtl languages we simply swap the value for 'horizontal'
66 if (WCF.Language.get('wcf.global.pageDirection') === 'rtl') {
67 options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
68 }
69
70 var horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
71 if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
72 var horizontalFlipped = this._tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
73 // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
74 if (horizontalFlipped.result) {
75 horizontal = horizontalFlipped;
76 }
77 }
78
79 var left = horizontal.left;
80 var right = horizontal.right;
81
82 var vertical = this._tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
83 if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
84 var verticalFlipped = this._tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
85 // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
86 if (verticalFlipped.result) {
87 vertical = verticalFlipped;
88 }
89 }
90
91 var bottom = vertical.bottom;
92 var top = vertical.top;
93
94 // set pointer position
95 if (options.pointer) {
96 //var pointer = null;
97 // TODO: implement pointer support, e.g. for interactive dropdowns
98 console.debug("TODO");
99 }
100 else if (options.pointerClassNames.length === 2) {
101 var pointerRight = 0;
102 var pointerBottom = 1;
103
104 el.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerBottom]);
105 el.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerRight]);
106 }
107
108 DOMUtil.setStyles(el, {
109 bottom: bottom + (bottom !== 'auto' ? 'px' : ''),
110 left: left + (left !== 'auto' ? 'px' : ''),
111 right: right + (right !== 'auto' ? 'px' : ''),
112 top: top + (top !== 'auto' ? 'px' : '')
113 });
114 },
115
116 /**
117 * Calculates left/right position and verifys if the element would be still within the page's boundaries.
118 *
119 * @param {string} align align to this side of the reference element
120 * @param {object<string, integer>} elDimensions element dimensions
121 * @param {object<string, integer>} refDimensions reference element dimensions
122 * @param {object<string, integer>} refOffsets position of reference element relative to the document
123 * @param {integer} windowWidth window width
124 * @returns {object<string, *>} calculation results
125 */
126 _tryAlignmentHorizontal: function(align, elDimensions, refDimensions, refOffsets, windowWidth) {
127 var left = 'auto';
128 var right = 'auto';
129 var result = true;
130
131 if (align === 'left') {
132 left = refOffsets.left;
133 if (left + elDimensions.width > windowWidth) {
134 result = false;
135 }
136 }
137 else {
138 right = refOffsets.left + refDimensions.width;
139 if (right - elDimensions.width < 0) {
140 result = false;
141 }
142 }
143
144 return {
145 left: left,
146 right: right,
147 result: result
148 };
149 },
150
151 /**
152 * Calculates top/bottom position and verifys if the element would be still within the page's boundaries.
153 *
154 * @param {string} align align to this side of the reference element
155 * @param {object<string, integer>} elDimensions element dimensions
156 * @param {object<string, integer>} refDimensions reference element dimensions
157 * @param {object<string, integer>} refOffsets position of reference element relative to the document
158 * @param {integer} windowHeight window height
159 * @param {integer} verticalOffset desired gap between element and reference element
160 * @returns {object<string, *>} calculation results
161 */
162 _tryAlignmentVertical: function(align, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset) {
163 var bottom = 'auto';
164 var top = 'auto';
165 var result = true;
166
167 if (align === 'top') {
168 bottom = refOffsets.top + verticalOffset;
169 if (bottom - elDimensions.height < 0) {
170 result = false;
171 }
172 }
173 else {
174 top = refOffsets.top + refDimensions.height + verticalOffset;
175 if (top + elDimensions.height > windowHeight) {
176 result = false;
177 }
178 }
179
180 return {
181 bottom: bottom,
182 top: top,
183 result: result
184 };
185 }
186 };
187
188 return new UIAlignment();
189 });