1 /**
  2  * @fileOverview Component.UIBase.Align
  3  * @author yiminghe@gmail.com, qiaohua@taobao.com
  4  */
  5 KISSY.add('component/uibase/align', function (S, UA, DOM, Node) {
  6 
  7     var win = S.Env.host;
  8 
  9     // var ieMode = document.documentMode || UA.ie;
 10 
 11     /*
 12      inspired by closure library by Google
 13      see http://yiminghe.iteye.com/blog/1124720
 14      */
 15 
 16     /**
 17      * 得到会导致元素显示不全的祖先元素
 18      */
 19     function getOffsetParent(element) {
 20         // ie 这个也不是完全可行
 21         /**
 22          <div style="width: 50px;height: 100px;overflow: hidden">
 23          <div style="width: 50px;height: 100px;position: relative;" id="d6">
 24          元素 6 高 100px 宽 50px<br/>
 25          </div>
 26          </div>
 27          **/
 28         // element.offsetParent does the right thing in ie7 and below. Return parent with layout!
 29         //  In other browsers it only includes elements with position absolute, relative or
 30         // fixed, not elements with overflow set to auto or scroll.
 31 //        if (UA.ie && ieMode < 8) {
 32 //            return element.offsetParent;
 33 //        }
 34         // 统一的 offsetParent 方法
 35         var doc = element.ownerDocument,
 36             body = doc.body,
 37             parent,
 38             positionStyle = DOM.css(element, 'position'),
 39             skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
 40 
 41         if (!skipStatic) {
 42             return element.nodeName.toLowerCase() == 'html' ? null : element.parentNode;
 43         }
 44 
 45         for (parent = element.parentNode; parent && parent != body; parent = parent.parentNode) {
 46             positionStyle = DOM.css(parent, 'position');
 47             if (positionStyle != "static") {
 48                 return parent;
 49             }
 50         }
 51         return null;
 52     }
 53 
 54     /**
 55      * 获得元素的显示部分的区域
 56      */
 57     function getVisibleRectForElement(element) {
 58         var visibleRect = {
 59                 left:0,
 60                 right:Infinity,
 61                 top:0,
 62                 bottom:Infinity
 63             },
 64             el,
 65             scrollX,
 66             scrollY,
 67             winSize,
 68             doc = element.ownerDocument,
 69             body = doc.body,
 70             documentElement = doc.documentElement;
 71 
 72         // Determine the size of the visible rect by climbing the dom accounting for
 73         // all scrollable containers.
 74         for (el = element; el = getOffsetParent(el);) {
 75             // clientWidth is zero for inline block elements in ie.
 76             if ((!UA.ie || el.clientWidth != 0) &&
 77                 // body may have overflow set on it, yet we still get the entire
 78                 // viewport. In some browsers, el.offsetParent may be
 79                 // document.documentElement, so check for that too.
 80                 (el != body && el != documentElement && DOM.css(el, 'overflow') != 'visible')) {
 81                 var pos = DOM.offset(el);
 82                 // add border
 83                 pos.left += el.clientLeft;
 84                 pos.top += el.clientTop;
 85 
 86                 visibleRect.top = Math.max(visibleRect.top, pos.top);
 87                 visibleRect.right = Math.min(visibleRect.right,
 88                     // consider area without scrollBar
 89                     pos.left + el.clientWidth);
 90                 visibleRect.bottom = Math.min(visibleRect.bottom,
 91                     pos.top + el.clientHeight);
 92                 visibleRect.left = Math.max(visibleRect.left, pos.left);
 93             }
 94         }
 95 
 96         // Clip by window's viewport.
 97         scrollX = DOM.scrollLeft();
 98         scrollY = DOM.scrollTop();
 99         visibleRect.left = Math.max(visibleRect.left, scrollX);
100         visibleRect.top = Math.max(visibleRect.top, scrollY);
101         winSize = {
102             width:DOM.viewportWidth(),
103             height:DOM.viewportHeight()
104         };
105         visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
106         visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
107         return visibleRect.top >= 0 && visibleRect.left >= 0 &&
108             visibleRect.bottom > visibleRect.top &&
109             visibleRect.right > visibleRect.left ?
110             visibleRect : null;
111     }
112 
113     function getElFuturePos(elRegion, refNodeRegion, points, offset) {
114         var xy,
115             diff,
116             p1,
117             p2;
118 
119         xy = {
120             left:elRegion.left,
121             top:elRegion.top
122         };
123 
124         p1 = getAlignOffset(refNodeRegion, points[0]);
125         p2 = getAlignOffset(elRegion, points[1]);
126 
127         diff = [p2.left - p1.left, p2.top - p1.top];
128 
129         return {
130             left:xy.left - diff[0] + (+offset[0]),
131             top:xy.top - diff[1] + (+offset[1])
132         };
133     }
134 
135     function isFailX(elFuturePos, elRegion, visibleRect) {
136         return elFuturePos.left < visibleRect.left ||
137             elFuturePos.left + elRegion.width > visibleRect.right;
138     }
139 
140     function isFailY(elFuturePos, elRegion, visibleRect) {
141         return elFuturePos.top < visibleRect.top ||
142             elFuturePos.top + elRegion.height > visibleRect.bottom;
143     }
144 
145     function adjustForViewport(elFuturePos, elRegion, visibleRect, overflow) {
146         var pos = S.clone(elFuturePos),
147             size = {
148                 width:elRegion.width,
149                 height:elRegion.height
150             };
151 
152         if (overflow.adjustX && pos.left < visibleRect.left) {
153             pos.left = visibleRect.left;
154         }
155 
156         // Left edge inside and right edge outside viewport, try to resize it.
157         if (overflow['resizeWidth'] &&
158             pos.left >= visibleRect.left &&
159             pos.left + size.width > visibleRect.right) {
160             size.width -= (pos.left + size.width) - visibleRect.right;
161         }
162 
163         // Right edge outside viewport, try to move it.
164         if (overflow.adjustX && pos.left + size.width > visibleRect.right) {
165             // 保证左边界和可视区域左边界对齐
166             pos.left = Math.max(visibleRect.right - size.width, visibleRect.left);
167         }
168 
169         // Top edge outside viewport, try to move it.
170         if (overflow.adjustY && pos.top < visibleRect.top) {
171             pos.top = visibleRect.top;
172         }
173 
174         // Top edge inside and bottom edge outside viewport, try to resize it.
175         if (overflow['resizeHeight'] &&
176             pos.top >= visibleRect.top &&
177             pos.top + size.height > visibleRect.bottom) {
178             size.height -= (pos.top + size.height) - visibleRect.bottom;
179         }
180 
181         // Bottom edge outside viewport, try to move it.
182         if (overflow.adjustY && pos.top + size.height > visibleRect.bottom) {
183             // 保证上边界和可视区域上边界对齐
184             pos.top = Math.max(visibleRect.bottom - size.height, visibleRect.top);
185         }
186 
187         return S.mix(pos, size);
188     }
189 
190 
191     function flip(points, reg, map) {
192         var ret = [];
193         S.each(points, function (p) {
194             ret.push(p.replace(reg, function (m) {
195                 return map[m];
196             }));
197         });
198         return ret;
199     }
200 
201     function flipOffset(offset, index) {
202         offset[index] = -offset[index];
203         return offset;
204     }
205 
206 
207     /**
208      * @name Align
209      * @class
210      * Align extension class.
211      * Align component with specified element.
212      * @memberOf Component.UIBase
213      */
214     function Align() {
215     }
216 
217 
218     Align.__getOffsetParent = getOffsetParent;
219 
220     Align.__getVisibleRectForElement = getVisibleRectForElement;
221 
222     Align.ATTRS =
223     /**
224      * @lends Component.UIBase.Align.prototype
225      */
226     {
227 
228         /**
229          * Align configuration.
230          * @type Object
231          * @field
232          * @example
233          * <code>
234          *     {
235          *        node: null,         // 参考元素, falsy 或 window 为可视区域, 'trigger' 为触发元素, 其他为指定元素
236          *        points: ['cc','cc'], // ['tr', 'tl'] 表示 overlay 的 tl 与参考节点的 tr 对齐
237          *        offset: [0, 0]      // 有效值为 [n, m]
238          *     }
239          * </code>
240          */
241         align:{
242             value:{}
243         }
244     };
245 
246     function getRegion(node) {
247         var offset, w, h;
248         if (!S.isWindow(node[0])) {
249             offset = node.offset();
250             w = node.outerWidth();
251             h = node.outerHeight();
252         } else {
253             offset = { left:DOM.scrollLeft(), top:DOM.scrollTop() };
254             w = DOM.viewportWidth();
255             h = DOM.viewportHeight();
256         }
257         offset.width = w;
258         offset.height = h;
259         return offset;
260     }
261 
262     /**
263      * 获取 node 上的 align 对齐点 相对于页面的坐标
264      * @param region
265      * @param align
266      */
267     function getAlignOffset(region, align) {
268         var V = align.charAt(0),
269             H = align.charAt(1),
270             w = region.width,
271             h = region.height,
272             x, y;
273 
274         x = region.left;
275         y = region.top;
276 
277         if (V === 'c') {
278             y += h / 2;
279         } else if (V === 'b') {
280             y += h;
281         }
282 
283         if (H === 'c') {
284             x += w / 2;
285         } else if (H === 'r') {
286             x += w;
287         }
288 
289         return { left:x, top:y };
290     }
291 
292     Align.prototype =
293     /**
294      * @lends Component.UIBase.Align.prototype
295      */
296     {
297         _uiSetAlign:function (v) {
298             if (v && v.points) {
299                 this.align(v.node, v.points, v.offset, v.overflow);
300             }
301         },
302 
303         /*
304          对齐 Overlay 到 node 的 points 点, 偏移 offset 处
305          @function
306          @ignore
307          @param {Element} node 参照元素, 可取配置选项中的设置, 也可是一元素
308          @param {String[]} points 对齐方式
309          @param {Number[]} [offset] 偏移
310          */
311         align:function (refNode, points, offset, overflow) {
312             refNode = Node.one(refNode || win);
313             offset = offset && [].concat(offset) || [0, 0];
314             overflow = overflow || {};
315 
316             var self = this,
317                 el = self.get("el"),
318                 fail = 0,
319             // 当前节点可以被放置的显示区域
320                 visibleRect = getVisibleRectForElement(el[0]),
321             // 当前节点所占的区域, left/top/width/height
322                 elRegion = getRegion(el),
323             // 参照节点所占的区域, left/top/width/height
324                 refNodeRegion = getRegion(refNode),
325             // 当前节点将要被放置的位置
326                 elFuturePos = getElFuturePos(elRegion, refNodeRegion, points, offset),
327             // 当前节点将要所处的区域
328                 newElRegion = S.merge(elRegion, elFuturePos);
329 
330             // 如果可视区域不能完全放置当前节点时允许调整
331             if (visibleRect && (overflow.adjustX || overflow.adjustY)) {
332 
333                 // 如果横向不能放下
334                 if (isFailX(elFuturePos, elRegion, visibleRect)) {
335                     fail = 1;
336                     // 对齐位置反下
337                     points = flip(points, /[lr]/ig, {
338                         l:"r",
339                         r:"l"
340                     });
341                     // 偏移量也反下
342                     offset = flipOffset(offset, 0);
343                 }
344 
345                 // 如果纵向不能放下
346                 if (isFailY(elFuturePos, elRegion, visibleRect)) {
347                     fail = 1;
348                     // 对齐位置反下
349                     points = flip(points, /[tb]/ig, {
350                         t:"b",
351                         b:"t"
352                     });
353                     // 偏移量也反下
354                     offset = flipOffset(offset, 1);
355                 }
356 
357                 // 如果失败,重新计算当前节点将要被放置的位置
358                 if (fail) {
359                     elFuturePos = getElFuturePos(elRegion, refNodeRegion, points, offset);
360                     S.mix(newElRegion, elFuturePos);
361                 }
362 
363                 var newOverflowCfg = {};
364 
365                 // 检查反下后的位置是否可以放下了
366                 // 如果仍然放不下只有指定了可以调整当前方向才调整
367                 newOverflowCfg.adjustX = overflow.adjustX &&
368                     isFailX(elFuturePos, elRegion, visibleRect);
369 
370                 newOverflowCfg.adjustY = overflow.adjustY &&
371                     isFailY(elFuturePos, elRegion, visibleRect);
372 
373                 // 确实要调整,甚至可能会调整高度宽度
374                 if (newOverflowCfg.adjustX || newOverflowCfg.adjustY) {
375                     newElRegion = adjustForViewport(elFuturePos, elRegion,
376                         visibleRect, newOverflowCfg);
377                 }
378             }
379 
380             // 新区域位置发生了变化
381             if (newElRegion.left != elRegion.left) {
382                 self.set("x", newElRegion.left)
383             }
384 
385             if (newElRegion.top != elRegion.top) {
386                 self.set("y", newElRegion.top)
387             }
388 
389             // 新区域高宽发生了变化
390             if (newElRegion.width != elRegion.width) {
391                 el.width(el.width() + newElRegion.width - elRegion.width);
392             }
393             if (newElRegion.height != elRegion.height) {
394                 el.height(el.height() + newElRegion.height - elRegion.height);
395             }
396 
397             return self;
398         },
399 
400         /**
401          * Make current element center within node.
402          * @param {undefined|String|HTMLElement|NodeList} node
403          * Same as node config of {@link Component.UIBase.Align#align} .
404          */
405         center:function (node) {
406             var self = this;
407             self.set('align', {
408                 node:node,
409                 points:["cc", "cc"],
410                 offset:[0, 0]
411             });
412             return self;
413         }
414     };
415 
416     return Align;
417 }, {
418     requires:["ua", "dom", "node"]
419 });
420 /**
421  *  2012-04-26 yiminghe@gmail.com
422  *   - 优化智能对齐算法
423  *   - 慎用 resizeXX
424  *
425  *  2011-07-13 yiminghe@gmail.com note:
426  *   - 增加智能对齐,以及大小调整选项
427  **/