1 /**
  2  * @fileOverview dom/style
  3  * @author yiminghe@gmail.com,lifesinger@gmail.com
  4  */
  5 KISSY.add('dom/style', function (S, DOM, UA, undefined) {
  6     "use strict";
  7     var WINDOW = S.Env.host,
  8         doc = WINDOW.document,
  9         docElem = doc.documentElement,
 10         isIE = UA['ie'],
 11         STYLE = 'style',
 12         FLOAT = 'float',
 13         CSS_FLOAT = 'cssFloat',
 14         STYLE_FLOAT = 'styleFloat',
 15         WIDTH = 'width',
 16         HEIGHT = 'height',
 17         AUTO = 'auto',
 18         DISPLAY = 'display',
 19         OLD_DISPLAY = DISPLAY + S.now(),
 20         NONE = 'none',
 21         PARSEINT = parseInt,
 22         RE_NUMPX = /^-?\d+(?:px)?$/i,
 23         cssNumber = {
 24             fillOpacity:1,
 25             fontWeight:1,
 26             lineHeight:1,
 27             opacity:1,
 28             orphans:1,
 29             widows:1,
 30             zIndex:1,
 31             zoom:1
 32         },
 33         rmsPrefix = /^-ms-/,
 34         RE_DASH = /-([a-z])/ig,
 35         CAMELCASE_FN = function (all, letter) {
 36             return letter.toUpperCase();
 37         },
 38         // 考虑 ie9 ...
 39         rupper = /([A-Z]|^ms)/g,
 40         EMPTY = '',
 41         DEFAULT_UNIT = 'px',
 42         CUSTOM_STYLES = {},
 43         cssProps = {},
 44         defaultDisplay = {};
 45 
 46     // normalize reserved word float alternatives ("cssFloat" or "styleFloat")
 47     if (docElem[STYLE][CSS_FLOAT] !== undefined) {
 48         cssProps[FLOAT] = CSS_FLOAT;
 49     }
 50     else if (docElem[STYLE][STYLE_FLOAT] !== undefined) {
 51         cssProps[FLOAT] = STYLE_FLOAT;
 52     }
 53 
 54     function camelCase(name) {
 55         // fix #92, ms!
 56         return name.replace(rmsPrefix, "ms-").replace(RE_DASH, CAMELCASE_FN);
 57     }
 58 
 59     var defaultDisplayDetectIframe,
 60         defaultDisplayDetectIframeDoc;
 61 
 62     // modified from jquery : bullet-proof method of getting default display
 63     // fix domain problem in ie>6 , ie6 still access denied
 64     function getDefaultDisplay(tagName) {
 65         var body,
 66             elem;
 67         if (!defaultDisplay[ tagName ]) {
 68             body = doc.body || doc.documentElement;
 69             elem = doc.createElement(tagName);
 70             DOM.prepend(elem, body);
 71             var oldDisplay = DOM.css(elem, "display");
 72             body.removeChild(elem);
 73             // If the simple way fails,
 74             // get element's real default display by attaching it to a temp iframe
 75             if (oldDisplay == "none" || oldDisplay == "") {
 76                 // No iframe to use yet, so create it
 77                 if (!defaultDisplayDetectIframe) {
 78                     defaultDisplayDetectIframe = doc.createElement("iframe");
 79 
 80                     defaultDisplayDetectIframe.frameBorder =
 81                         defaultDisplayDetectIframe.width =
 82                             defaultDisplayDetectIframe.height = 0;
 83 
 84                     DOM.prepend(defaultDisplayDetectIframe, body);
 85                     var iframeSrc;
 86                     if (iframeSrc = DOM.getEmptyIframeSrc()) {
 87                         defaultDisplayDetectIframe.src = iframeSrc;
 88                     }
 89                 } else {
 90                     DOM.prepend(defaultDisplayDetectIframe, body);
 91                 }
 92 
 93                 // Create a cacheable copy of the iframe document on first call.
 94                 // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
 95                 // document to it; WebKit & Firefox won't allow reusing the iframe document.
 96                 if (!defaultDisplayDetectIframeDoc || !defaultDisplayDetectIframe.createElement) {
 97 
 98                     try {
 99                         defaultDisplayDetectIframeDoc = defaultDisplayDetectIframe.contentWindow.document;
100                         defaultDisplayDetectIframeDoc.write(( doc.compatMode === "CSS1Compat" ? "<!doctype html>" : "" )
101                             + "<html><head>" +
102                             (UA['ie'] && DOM.isCustomDomain() ?
103                                 "<script>document.domain = '" +
104                                     doc.domain
105                                     + "';</script>" : "")
106                             +
107                             "</head><body>");
108                         defaultDisplayDetectIframeDoc.close();
109                     } catch (e) {
110                         // ie6 need a breath , such as alert(8) or setTimeout;
111                         // 同时需要同步,所以无解,勉强返回
112                         return "block";
113                     }
114                 }
115 
116                 elem = defaultDisplayDetectIframeDoc.createElement(tagName);
117 
118                 defaultDisplayDetectIframeDoc.body.appendChild(elem);
119 
120                 oldDisplay = DOM.css(elem, "display");
121 
122                 body.removeChild(defaultDisplayDetectIframe);
123             }
124 
125             // Store the correct default display
126             defaultDisplay[ tagName ] = oldDisplay;
127         }
128 
129         return defaultDisplay[ tagName ];
130     }
131 
132     S.mix(DOM,
133         /**
134          * @lends DOM
135          */
136         {
137             _camelCase:camelCase,
138             // _cssNumber:cssNumber,
139             _CUSTOM_STYLES:CUSTOM_STYLES,
140             _cssProps:cssProps,
141             _getComputedStyle:function (elem, name) {
142                 var val = "",
143                     computedStyle,
144                     d = elem.ownerDocument;
145 
146                 name = name.replace(rupper, "-$1").toLowerCase();
147 
148                 // https://github.com/kissyteam/kissy/issues/61
149                 if (computedStyle = d.defaultView.getComputedStyle(elem, null)) {
150                     val = computedStyle.getPropertyValue(name) || computedStyle[name];
151                 }
152 
153                 // 还没有加入到 document,就取行内
154                 if (val == "" && !DOM.contains(d.documentElement, elem)) {
155                     name = cssProps[name] || name;
156                     val = elem[STYLE][name];
157                 }
158 
159                 return val;
160             },
161 
162             /**
163              *  Get inline style property from the first element of matched elements
164              *  or
165              *  Set one or more CSS properties for the set of matched elements.
166              *  @param {HTMLElement[]|String|HTMLElement} selector Matched elements
167              *  @param {String|Object} name A CSS property. or A map of property-value pairs to set.
168              *  @param [val] A value to set for the property.
169              *  @returns {undefined|String}
170              */
171             style:function (selector, name, val) {
172                 var els = DOM.query(selector), elem = els[0], i;
173                 // supports hash
174                 if (S.isPlainObject(name)) {
175                     for (var k in name) {
176                         for (i = els.length - 1; i >= 0; i--) {
177                             style(els[i], k, name[k]);
178                         }
179                     }
180                     return undefined;
181                 }
182                 if (val === undefined) {
183                     var ret = '';
184                     if (elem) {
185                         ret = style(elem, name, val);
186                     }
187                     return ret;
188                 } else {
189                     for (i = els.length - 1; i >= 0; i--) {
190                         style(els[i], name, val);
191                     }
192                 }
193                 return undefined;
194             },
195 
196             /**
197              * Get the computed value of a style property for the first element in the set of matched elements.
198              * or
199              * Set one or more CSS properties for the set of matched elements.
200              * @param {HTMLElement[]|String|HTMLElement|Element} selector 选择器或节点或节点数组
201              * @param {String|Object} name A CSS property. or A map of property-value pairs to set.
202              * @param [val] A value to set for the property.
203              * @returns {undefined|String}
204              */
205             css:function (selector, name, val) {
206                 var els = DOM.query(selector),
207                     elem = els[0],
208                     i;
209                 // supports hash
210                 if (S.isPlainObject(name)) {
211                     for (var k in name) {
212                         for (i = els.length - 1; i >= 0; i--) {
213                             style(els[i], k, name[k]);
214                         }
215                     }
216                     return undefined;
217                 }
218 
219                 name = camelCase(name);
220                 var hook = CUSTOM_STYLES[name];
221                 // getter
222                 if (val === undefined) {
223                     // supports css selector/Node/NodeList
224                     var ret = '';
225                     if (elem) {
226                         // If a hook was provided get the computed value from there
227                         if (hook && "get" in hook && (ret = hook.get(elem, true)) !== undefined) {
228                         } else {
229                             ret = DOM._getComputedStyle(elem, name);
230                         }
231                     }
232                     return ret === undefined ? '' : ret;
233                 }
234                 // setter
235                 else {
236                     for (i = els.length - 1; i >= 0; i--) {
237                         style(els[i], name, val);
238                     }
239                 }
240                 return undefined;
241             },
242 
243             /**
244              * Display the matched elements.
245              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements.
246              */
247             show:function (selector) {
248                 var els = DOM.query(selector), elem, i;
249                 for (i = els.length - 1; i >= 0; i--) {
250                     elem = els[i];
251                     elem[STYLE][DISPLAY] = DOM.data(elem, OLD_DISPLAY) || EMPTY;
252 
253                     // 可能元素还处于隐藏状态,比如 css 里设置了 display: none
254                     if (DOM.css(elem, DISPLAY) === NONE) {
255                         var tagName = elem.tagName.toLowerCase(),
256                             old = getDefaultDisplay(tagName);
257                         DOM.data(elem, OLD_DISPLAY, old);
258                         elem[STYLE][DISPLAY] = old;
259                     }
260                 }
261             },
262 
263             /**
264              * Hide the matched elements.
265              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements.
266              */
267             hide:function (selector) {
268                 var els = DOM.query(selector), elem, i;
269                 for (i = els.length - 1; i >= 0; i--) {
270                     elem = els[i];
271                     var style = elem[STYLE], old = style[DISPLAY];
272                     if (old !== NONE) {
273                         if (old) {
274                             DOM.data(elem, OLD_DISPLAY, old);
275                         }
276                         style[DISPLAY] = NONE;
277                     }
278                 }
279             },
280 
281             /**
282              * Display or hide the matched elements.
283              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements.
284              */
285             toggle:function (selector) {
286                 var els = DOM.query(selector), elem, i;
287                 for (i = els.length - 1; i >= 0; i--) {
288                     elem = els[i];
289                     if (DOM.css(elem, DISPLAY) === NONE) {
290                         DOM.show(elem);
291                     } else {
292                         DOM.hide(elem);
293                     }
294                 }
295             },
296 
297             /**
298              * Creates a stylesheet from a text blob of rules.
299              * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
300              * @param {window} [refWin=window] Window which will accept this stylesheet
301              * @param {String} cssText The text containing the css rules
302              * @param {String} [id] An id to add to the stylesheet for later removal
303              */
304             addStyleSheet:function (refWin, cssText, id) {
305                 refWin = refWin || WINDOW;
306                 if (S.isString(refWin)) {
307                     id = cssText;
308                     cssText = refWin;
309                     refWin = WINDOW;
310                 }
311                 refWin = DOM.get(refWin);
312                 var win = DOM._getWin(refWin),
313                     doc = win.document,
314                     elem;
315 
316                 if (id && (id = id.replace('#', EMPTY))) {
317                     elem = DOM.get('#' + id, doc);
318                 }
319 
320                 // 仅添加一次,不重复添加
321                 if (elem) {
322                     return;
323                 }
324 
325                 elem = DOM.create('<style>', { id:id }, doc);
326 
327                 // 先添加到 DOM 树中,再给 cssText 赋值,否则 css hack 会失效
328                 DOM.get('head', doc).appendChild(elem);
329 
330                 if (elem.styleSheet) { // IE
331                     elem.styleSheet.cssText = cssText;
332                 } else { // W3C
333                     elem.appendChild(doc.createTextNode(cssText));
334                 }
335             },
336 
337             /**
338              * Make matched elements unselectable
339              * @param {HTMLElement[]|String|HTMLElement} selector  Matched elements.
340              */
341             unselectable:function (selector) {
342                 var _els = DOM.query(selector), elem, j;
343                 for (j = _els.length - 1; j >= 0; j--) {
344                     elem = _els[j];
345                     if (UA['gecko']) {
346                         elem[STYLE]['MozUserSelect'] = 'none';
347                     }
348                     else if (UA['webkit']) {
349                         elem[STYLE]['KhtmlUserSelect'] = 'none';
350                     } else {
351                         if (UA['ie'] || UA['opera']) {
352                             var e, i = 0,
353                                 els = elem.getElementsByTagName("*");
354                             elem.setAttribute("unselectable", 'on');
355                             while (( e = els[ i++ ] )) {
356                                 switch (e.tagName.toLowerCase()) {
357                                     case 'iframe' :
358                                     case 'textarea' :
359                                     case 'input' :
360                                     case 'select' :
361                                         /* Ignore the above tags */
362                                         break;
363                                     default :
364                                         e.setAttribute("unselectable", 'on');
365                                 }
366                             }
367                         }
368                     }
369                 }
370             },
371 
372             /**
373              * Get the current computed width for the first element in the set of matched elements, including padding but not border.
374              * @function
375              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
376              * @returns {Number}
377              */
378             innerWidth:0,
379             /**
380              * Get the current computed height for the first element in the set of matched elements, including padding but not border.
381              * @function
382              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
383              * @returns {Number}
384              */
385             innerHeight:0,
386             /**
387              *  Get the current computed width for the first element in the set of matched elements, including padding and border, and optionally margin.
388              * @function
389              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
390              * @param {Boolean} [includeMargin] A Boolean indicating whether to include the element's margin in the calculation.
391              * @returns {Number}
392              */
393             outerWidth:0,
394             /**
395              * Get the current computed height for the first element in the set of matched elements, including padding, border, and optionally margin.
396              * @function
397              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
398              * @param {Boolean} [includeMargin] A Boolean indicating whether to include the element's margin in the calculation.
399              * @returns {Number}
400              */
401             outerHeight:0,
402             /**
403              * Get the current computed width for the first element in the set of matched elements.
404              * or
405              * Set the CSS width of each element in the set of matched elements.
406              * @function
407              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
408              * @param {String|Number} [value]
409              * An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
410              * @returns {Number|undefined}
411              */
412             width:0,
413             /**
414              * Get the current computed height for the first element in the set of matched elements.
415              * or
416              * Set the CSS height of each element in the set of matched elements.
417              * @function
418              * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
419              * @param {String|Number} [value]
420              * An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string).
421              * @returns {Number|undefined}
422              */
423             height:0
424         });
425 
426     function capital(str) {
427         return str.charAt(0).toUpperCase() + str.substring(1);
428     }
429 
430     S.each([WIDTH, HEIGHT], function (name) {
431         DOM["inner" + capital(name)] = function (selector) {
432             var el = DOM.get(selector);
433             return el && getWHIgnoreDisplay(el, name, "padding");
434         };
435 
436         DOM["outer" + capital(name)] = function (selector, includeMargin) {
437             var el = DOM.get(selector);
438             return el && getWHIgnoreDisplay(el, name, includeMargin ? "margin" : "border");
439         };
440 
441         DOM[name] = function (selector, val) {
442             var ret = DOM.css(selector, name, val);
443             if (ret) {
444                 ret = parseFloat(ret);
445             }
446             return ret;
447         };
448     });
449 
450     var cssShow = { position:"absolute", visibility:"hidden", display:"block" };
451 
452     /*
453      css height,width 永远都是计算值
454      */
455     S.each(["height", "width"], function (name) {
456         /**
457          * @ignore
458          */
459         CUSTOM_STYLES[ name ] = {
460             /**
461              * @ignore
462              */
463             get:function (elem, computed) {
464                 if (computed) {
465                     return getWHIgnoreDisplay(elem, name) + "px";
466                 }
467             },
468             set:function (elem, value) {
469                 if (RE_NUMPX.test(value)) {
470                     value = parseFloat(value);
471                     if (value >= 0) {
472                         return value + "px";
473                     }
474                 } else {
475                     return value;
476                 }
477             }
478         };
479     });
480 
481     S.each(["left", "top"], function (name) {
482         /**
483          * @ignore
484          */
485         CUSTOM_STYLES[ name ] = {
486             get:function (elem, computed) {
487                 if (computed) {
488                     var val = DOM._getComputedStyle(elem, name), offset;
489 
490                     // 1. 当没有设置 style.left 时,getComputedStyle 在不同浏览器下,返回值不同
491                     //    比如:firefox 返回 0, webkit/ie 返回 auto
492                     // 2. style.left 设置为百分比时,返回值为百分比
493                     // 对于第一种情况,如果是 relative 元素,值为 0. 如果是 absolute 元素,值为 offsetLeft - marginLeft
494                     // 对于第二种情况,大部分类库都未做处理,属于“明之而不 fix”的保留 bug
495                     if (val === AUTO) {
496                         val = 0;
497                         if (S.inArray(DOM.css(elem, 'position'), ['absolute', 'fixed'])) {
498                             offset = elem[name === 'left' ? 'offsetLeft' : 'offsetTop'];
499 
500                             // old-ie 下,elem.offsetLeft 包含 offsetParent 的 border 宽度,需要减掉
501                             if (isIE && doc['documentMode'] != 9 || UA['opera']) {
502                                 // 类似 offset ie 下的边框处理
503                                 // 如果 offsetParent 为 html ,需要减去默认 2 px == documentElement.clientTop
504                                 // 否则减去 borderTop 其实也是 clientTop
505                                 // http://msdn.microsoft.com/en-us/library/aa752288%28v=vs.85%29.aspx
506                                 // ie<9 注意有时候 elem.offsetParent 为 null ...
507                                 // 比如 DOM.append(DOM.create("<div class='position:absolute'></div>"),document.body)
508                                 offset -= elem.offsetParent && elem.offsetParent['client' + (name == 'left' ? 'Left' : 'Top')]
509                                     || 0;
510                             }
511                             val = offset - (PARSEINT(DOM.css(elem, 'margin-' + name)) || 0);
512                         }
513                         val += "px";
514                     }
515                     return val;
516                 }
517             }
518         };
519     });
520 
521     function swap(elem, options, callback) {
522         var old = {};
523 
524         // Remember the old values, and insert the new ones
525         for (var name in options) {
526             old[ name ] = elem[STYLE][ name ];
527             elem[STYLE][ name ] = options[ name ];
528         }
529 
530         callback.call(elem);
531 
532         // Revert the old values
533         for (name in options) {
534             elem[STYLE][ name ] = old[ name ];
535         }
536     }
537 
538     function style(elem, name, val) {
539         var style;
540         if (elem.nodeType === 3 || elem.nodeType === 8 || !(style = elem[STYLE])) {
541             return undefined;
542         }
543         name = camelCase(name);
544         var ret, hook = CUSTOM_STYLES[name];
545         name = cssProps[name] || name;
546         // setter
547         if (val !== undefined) {
548             // normalize unsetting
549             if (val === null || val === EMPTY) {
550                 val = EMPTY;
551             }
552             // number values may need a unit
553             else if (!isNaN(Number(val)) && !cssNumber[name]) {
554                 val += DEFAULT_UNIT;
555             }
556             if (hook && hook.set) {
557                 val = hook.set(elem, val);
558             }
559             if (val !== undefined) {
560                 // ie 无效值报错
561                 try {
562                     style[name] = val;
563                 } catch (e) {
564                     S.log("css set error :" + e);
565                 }
566                 // #80 fix,font-family
567                 if (val === EMPTY && style.removeAttribute) {
568                     style.removeAttribute(name);
569                 }
570             }
571             if (!style.cssText) {
572                 elem.removeAttribute('style');
573             }
574             return undefined;
575         }
576         //getter
577         else {
578             // If a hook was provided get the non-computed value from there
579             if (hook && "get" in hook && (ret = hook.get(elem, false)) !== undefined) {
580 
581             } else {
582                 // Otherwise just get the value from the style object
583                 ret = style[ name ];
584             }
585             return ret === undefined ? "" : ret;
586         }
587     }
588 
589     // fix #119 : https://github.com/kissyteam/kissy/issues/119
590     function getWHIgnoreDisplay(elem) {
591         var val, args = arguments;
592         // incase elem is window
593         // elem.offsetWidth === undefined
594         if (elem.offsetWidth !== 0) {
595             val = getWH.apply(undefined, args);
596         } else {
597             swap(elem, cssShow, function () {
598                 val = getWH.apply(undefined, args);
599             });
600         }
601         return val;
602     }
603 
604 
605     /**
606      * 得到元素的大小信息
607      * @param elem
608      * @param name
609      * @param {String} [extra]  "padding" : (css width) + padding
610      *                          "border" : (css width) + padding + border
611      *                          "margin" : (css width) + padding + border + margin
612      */
613     function getWH(elem, name, extra) {
614         if (S.isWindow(elem)) {
615             return name == WIDTH ? DOM.viewportWidth(elem) : DOM.viewportHeight(elem);
616         } else if (elem.nodeType == 9) {
617             return name == WIDTH ? DOM.docWidth(elem) : DOM.docHeight(elem);
618         }
619         var which = name === WIDTH ? ['Left', 'Right'] : ['Top', 'Bottom'],
620             val = name === WIDTH ? elem.offsetWidth : elem.offsetHeight;
621 
622         if (val > 0) {
623             if (extra !== "border") {
624                 S.each(which, function (w) {
625                     if (!extra) {
626                         val -= parseFloat(DOM.css(elem, "padding" + w)) || 0;
627                     }
628                     if (extra === "margin") {
629                         val += parseFloat(DOM.css(elem, extra + w)) || 0;
630                     } else {
631                         val -= parseFloat(DOM.css(elem, "border" + w + "Width")) || 0;
632                     }
633                 });
634             }
635 
636             return val;
637         }
638 
639         // Fall back to computed then uncomputed css if necessary
640         val = DOM._getComputedStyle(elem, name);
641         if (val == null || (Number(val)) < 0) {
642             val = elem.style[ name ] || 0;
643         }
644         // Normalize "", auto, and prepare for extra
645         val = parseFloat(val) || 0;
646 
647         // Add padding, border, margin
648         if (extra) {
649             S.each(which, function (w) {
650                 val += parseFloat(DOM.css(elem, "padding" + w)) || 0;
651                 if (extra !== "padding") {
652                     val += parseFloat(DOM.css(elem, "border" + w + "Width")) || 0;
653                 }
654                 if (extra === "margin") {
655                     val += parseFloat(DOM.css(elem, extra + w)) || 0;
656                 }
657             });
658         }
659 
660         return val;
661     }
662 
663     return DOM;
664 }, {
665     requires:["dom/base", "ua"]
666 });
667 
668 /**
669  * 2011-12-21
670  *  - backgroundPositionX, backgroundPositionY firefox/w3c 不支持
671  *  - w3c 为准,这里不 fix 了
672  *
673  *
674  * 2011-08-19
675  *  - 调整结构,减少耦合
676  *  - fix css("height") == auto
677  *
678  * NOTES:
679  *  - Opera 下,color 默认返回 #XXYYZZ, 非 rgb(). 目前 jQuery 等类库均忽略此差异,KISSY 也忽略。
680  *  - Safari 低版本,transparent 会返回为 rgba(0, 0, 0, 0), 考虑低版本才有此 bug, 亦忽略。
681  *
682  *
683  *  - getComputedStyle 在 webkit 下,会舍弃小数部分,ie 下会四舍五入,gecko 下直接输出 float 值。
684  *
685  *  - color: blue 继承值,getComputedStyle, 在 ie 下返回 blue, opera 返回 #0000ff, 其它浏览器
686  *    返回 rgb(0, 0, 255)
687  *
688  *  - 总之:要使得返回值完全一致是不大可能的,jQuery/ExtJS/KISSY 未“追求完美”。YUI3 做了部分完美处理,但
689  *    依旧存在浏览器差异。
690  */
691