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