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 */