1 /**
  2  * @fileOverview dom-attr
  3  * @author yiminghe@gmail.com,lifesinger@gmail.com
  4  */
  5 KISSY.add('dom/attr', function (S, DOM, UA, undefined) {
  6 
  7     var doc = S.Env.host.document,
  8         docElement = doc.documentElement,
  9         oldIE = !docElement.hasAttribute,
 10         TEXT = docElement.textContent === undefined ?
 11             'innerText' : 'textContent',
 12         EMPTY = '',
 13         nodeName = DOM.nodeName,
 14         rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
 15         rfocusable = /^(?:button|input|object|select|textarea)$/i,
 16         rclickable = /^a(?:rea)?$/i,
 17         rinvalidChar = /:|^on/,
 18         rreturn = /\r/g,
 19         attrFix = {
 20         },
 21         attrFn = {
 22             val:1,
 23             css:1,
 24             html:1,
 25             text:1,
 26             data:1,
 27             width:1,
 28             height:1,
 29             offset:1,
 30             scrollTop:1,
 31             scrollLeft:1
 32         },
 33         attrHooks = {
 34             // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
 35             tabindex:{
 36                 get:function (el) {
 37                     // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
 38                     var attributeNode = el.getAttributeNode("tabindex");
 39                     return attributeNode && attributeNode.specified ?
 40                         parseInt(attributeNode.value, 10) :
 41                         rfocusable.test(el.nodeName) || rclickable.test(el.nodeName) && el.href ?
 42                             0 :
 43                             undefined;
 44                 }
 45             },
 46             // 在标准浏览器下,用 getAttribute 获取 style 值
 47             // IE7- 下,需要用 cssText 来获取
 48             // 统一使用 cssText
 49             style:{
 50                 get:function (el) {
 51                     return el.style.cssText;
 52                 },
 53                 set:function (el, val) {
 54                     el.style.cssText = val;
 55                 }
 56             }
 57         },
 58         propFix = {
 59             hidefocus:"hideFocus",
 60             tabindex:"tabIndex",
 61             readonly:"readOnly",
 62             for:"htmlFor",
 63             class:"className",
 64             maxlength:"maxLength",
 65             cellspacing:"cellSpacing",
 66             cellpadding:"cellPadding",
 67             rowspan:"rowSpan",
 68             colspan:"colSpan",
 69             usemap:"useMap",
 70             frameborder:"frameBorder",
 71             contenteditable:"contentEditable"
 72         },
 73     // Hook for boolean attributes
 74     // if bool is false
 75     //  - standard browser returns null
 76     //  - ie<8 return false
 77     //   - so norm to undefined
 78         boolHook = {
 79             get:function (elem, name) {
 80                 // 转发到 prop 方法
 81                 return DOM.prop(elem, name) ?
 82                     // 根据 w3c attribute , true 时返回属性名字符串
 83                     name.toLowerCase() :
 84                     undefined;
 85             },
 86             set:function (elem, value, name) {
 87                 var propName;
 88                 if (value === false) {
 89                     // Remove boolean attributes when set to false
 90                     DOM.removeAttr(elem, name);
 91                 } else {
 92                     // 直接设置 true,因为这是 bool 类属性
 93                     propName = propFix[ name ] || name;
 94                     if (propName in elem) {
 95                         // Only set the IDL specifically if it already exists on the element
 96                         elem[ propName ] = true;
 97                     }
 98                     elem.setAttribute(name, name.toLowerCase());
 99                 }
100                 return name;
101             }
102         },
103         propHooks = {},
104     // get attribute value from attribute node , only for ie
105         attrNodeHook = {
106         },
107         valHooks = {
108             option:{
109                 get:function (elem) {
110                     // 当没有设定 value 时,标准浏览器 option.value === option.text
111                     // ie7- 下,没有设定 value 时,option.value === '', 需要用 el.attributes.value 来判断是否有设定 value
112                     var val = elem.attributes.value;
113                     return !val || val.specified ? elem.value : elem.text;
114                 }
115             },
116             select:{
117                 // 对于 select, 特别是 multiple type, 存在很严重的兼容性问题
118                 get:function (elem) {
119                     var index = elem.selectedIndex,
120                         options = elem.options,
121                         one = elem.type === "select-one";
122 
123                     // Nothing was selected
124                     if (index < 0) {
125                         return null;
126                     } else if (one) {
127                         return DOM.val(options[index]);
128                     }
129 
130                     // Loop through all the selected options
131                     var ret = [], i = 0, len = options.length;
132                     for (; i < len; ++i) {
133                         if (options[i].selected) {
134                             ret.push(DOM.val(options[i]));
135                         }
136                     }
137                     // Multi-Selects return an array
138                     return ret;
139                 },
140 
141                 set:function (elem, value) {
142                     var values = S.makeArray(value),
143                         opts = elem.options;
144                     S.each(opts, function (opt) {
145                         opt.selected = S.inArray(DOM.val(opt), values);
146                     });
147 
148                     if (!values.length) {
149                         elem.selectedIndex = -1;
150                     }
151                     return values;
152                 }
153             }};
154 
155     if (oldIE) {
156 
157         // get attribute value from attribute node for ie
158         attrNodeHook = {
159             get:function (elem, name) {
160                 var ret;
161                 ret = elem.getAttributeNode(name);
162                 // Return undefined if attribute node specified by user
163                 return ret && (
164                     // fix #100
165                     ret.specified
166                         // input.attr("value")
167                         || ret.nodeValue) ?
168                     ret.nodeValue :
169                     undefined;
170             },
171             set:function (elem, value, name) {
172                 // Check form objects in IE (multiple bugs related)
173                 // Only use nodeValue if the attribute node exists on the form
174                 var ret = elem.getAttributeNode(name);
175                 if (ret) {
176                     ret.nodeValue = value;
177                 } else {
178                     try {
179                         var attr = elem.ownerDocument.createAttribute(name);
180                         attr.value = value;
181                         elem.setAttributeNode(attr);
182                     }
183                     catch (e) {
184                         // It's a real failure only if setAttribute also fails.
185                         // http://msdn.microsoft.com/en-us/library/ms536739(v=vs.85).aspx
186                         // 0 : Match sAttrName regardless of case.
187                         return elem.setAttribute(name, value, 0);
188                     }
189                 }
190             }
191         };
192 
193 
194         // ie6,7 不区分 attribute 与 property
195         attrFix = propFix;
196         // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
197         attrHooks.tabIndex = attrHooks.tabindex;
198         // fix ie bugs
199         // 不光是 href, src, 还有 rowspan 等非 mapping 属性,也需要用第 2 个参数来获取原始值
200         // 注意 colSpan rowSpan 已经由 propFix 转为大写
201         S.each([ "href", "src", "width", "height", "colSpan", "rowSpan" ], function (name) {
202             attrHooks[ name ] = {
203                 get:function (elem) {
204                     var ret = elem.getAttribute(name, 2);
205                     return ret === null ? undefined : ret;
206                 }
207             };
208         });
209         // button 元素的 value 属性和其内容冲突
210         // <button value='xx'>zzz</button>
211         valHooks.button = attrHooks.value = attrNodeHook;
212     }
213 
214     // Radios and checkboxes getter/setter
215 
216     S.each([ "radio", "checkbox" ], function (r) {
217         valHooks[ r ] = {
218             get:function (elem) {
219                 // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
220                 return elem.getAttribute("value") === null ? "on" : elem.value;
221             },
222             set:function (elem, value) {
223                 if (S.isArray(value)) {
224                     return elem.checked = S.inArray(DOM.val(elem), value);
225                 }
226             }
227 
228         };
229     });
230 
231     function getProp(elem, name) {
232         name = propFix[ name ] || name;
233         var hook = propHooks[ name ];
234         if (hook && hook.get) {
235             return hook.get(elem, name);
236 
237         } else {
238             return elem[ name ];
239         }
240     }
241 
242     S.mix(DOM,
243         /**
244          * @lends DOM
245          */
246         {
247             /**
248              * Get the value of a property for the first element in the set of matched elements.
249              * or
250              * Set one or more properties for the set of matched elements.
251              * @param {Array<HTMLElement>|String|HTMLElement} selector matched elements
252              * @param {String|Object} name
253              * The name of the property to set.
254              * or
255              * A map of property-value pairs to set.
256              * @param [value] A value to set for the property.
257              * @returns {String|undefined|Boolean}
258              */
259             prop:function (selector, name, value) {
260                 var elems = DOM.query(selector);
261 
262                 // supports hash
263                 if (S.isPlainObject(name)) {
264                     for (var k in name) {
265                         DOM.prop(elems, k, name[k]);
266                     }
267                     return undefined;
268                 }
269 
270                 // Try to normalize/fix the name
271                 name = propFix[ name ] || name;
272                 var hook = propHooks[ name ];
273                 if (value !== undefined) {
274                     for (var i = elems.length - 1; i >= 0; i--) {
275                         var elem = elems[i];
276                         if (hook && hook.set) {
277                             hook.set(elem, value, name);
278                         } else {
279                             elem[ name ] = value;
280                         }
281                     }
282                 } else {
283                     if (elems.length) {
284                         return getProp(elems[0], name);
285                     }
286                 }
287                 return undefined;
288             },
289 
290             /**
291              * Whether one of the matched elements has specified property name
292              * @param {Array<HTMLElement>|String|HTMLElement} selector 元素
293              * @param {String} name The name of property to test
294              * @return {Boolean}
295              */
296             hasProp:function (selector, name) {
297                 var elems = DOM.query(selector);
298                 for (var i = 0; i < elems.length; i++) {
299                     var el = elems[i];
300                     if (getProp(el, name) !== undefined) {
301                         return true;
302                     }
303                 }
304                 return false;
305             },
306 
307             /**
308              * Remove a property for the set of matched elements.
309              * @param {Array<HTMLElement>|String|HTMLElement} selector matched elements
310              * @param {String} name The name of the property to remove.
311              */
312             removeProp:function (selector, name) {
313                 name = propFix[ name ] || name;
314                 var elems = DOM.query(selector);
315                 for (var i = elems.length - 1; i >= 0; i--) {
316                     var el = elems[i];
317                     try {
318                         el[ name ] = undefined;
319                         delete el[ name ];
320                     } catch (e) {
321                         // S.log("delete el property error : ");
322                         // S.log(e);
323                     }
324                 }
325             },
326 
327             /**
328              * Get the value of an attribute for the first element in the set of matched elements.
329              * or
330              * Set one or more attributes for the set of matched elements.
331              * @param {HTMLElement[]|HTMLElement|String|Element} selector matched elements
332              * @param {String|Object} name The name of the attribute to set. or A map of attribute-value pairs to set.
333              * @param [val] A value to set for the attribute.
334              * @returns {String}
335              */
336             attr:function (selector, name, val, pass) {
337                 /*
338                  Hazards From Caja Note:
339 
340                  - In IE[67], el.setAttribute doesn't work for attributes like
341                  'class' or 'for'.  IE[67] expects you to set 'className' or
342                  'htmlFor'.  Caja use setAttributeNode solves this problem.
343 
344                  - In IE[67], <input> elements can shadow attributes.  If el is a
345                  form that contains an <input> named x, then el.setAttribute(x, y)
346                  will set x's value rather than setting el's attribute.  Using
347                  setAttributeNode solves this problem.
348 
349                  - In IE[67], the style attribute can only be modified by setting
350                  el.style.cssText.  Neither setAttribute nor setAttributeNode will
351                  work.  el.style.cssText isn't bullet-proof, since it can be
352                  shadowed by <input> elements.
353 
354                  - In IE[67], you can never change the type of an <button> element.
355                  setAttribute('type') silently fails, but setAttributeNode
356                  throws an exception.  caja : the silent failure. KISSY throws error.
357 
358                  - In IE[67], you can never change the type of an <input> element.
359                  setAttribute('type') throws an exception.  We want the exception.
360 
361                  - In IE[67], setAttribute is case-sensitive, unless you pass 0 as a
362                  3rd argument.  setAttributeNode is case-insensitive.
363 
364                  - Trying to set an invalid name like ":" is supposed to throw an
365                  error.  In IE[678] and Opera 10, it fails without an error.
366                  */
367 
368                 var els = DOM.query(selector);
369 
370                 // supports hash
371                 if (S.isPlainObject(name)) {
372                     pass = val;
373                     for (var k in name) {
374                         if (name.hasOwnProperty(k)) {
375                             DOM.attr(els, k, name[k], pass);
376                         }
377                     }
378                     return undefined;
379                 }
380 
381                 if (!(name = S.trim(name))) {
382                     return undefined;
383                 }
384 
385                 // attr functions
386                 if (pass && attrFn[name]) {
387                     return DOM[name](selector, val);
388                 }
389 
390                 // scrollLeft
391                 name = name.toLowerCase();
392 
393                 if (pass && attrFn[name]) {
394                     return DOM[name](selector, val);
395                 }
396 
397                 // custom attrs
398                 name = attrFix[name] || name;
399 
400                 var attrNormalizer,
401                     el = els[0],
402                     ret;
403 
404                 if (rboolean.test(name)) {
405                     attrNormalizer = boolHook;
406                 }
407                 // only old ie?
408                 else if (rinvalidChar.test(name)) {
409                     attrNormalizer = attrNodeHook;
410                 } else {
411                     attrNormalizer = attrHooks[name];
412                 }
413 
414 
415                 if (val === undefined) {
416                     if (el && el.nodeType === DOM.ELEMENT_NODE) {
417                         // browsers index elements by id/name on forms, give priority to attributes.
418                         if (nodeName(el) == "form") {
419                             attrNormalizer = attrNodeHook;
420                         }
421                         if (attrNormalizer && attrNormalizer.get) {
422                             return attrNormalizer.get(el, name);
423                         }
424 
425                         ret = el.getAttribute(name);
426 
427                         // standard browser non-existing attribute return null
428                         // ie<8 will return undefined , because it return property
429                         // so norm to undefined
430                         return ret === null ? undefined : ret;
431                     }
432                 } else {
433                     for (var i = els.length - 1; i >= 0; i--) {
434                         el = els[i];
435                         if (el && el.nodeType === DOM.ELEMENT_NODE) {
436                             if (nodeName(el) == "form") {
437                                 attrNormalizer = attrNodeHook;
438                             }
439                             if (attrNormalizer && attrNormalizer.set) {
440                                 attrNormalizer.set(el, val, name);
441                             } else {
442                                 // convert the value to a string (all browsers do this but IE)
443                                 el.setAttribute(name, EMPTY + val);
444                             }
445                         }
446                     }
447                 }
448                 return undefined;
449             },
450 
451             /**
452              * Remove an attribute from each element in the set of matched elements.
453              * @param {Array<HTMLElement>|String} selector matched elements
454              * @param {String} name An attribute to remove
455              */
456             removeAttr:function (selector, name) {
457                 name = name.toLowerCase();
458                 name = attrFix[name] || name;
459                 var els = DOM.query(selector), el, i;
460                 for (i = els.length - 1; i >= 0; i--) {
461                     el = els[i];
462                     if (el.nodeType == DOM.ELEMENT_NODE) {
463                         var propName;
464                         el.removeAttribute(name);
465                         // Set corresponding property to false for boolean attributes
466                         if (rboolean.test(name) && (propName = propFix[ name ] || name) in el) {
467                             el[ propName ] = false;
468                         }
469                     }
470                 }
471             },
472 
473             /**
474              * Whether one of the matched elements has specified attribute
475              * @function
476              * @param {Array<HTMLElement>|String} selector matched elements
477              * @param {String} name The attribute to be tested
478              * @returns {Boolean}
479              */
480             hasAttr:oldIE ?
481                 function (selector, name) {
482                     name = name.toLowerCase();
483                     var elems = DOM.query(selector);
484                     // from ppk :http://www.quirksmode.org/dom/w3c_core.html
485                     // IE5-7 doesn't return the value of a style attribute.
486                     // var $attr = el.attributes[name];
487                     for (var i = 0; i < elems.length; i++) {
488                         var el = elems[i];
489                         var $attr = el.getAttributeNode(name);
490                         if ($attr && $attr.specified) {
491                             return true;
492                         }
493                     }
494                     return false;
495                 }
496                 :
497                 function (selector, name) {
498                     var elems = DOM.query(selector);
499                     for (var i = 0; i < elems.length; i++) {
500                         var el = elems[i];
501                         //使用原生实现
502                         if (el.hasAttribute(name)) {
503                             return true;
504                         }
505                     }
506                     return false;
507                 },
508 
509             /**
510              * Get the current value of the first element in the set of matched elements.
511              * or
512              * Set the value of each element in the set of matched elements.
513              * @param {Array<HTMLElement>|String} selector matched elements
514              * @param {String|Array<String>} [value] A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked.
515              * @returns {undefined|String|Array<String>|Number}
516              */
517             val:function (selector, value) {
518                 var hook, ret;
519 
520                 //getter
521                 if (value === undefined) {
522 
523                     var elem = DOM.get(selector);
524 
525                     if (elem) {
526                         hook = valHooks[ nodeName(elem) ] || valHooks[ elem.type ];
527 
528                         if (hook && "get" in hook && (ret = hook.get(elem, "value")) !== undefined) {
529                             return ret;
530                         }
531 
532                         ret = elem.value;
533 
534                         return typeof ret === "string" ?
535                             // handle most common string cases
536                             ret.replace(rreturn, "") :
537                             // handle cases where value is null/undefined or number
538                             ret == null ? "" : ret;
539                     }
540 
541                     return undefined;
542                 }
543 
544                 var els = DOM.query(selector), i;
545                 for (i = els.length - 1; i >= 0; i--) {
546                     elem = els[i];
547                     if (elem.nodeType !== 1) {
548                         return undefined;
549                     }
550 
551                     var val = value;
552 
553                     // Treat null/undefined as ""; convert numbers to string
554                     if (val == null) {
555                         val = "";
556                     } else if (typeof val === "number") {
557                         val += "";
558                     } else if (S.isArray(val)) {
559                         val = S.map(val, function (value) {
560                             return value == null ? "" : value + "";
561                         });
562                     }
563 
564                     hook = valHooks[ nodeName(elem)] || valHooks[ elem.type ];
565 
566                     // If set returns undefined, fall back to normal setting
567                     if (!hook || !("set" in hook) || hook.set(elem, val, "value") === undefined) {
568                         elem.value = val;
569                     }
570                 }
571                 return undefined;
572             },
573 
574             _propHooks:propHooks,
575 
576             /**
577              * Get the combined text contents of each element in the set of matched elements, including their descendants.
578              * or
579              * Set the content of each element in the set of matched elements to the specified text.
580              * @param {HTMLElement[]|HTMLElement|String} selector matched elements
581              * @param {String} [val] A string of text to set as the content of each matched element.
582              * @returns {String|undefined}
583              */
584             text:function (selector, val) {
585                 // getter
586                 if (val === undefined) {
587                     // supports css selector/Node/NodeList
588                     var el = DOM.get(selector);
589 
590                     // only gets value on supported nodes
591                     if (el.nodeType == DOM.ELEMENT_NODE) {
592                         return el[TEXT] || EMPTY;
593                     }
594                     else if (el.nodeType == DOM.TEXT_NODE) {
595                         return el.nodeValue;
596                     }
597                     return undefined;
598                 }
599                 // setter
600                 else {
601                     var els = DOM.query(selector), i;
602                     for (i = els.length - 1; i >= 0; i--) {
603                         el = els[i];
604                         if (el.nodeType == DOM.ELEMENT_NODE) {
605                             el[TEXT] = val;
606                         }
607                         else if (el.nodeType == DOM.TEXT_NODE) {
608                             el.nodeValue = val;
609                         }
610                     }
611                 }
612                 return undefined;
613             }
614         });
615     if (1 > 2) {
616         DOM.removeProp("j", "1");
617     }
618     return DOM;
619 }, {
620     requires:["./base", "ua"]
621 });
622 /**
623  * NOTES:
624  * yiminghe@gmail.com:2011-06-03
625  *  - 借鉴 jquery 1.6,理清 attribute 与 property
626  *
627  * yiminghe@gmail.com:2011-01-28
628  *  - 处理 tabindex,顺便重构
629  *
630  * 2010.03
631  *  - 在 jquery/support.js 中,special attrs 里还有 maxlength, cellspacing,
632  *    rowspan, colspan, useap, frameboder, 但测试发现,在 Grade-A 级浏览器中
633  *    并无兼容性问题。
634  *  - 当 colspan/rowspan 属性值设置有误时,ie7- 会自动纠正,和 href 一样,需要传递
635  *    第 2 个参数来解决。jQuery 未考虑,存在兼容性 bug.
636  *  - jQuery 考虑了未显式设定 tabindex 时引发的兼容问题,kissy 里忽略(太不常用了)
637  *  - jquery/attributes.js: Safari mis-reports the default selected
638  *    property of an option 在 Safari 4 中已修复。
639  *
640  */