|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('widget-position-align', function (Y, NAME) { |
|
9 |
|
10 /** |
|
11 Provides extended/advanced XY positioning support for Widgets, through an |
|
12 extension. |
|
13 |
|
14 It builds on top of the `widget-position` module, to provide alignment and |
|
15 centering support. Future releases aim to add constrained and fixed positioning |
|
16 support. |
|
17 |
|
18 @module widget-position-align |
|
19 **/ |
|
20 var Lang = Y.Lang, |
|
21 |
|
22 ALIGN = 'align', |
|
23 ALIGN_ON = 'alignOn', |
|
24 |
|
25 VISIBLE = 'visible', |
|
26 BOUNDING_BOX = 'boundingBox', |
|
27 |
|
28 OFFSET_WIDTH = 'offsetWidth', |
|
29 OFFSET_HEIGHT = 'offsetHeight', |
|
30 REGION = 'region', |
|
31 VIEWPORT_REGION = 'viewportRegion'; |
|
32 |
|
33 /** |
|
34 Widget extension, which can be used to add extended XY positioning support to |
|
35 the base Widget class, through the `Base.create` method. |
|
36 |
|
37 **Note:** This extension requires that the `WidgetPosition` extension be added |
|
38 to the Widget (before `WidgetPositionAlign`, if part of the same extension list |
|
39 passed to `Base.build`). |
|
40 |
|
41 @class WidgetPositionAlign |
|
42 @param {Object} config User configuration object. |
|
43 @constructor |
|
44 **/ |
|
45 function PositionAlign (config) { |
|
46 if ( ! this._posNode) { |
|
47 Y.error('WidgetPosition needs to be added to the Widget, ' + |
|
48 'before WidgetPositionAlign is added'); |
|
49 } |
|
50 |
|
51 Y.after(this._bindUIPosAlign, this, 'bindUI'); |
|
52 Y.after(this._syncUIPosAlign, this, 'syncUI'); |
|
53 } |
|
54 |
|
55 PositionAlign.ATTRS = { |
|
56 |
|
57 /** |
|
58 The alignment configuration for this widget. |
|
59 |
|
60 The `align` attribute is used to align a reference point on the widget, with |
|
61 the reference point on another `Node`, or the viewport. The object which |
|
62 `align` expects has the following properties: |
|
63 |
|
64 * __`node`__: The `Node` to which the widget is to be aligned. If set to |
|
65 `null`, or not provided, the widget is aligned to the viewport. |
|
66 |
|
67 * __`points`__: A two element Array, defining the two points on the widget |
|
68 and `Node`/viewport which are to be aligned. The first element is the |
|
69 point on the widget, and the second element is the point on the |
|
70 `Node`/viewport. Supported alignment points are defined as static |
|
71 properties on `WidgetPositionAlign`. |
|
72 |
|
73 @example Aligns the top-right corner of the widget with the top-left corner |
|
74 of the viewport: |
|
75 |
|
76 myWidget.set('align', { |
|
77 points: [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.TL] |
|
78 }); |
|
79 |
|
80 @attribute align |
|
81 @type Object |
|
82 @default null |
|
83 **/ |
|
84 align: { |
|
85 value: null |
|
86 }, |
|
87 |
|
88 /** |
|
89 A convenience Attribute, which can be used as a shortcut for the `align` |
|
90 Attribute. |
|
91 |
|
92 If set to `true`, the widget is centered in the viewport. If set to a `Node` |
|
93 reference or valid selector String, the widget will be centered within the |
|
94 `Node`. If set to `false`, no center positioning is applied. |
|
95 |
|
96 @attribute centered |
|
97 @type Boolean|Node |
|
98 @default false |
|
99 **/ |
|
100 centered: { |
|
101 setter : '_setAlignCenter', |
|
102 lazyAdd:false, |
|
103 value :false |
|
104 }, |
|
105 |
|
106 /** |
|
107 An Array of Objects corresponding to the `Node`s and events that will cause |
|
108 the alignment of this widget to be synced to the DOM. |
|
109 |
|
110 The `alignOn` Attribute is expected to be an Array of Objects with the |
|
111 following properties: |
|
112 |
|
113 * __`eventName`__: The String event name to listen for. |
|
114 |
|
115 * __`node`__: The optional `Node` that will fire the event, it can be a |
|
116 `Node` reference or a selector String. This will default to the widget's |
|
117 `boundingBox`. |
|
118 |
|
119 @example Sync this widget's alignment on window resize: |
|
120 |
|
121 myWidget.set('alignOn', [ |
|
122 { |
|
123 node : Y.one('win'), |
|
124 eventName: 'resize' |
|
125 } |
|
126 ]); |
|
127 |
|
128 @attribute alignOn |
|
129 @type Array |
|
130 @default [] |
|
131 **/ |
|
132 alignOn: { |
|
133 value : [], |
|
134 validator: Y.Lang.isArray |
|
135 } |
|
136 }; |
|
137 |
|
138 /** |
|
139 Constant used to specify the top-left corner for alignment |
|
140 |
|
141 @property TL |
|
142 @type String |
|
143 @value 'tl' |
|
144 @static |
|
145 **/ |
|
146 PositionAlign.TL = 'tl'; |
|
147 |
|
148 /** |
|
149 Constant used to specify the top-right corner for alignment |
|
150 |
|
151 @property TR |
|
152 @type String |
|
153 @value 'tr' |
|
154 @static |
|
155 **/ |
|
156 PositionAlign.TR = 'tr'; |
|
157 |
|
158 /** |
|
159 Constant used to specify the bottom-left corner for alignment |
|
160 |
|
161 @property BL |
|
162 @type String |
|
163 @value 'bl' |
|
164 @static |
|
165 **/ |
|
166 PositionAlign.BL = 'bl'; |
|
167 |
|
168 /** |
|
169 Constant used to specify the bottom-right corner for alignment |
|
170 |
|
171 @property BR |
|
172 @type String |
|
173 @value 'br' |
|
174 @static |
|
175 **/ |
|
176 PositionAlign.BR = 'br'; |
|
177 |
|
178 /** |
|
179 Constant used to specify the top edge-center point for alignment |
|
180 |
|
181 @property TC |
|
182 @type String |
|
183 @value 'tc' |
|
184 @static |
|
185 **/ |
|
186 PositionAlign.TC = 'tc'; |
|
187 |
|
188 /** |
|
189 Constant used to specify the right edge, center point for alignment |
|
190 |
|
191 @property RC |
|
192 @type String |
|
193 @value 'rc' |
|
194 @static |
|
195 **/ |
|
196 PositionAlign.RC = 'rc'; |
|
197 |
|
198 /** |
|
199 Constant used to specify the bottom edge, center point for alignment |
|
200 |
|
201 @property BC |
|
202 @type String |
|
203 @value 'bc' |
|
204 @static |
|
205 **/ |
|
206 PositionAlign.BC = 'bc'; |
|
207 |
|
208 /** |
|
209 Constant used to specify the left edge, center point for alignment |
|
210 |
|
211 @property LC |
|
212 @type String |
|
213 @value 'lc' |
|
214 @static |
|
215 **/ |
|
216 PositionAlign.LC = 'lc'; |
|
217 |
|
218 /** |
|
219 Constant used to specify the center of widget/node/viewport for alignment |
|
220 |
|
221 @property CC |
|
222 @type String |
|
223 @value 'cc' |
|
224 @static |
|
225 */ |
|
226 PositionAlign.CC = 'cc'; |
|
227 |
|
228 PositionAlign.prototype = { |
|
229 // -- Protected Properties ------------------------------------------------- |
|
230 |
|
231 /** |
|
232 Holds the alignment-syncing event handles. |
|
233 |
|
234 @property _posAlignUIHandles |
|
235 @type Array |
|
236 @default null |
|
237 @protected |
|
238 **/ |
|
239 _posAlignUIHandles: null, |
|
240 |
|
241 // -- Lifecycle Methods ---------------------------------------------------- |
|
242 |
|
243 destructor: function () { |
|
244 this._detachPosAlignUIHandles(); |
|
245 }, |
|
246 |
|
247 /** |
|
248 Bind event listeners responsible for updating the UI state in response to |
|
249 the widget's position-align related state changes. |
|
250 |
|
251 This method is invoked after `bindUI` has been invoked for the `Widget` |
|
252 class using the AOP infrastructure. |
|
253 |
|
254 @method _bindUIPosAlign |
|
255 @protected |
|
256 **/ |
|
257 _bindUIPosAlign: function () { |
|
258 this.after('alignChange', this._afterAlignChange); |
|
259 this.after('alignOnChange', this._afterAlignOnChange); |
|
260 this.after('visibleChange', this._syncUIPosAlign); |
|
261 }, |
|
262 |
|
263 /** |
|
264 Synchronizes the current `align` Attribute value to the DOM. |
|
265 |
|
266 This method is invoked after `syncUI` has been invoked for the `Widget` |
|
267 class using the AOP infrastructure. |
|
268 |
|
269 @method _syncUIPosAlign |
|
270 @protected |
|
271 **/ |
|
272 _syncUIPosAlign: function () { |
|
273 var align = this.get(ALIGN); |
|
274 |
|
275 this._uiSetVisiblePosAlign(this.get(VISIBLE)); |
|
276 |
|
277 if (align) { |
|
278 this._uiSetAlign(align.node, align.points); |
|
279 } |
|
280 }, |
|
281 |
|
282 // -- Public Methods ------------------------------------------------------- |
|
283 |
|
284 /** |
|
285 Aligns this widget to the provided `Node` (or viewport) using the provided |
|
286 points. This method can be invoked with no arguments which will cause the |
|
287 widget's current `align` Attribute value to be synced to the DOM. |
|
288 |
|
289 @example Aligning to the top-left corner of the `<body>`: |
|
290 |
|
291 myWidget.align('body', |
|
292 [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.TR]); |
|
293 |
|
294 @method align |
|
295 @param {Node|String|null} [node] A reference (or selector String) for the |
|
296 `Node` which with the widget is to be aligned. If null is passed in, the |
|
297 widget will be aligned with the viewport. |
|
298 @param {Array[2]} [points] A two item array specifying the points on the |
|
299 widget and `Node`/viewport which will to be aligned. The first entry is |
|
300 the point on the widget, and the second entry is the point on the |
|
301 `Node`/viewport. Valid point references are defined as static constants on |
|
302 the `WidgetPositionAlign` extension. |
|
303 @chainable |
|
304 **/ |
|
305 align: function (node, points) { |
|
306 if (arguments.length) { |
|
307 // Set the `align` Attribute. |
|
308 this.set(ALIGN, { |
|
309 node : node, |
|
310 points: points |
|
311 }); |
|
312 } else { |
|
313 // Sync the current `align` Attribute value to the DOM. |
|
314 this._syncUIPosAlign(); |
|
315 } |
|
316 |
|
317 return this; |
|
318 }, |
|
319 |
|
320 /** |
|
321 Centers the widget in the viewport, or if a `Node` is passed in, it will |
|
322 be centered to that `Node`. |
|
323 |
|
324 @method centered |
|
325 @param {Node|String} [node] A `Node` reference or selector String defining |
|
326 the `Node` which the widget should be centered. If a `Node` is not passed |
|
327 in, then the widget will be centered to the viewport. |
|
328 @chainable |
|
329 **/ |
|
330 centered: function (node) { |
|
331 return this.align(node, [PositionAlign.CC, PositionAlign.CC]); |
|
332 }, |
|
333 |
|
334 // -- Protected Methods ---------------------------------------------------- |
|
335 |
|
336 /** |
|
337 Default setter for `center` Attribute changes. Sets up the appropriate |
|
338 value, and passes it through the to the align attribute. |
|
339 |
|
340 @method _setAlignCenter |
|
341 @param {Boolean|Node} val The Attribute value being set. |
|
342 @return {Boolean|Node} the value passed in. |
|
343 @protected |
|
344 **/ |
|
345 _setAlignCenter: function (val) { |
|
346 if (val) { |
|
347 this.set(ALIGN, { |
|
348 node : val === true ? null : val, |
|
349 points: [PositionAlign.CC, PositionAlign.CC] |
|
350 }); |
|
351 } |
|
352 |
|
353 return val; |
|
354 }, |
|
355 |
|
356 /** |
|
357 Updates the UI to reflect the `align` value passed in. |
|
358 |
|
359 **Note:** See the `align` Attribute documentation, for the Object structure |
|
360 expected. |
|
361 |
|
362 @method _uiSetAlign |
|
363 @param {Node|String|null} [node] The node to align to, or null to indicate |
|
364 the viewport. |
|
365 @param {Array} points The alignment points. |
|
366 @protected |
|
367 **/ |
|
368 _uiSetAlign: function (node, points) { |
|
369 if ( ! Lang.isArray(points) || points.length !== 2) { |
|
370 Y.error('align: Invalid Points Arguments'); |
|
371 return; |
|
372 } |
|
373 |
|
374 var nodeRegion = this._getRegion(node), |
|
375 widgetPoint, nodePoint, xy; |
|
376 |
|
377 if ( ! nodeRegion) { |
|
378 // No-op, nothing to align to. |
|
379 return; |
|
380 } |
|
381 |
|
382 widgetPoint = points[0]; |
|
383 nodePoint = points[1]; |
|
384 |
|
385 // TODO: Optimize KWeight - Would lookup table help? |
|
386 switch (nodePoint) { |
|
387 case PositionAlign.TL: |
|
388 xy = [nodeRegion.left, nodeRegion.top]; |
|
389 break; |
|
390 |
|
391 case PositionAlign.TR: |
|
392 xy = [nodeRegion.right, nodeRegion.top]; |
|
393 break; |
|
394 |
|
395 case PositionAlign.BL: |
|
396 xy = [nodeRegion.left, nodeRegion.bottom]; |
|
397 break; |
|
398 |
|
399 case PositionAlign.BR: |
|
400 xy = [nodeRegion.right, nodeRegion.bottom]; |
|
401 break; |
|
402 |
|
403 case PositionAlign.TC: |
|
404 xy = [ |
|
405 nodeRegion.left + Math.floor(nodeRegion.width / 2), |
|
406 nodeRegion.top |
|
407 ]; |
|
408 break; |
|
409 |
|
410 case PositionAlign.BC: |
|
411 xy = [ |
|
412 nodeRegion.left + Math.floor(nodeRegion.width / 2), |
|
413 nodeRegion.bottom |
|
414 ]; |
|
415 break; |
|
416 |
|
417 case PositionAlign.LC: |
|
418 xy = [ |
|
419 nodeRegion.left, |
|
420 nodeRegion.top + Math.floor(nodeRegion.height / 2) |
|
421 ]; |
|
422 break; |
|
423 |
|
424 case PositionAlign.RC: |
|
425 xy = [ |
|
426 nodeRegion.right, |
|
427 nodeRegion.top + Math.floor(nodeRegion.height / 2) |
|
428 ]; |
|
429 break; |
|
430 |
|
431 case PositionAlign.CC: |
|
432 xy = [ |
|
433 nodeRegion.left + Math.floor(nodeRegion.width / 2), |
|
434 nodeRegion.top + Math.floor(nodeRegion.height / 2) |
|
435 ]; |
|
436 break; |
|
437 |
|
438 default: |
|
439 break; |
|
440 |
|
441 } |
|
442 |
|
443 if (xy) { |
|
444 this._doAlign(widgetPoint, xy[0], xy[1]); |
|
445 } |
|
446 }, |
|
447 |
|
448 /** |
|
449 Attaches or detaches alignment-syncing event handlers based on the widget's |
|
450 `visible` Attribute state. |
|
451 |
|
452 @method _uiSetVisiblePosAlign |
|
453 @param {Boolean} visible The current value of the widget's `visible` |
|
454 Attribute. |
|
455 @protected |
|
456 **/ |
|
457 _uiSetVisiblePosAlign: function (visible) { |
|
458 if (visible) { |
|
459 this._attachPosAlignUIHandles(); |
|
460 } else { |
|
461 this._detachPosAlignUIHandles(); |
|
462 } |
|
463 }, |
|
464 |
|
465 /** |
|
466 Attaches the alignment-syncing event handlers. |
|
467 |
|
468 @method _attachPosAlignUIHandles |
|
469 @protected |
|
470 **/ |
|
471 _attachPosAlignUIHandles: function () { |
|
472 if (this._posAlignUIHandles) { |
|
473 // No-op if we have already setup the event handlers. |
|
474 return; |
|
475 } |
|
476 |
|
477 var bb = this.get(BOUNDING_BOX), |
|
478 syncAlign = Y.bind(this._syncUIPosAlign, this), |
|
479 handles = []; |
|
480 |
|
481 Y.Array.each(this.get(ALIGN_ON), function (o) { |
|
482 var event = o.eventName, |
|
483 node = Y.one(o.node) || bb; |
|
484 |
|
485 if (event) { |
|
486 handles.push(node.on(event, syncAlign)); |
|
487 } |
|
488 }); |
|
489 |
|
490 this._posAlignUIHandles = handles; |
|
491 }, |
|
492 |
|
493 /** |
|
494 Detaches the alignment-syncing event handlers. |
|
495 |
|
496 @method _detachPosAlignUIHandles |
|
497 @protected |
|
498 **/ |
|
499 _detachPosAlignUIHandles: function () { |
|
500 var handles = this._posAlignUIHandles; |
|
501 if (handles) { |
|
502 new Y.EventHandle(handles).detach(); |
|
503 this._posAlignUIHandles = null; |
|
504 } |
|
505 }, |
|
506 |
|
507 // -- Private Methods ------------------------------------------------------ |
|
508 |
|
509 /** |
|
510 Helper method, used to align the given point on the widget, with the XY page |
|
511 coordinates provided. |
|
512 |
|
513 @method _doAlign |
|
514 @param {String} widgetPoint Supported point constant |
|
515 (e.g. WidgetPositionAlign.TL) |
|
516 @param {Number} x X page coordinate to align to. |
|
517 @param {Number} y Y page coordinate to align to. |
|
518 @private |
|
519 **/ |
|
520 _doAlign: function (widgetPoint, x, y) { |
|
521 var widgetNode = this._posNode, |
|
522 xy; |
|
523 |
|
524 switch (widgetPoint) { |
|
525 case PositionAlign.TL: |
|
526 xy = [x, y]; |
|
527 break; |
|
528 |
|
529 case PositionAlign.TR: |
|
530 xy = [ |
|
531 x - widgetNode.get(OFFSET_WIDTH), |
|
532 y |
|
533 ]; |
|
534 break; |
|
535 |
|
536 case PositionAlign.BL: |
|
537 xy = [ |
|
538 x, |
|
539 y - widgetNode.get(OFFSET_HEIGHT) |
|
540 ]; |
|
541 break; |
|
542 |
|
543 case PositionAlign.BR: |
|
544 xy = [ |
|
545 x - widgetNode.get(OFFSET_WIDTH), |
|
546 y - widgetNode.get(OFFSET_HEIGHT) |
|
547 ]; |
|
548 break; |
|
549 |
|
550 case PositionAlign.TC: |
|
551 xy = [ |
|
552 x - (widgetNode.get(OFFSET_WIDTH) / 2), |
|
553 y |
|
554 ]; |
|
555 break; |
|
556 |
|
557 case PositionAlign.BC: |
|
558 xy = [ |
|
559 x - (widgetNode.get(OFFSET_WIDTH) / 2), |
|
560 y - widgetNode.get(OFFSET_HEIGHT) |
|
561 ]; |
|
562 break; |
|
563 |
|
564 case PositionAlign.LC: |
|
565 xy = [ |
|
566 x, |
|
567 y - (widgetNode.get(OFFSET_HEIGHT) / 2) |
|
568 ]; |
|
569 break; |
|
570 |
|
571 case PositionAlign.RC: |
|
572 xy = [ |
|
573 x - widgetNode.get(OFFSET_WIDTH), |
|
574 y - (widgetNode.get(OFFSET_HEIGHT) / 2) |
|
575 ]; |
|
576 break; |
|
577 |
|
578 case PositionAlign.CC: |
|
579 xy = [ |
|
580 x - (widgetNode.get(OFFSET_WIDTH) / 2), |
|
581 y - (widgetNode.get(OFFSET_HEIGHT) / 2) |
|
582 ]; |
|
583 break; |
|
584 |
|
585 default: |
|
586 break; |
|
587 |
|
588 } |
|
589 |
|
590 if (xy) { |
|
591 this.move(xy); |
|
592 } |
|
593 }, |
|
594 |
|
595 /** |
|
596 Returns the region of the passed-in `Node`, or the viewport region if |
|
597 calling with passing in a `Node`. |
|
598 |
|
599 @method _getRegion |
|
600 @param {Node} [node] The node to get the region of. |
|
601 @return {Object} The node's region. |
|
602 @private |
|
603 **/ |
|
604 _getRegion: function (node) { |
|
605 var nodeRegion; |
|
606 |
|
607 if ( ! node) { |
|
608 nodeRegion = this._posNode.get(VIEWPORT_REGION); |
|
609 } else { |
|
610 node = Y.Node.one(node); |
|
611 if (node) { |
|
612 nodeRegion = node.get(REGION); |
|
613 } |
|
614 } |
|
615 |
|
616 return nodeRegion; |
|
617 }, |
|
618 |
|
619 // -- Protected Event Handlers --------------------------------------------- |
|
620 |
|
621 /** |
|
622 Handles `alignChange` events by updating the UI in response to `align` |
|
623 Attribute changes. |
|
624 |
|
625 @method _afterAlignChange |
|
626 @param {EventFacade} e |
|
627 @protected |
|
628 **/ |
|
629 _afterAlignChange: function (e) { |
|
630 var align = e.newVal; |
|
631 if (align) { |
|
632 this._uiSetAlign(align.node, align.points); |
|
633 } |
|
634 }, |
|
635 |
|
636 /** |
|
637 Handles `alignOnChange` events by updating the alignment-syncing event |
|
638 handlers. |
|
639 |
|
640 @method _afterAlignOnChange |
|
641 @param {EventFacade} e |
|
642 @protected |
|
643 **/ |
|
644 _afterAlignOnChange: function(e) { |
|
645 this._detachPosAlignUIHandles(); |
|
646 |
|
647 if (this.get(VISIBLE)) { |
|
648 this._attachPosAlignUIHandles(); |
|
649 } |
|
650 } |
|
651 }; |
|
652 |
|
653 Y.WidgetPositionAlign = PositionAlign; |
|
654 |
|
655 |
|
656 }, '3.10.3', {"requires": ["widget-position"]}); |