mass Framework attr模块
属性在这里只是一个统称,它对应两个术语attribute与property。attribute是指用户通过setAttribute设置的自定义属性,其值只能是字符串,如果没有显式定义,用getAttribute取值为undefined。property是指元素固有属性,像title, className,tabIndex, src, checked等等。这些属性的值可以是五花八门,如className总是字符串,tabIndex总是数字(它只限表单元素与个别能获取焦点的元素),表单元素的form属性总是指向其外围的表单对象(当然如果它没有放在form标签之内是不存在的), checked, disabled , defer等属性,如果此元素支持它,总总是返回布尔值。这些固有属性,如果没有显示定义,基本上是返回null(如果className, value则是字符串)。
document.body.className = function(){}
alert(typeof document.body.className )//无法重写,返回"string"
如何处理这些庞杂的属性就是本模块要做的事情。它拥有如下四类方法。
val用于设置获取表单元素的value值,如果是普通元素则内部转交prop方法去处理。
hasClass, addClass, removeClass, toggleClass, replaceClass用于判定,添加, 移除, 切换与替换元素节点的className属性。className是一个特别的属性,它对应由多个类名组成的字符串,因此从prop独立出来区别对待。
attr,removeAttr用于处理元素节点的自定义属性,如果发现对象是window,则转交prop方法处理。
prop,removeProp用于固有属性。在下面给出的JS代码中,给出两份清单,一份是用于属性名映射,如著名的class变成className,for变成htmlFor,rowspan变成rowSpan....另一个是布尔属性,如async,autofocus,checked....它们比jquery源码那份更齐全了。
val, attr, prop 都支配着复数个适配器,确保能取得正确的值。
;
(function(global,DOC){
var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
dom.define("attr","node", function(){
var rclass = /(^|\s)(\S+)(?=\s(?:\S+\s)*\2(?:\s|$))/g,
rfocusable = /^(?:button|input|object|select|textarea)$/i,
rclickable = /^a(?:rea)?$/i,
rspaces = /\s+/,
support = dom.support,
nodeAdapter,
valOne = {
"SELECT":"select",
"OPTION":"option",
"BUTTON":"button"
},
getValType = function(node){
return "form" in node && (valOne[node.tagName] || node.type)
}
//Breast Expansion - Kate beim Arzt
dom.implement({
/**
* 为所有匹配的元素节点添加className,添加多个className要用空白隔开
* 如dom("body").addClass("aaa");dom("body").addClass("aaa bbb");
* <a href="http://www.cnblogs.com/rubylouvre/archive/2011/01/27/1946397.html">相关链接</a>
*/
addClass:function(value){
if ( typeof value == "string") {
for ( var i = 0, el; el = this[i++]; ) {
if ( el.nodeType === 1 ) {
if ( !el.className ) {
el.className = value;
} else {
el.className = (el.className +" "+value).replace(rclass,"")
}
}
}
}
return this;
},
//如果第二个参数为true,则只判定第一个是否存在此类名,否则对所有元素进行操作;
hasClass: function( value, every ) {
var method = every === true ? "every" : "some"
var rclass = new RegExp('(\\s|^)'+value+'(\\s|$)');//判定多个元素,正则比indexOf快点
return dom.slice(this)[method](function(el){
return "classList" in el ? el.classList.contains(value):
(el.className || "").match(rclass);
});
},
//如果不传入类名,则去掉所有类名,允许传入多个类名
removeClass: function( value ) {
if ( (value && typeof value === "string") || value === undefined ) {
var classNames = (value || "").split( rspaces );
for ( var i = 0, node; node = this[i++]; ) {
if ( node.nodeType === 1 && node.className ) {
if ( value ) {
var className = (" " + node.className + " ").replace(rspaces, " ");
for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
className = className.replace(" " + classNames[c] + " ", " ");
}
node.className = className.trim();
} else {
node.className = "";
}
}
}
}
return this;
},
//如果存在(不存在)就删除(添加)一个类。对所有匹配元素进行操作。
toggleClass:function(value){
var classNames = value.split(rspaces ), i, className;
var type = typeof value
return this.each(function(el) {
i = 0;
if(el.nodeType === 1){
var self = dom(el);
if(type == "string" ){
while ( (className = classNames[ i++ ]) ) {
self[ self.hasClass( className ) ? "removeClass" : "addClass" ]( className );
}
} else if ( type === "undefined" || type === "boolean" ) {
if ( el.className ) {
self._data( "__className__", el.className );
}
el.className = el.className || value === false ? "" : self.data( "__className__") || "";
}
}
});
},
//如果匹配元素存在old类名则将其改应neo类名
replaceClass:function(old, neo){
for ( var i = 0, node; node = this[i++]; ) {
if ( node.nodeType === 1 && node.className ) {
var arr = node.className.split(rspaces), arr2 = [];
for (var j = 0; j<arr.length; j++) {
arr2.push(arr[j] != old ? arr[j] : neo);
}
node.className = arr2.join(" ");
}
}
return this;
},
val: function( value ) {
var node = this[0], adapter = dom.valAdapter;
if ( !arguments.length ) {//读操作
if ( node && node.nodeType == 1 ) {
//处理select-multiple, select-one,option,button
var ret = (adapter[ getValType(node)+":get" ] || dom.propAdapter[ "_default:get" ])(node, "value");
return ret || ""
}
return undefined;
}
//强制将null/undefined转换为"", number变为字符串
if(Array.isArray(value)){
value = value.map(function (value) {
return value == null ? "" : value + "";
});
}else if(isFinite(value)){
value += "";
}else{
value = value || "";//强制转换为数组
}
return this.each(function(node) {//写操作
if ( node.nodeType == 1 ) {
(adapter[ getValType(node)+":set" ] || dom.propAdapter[ "_default:set" ])(node, "value",value);
}
});
},
removeAttr: function( name ) {
name = dom.attrMap[ name ] || name;
var isBool = boolOne[name];
return this.each(function(node) {
if(node.nodeType === 1){
dom["@remove_attr"]( node, name, isBool );
}
});
},
removeProp: function( name ) {
name = dom.propMap[ name ] || name;
return this.each(function() {
// try/catch handles cases where IE balks (such as removing a property on window)
try {
this[ name ] = undefined;
delete this[ name ];
} catch( e ) {}
});
}
});
dom.extend({
attrMap:{
tabindex: "tabIndex"
},
propMap:{
"accept-charset": "acceptCharset",
"char": "ch",
charoff: "chOff",
"class": "className",
"for": "htmlFor",
"http-equiv": "httpEquiv"
},
//内部函数,原则上拒绝用户的调用
"@remove_attr": function( node, name, isBool ) {
var propName;
name = dom.attrMap[ name ] || name;
//如果支持removeAttribute,则使用removeAttribute
dom.attr( node, name, "" );
node.removeAttribute( name );
// 确保bool属性的值为bool
if ( isBool && (propName = dom.propMap[ name ] || name) in node ) {
node[ propName ] = false;
}
},
attrAdapter: {//最基本的适配器
"_default:get":function(node,name){
var ret = node.getAttribute( name ) ;
return ret === null ? undefined : ret;
},
"_default:set":function(node, name, value){
//注意,属性名不能为数字,在FF下会报错String contains an invalid character" code: "5
node.setAttribute( name, "" + value )
},
"tabIndex:get":function( node ) {
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
var attributeNode = node.getAttributeNode( "tabIndex" );
return attributeNode && attributeNode.specified ?
parseInt( attributeNode.value, 10 ) :
rfocusable.test(node.nodeName) || rclickable.test(node.nodeName) && node.href ? 0 : undefined;
}
},
propAdapter:{
"_default:get":function(node,name){
return node[ name ]
},
"_default:set":function(node, name, value){
node[ name ] = value;
},
"value:set":function(node, name, value){
return dom.fn.val.call([node],value) ;
},
"value:get":function(node){
return dom.fn.val.call([node])
}
},
valAdapter: {
"option:get": function( elem ) {
var val = elem.attributes.value;
return !val || val.specified ? elem.value : elem.text;
},
"select:get": function( node ) {
var index = node.selectedIndex, values = [],options = node.options, value;
var one = !node.multiple
if ( index < 0 ) {
return null;
}
for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
var option = options[ i ];
if ( option.selected ) {
value = option.value || option.text;
if ( one ) {
return value;
}
values.push( value );
}
}
if(one && !value.length && node.length){
return dom(options[index]).val();
}
return values;
},
"select:set": function( node, name,values ) {
dom.lang(node.options).forEach(function(el){
el.selected = values.indexOf(el.value || el.text) >= 0;
});
if ( !values.length ) {
node.selectedIndex = -1;
}
return values;
}
}
});
//attr方法只能用于元素节点,只能写入字符串,只能取得字符串或undefined
//prop方法只能用于原生的事件发送器,能写入除flase,undefined之外的各种数据,如果是bool属性必返回bool值
//attr是用于模拟set/getAttribute操作,用于设置或取得对象的自定义属性
//prop是用于模拟obj[name]操作,用于设置或取得对象的固有属性
"attr,prop".replace(dom.rword,function(method){
dom[method] = function( node, name, value ) {
if(node && (dom["@emitter"] in node || method == "prop")){
if ( !("getAttribute" in node) ) {
method = "prop";
}
var notxml = !node.nodeType || !dom.isXML(node);
//对于HTML元素节点,我们需要对一些属性名进行映射
name = notxml && dom[method+"Map"][ name ] || name;
var adapter = dom[method+"Adapter"];
if(method === "attr" && node.tagName){
//比如input的readonly的取值可以为true false readonly
if(boolOne[name] && (typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase())) {
name = dom.propMap[name] || name;
adapter = boolAdapter;
}else if(nodeAdapter ){
adapter = nodeAdapter;
}
}
if ( value !== void 0 ){
if( method === "attr" && value === null){ //移除属性
return dom["@remove_"+method]( node, name );
}else{ //设置属性
return (notxml && adapter[name+":set"] || adapter["_default:set"])( node, name, value );
}
} //获取属性
return (notxml && adapter[name+":get"] || adapter["_default:get"])( node, name );
}
};
dom.fn[method] = function( name, value ) {
return dom.access( this, name, value, dom[method], dom[method]);
}
});
//========================propAdapter 的相关补充==========================
var prop = "accessKey,allowTransparency,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan,contentEditable,"+
"dateTime,defaultChecked,defaultSelected,defaultValue,frameBorder,isMap,longDesc,maxLength,marginWidth,marginHeight,"+
"noHref,noResize,noShade,readOnly,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign";
prop.replace(dom.rword, function(name){
dom.propMap[name.toLowerCase()] = name;
});
//safari IE9 IE8 我们必须访问上一级元素时,才能获取这个值
if ( !support.attrSelected ) {
dom.propAdapter[ "selected:get" ] = function( parent ) {
var node = parent
for(var i= -3;i++; parent.selectedIndex, parent = parent.parentNode){};
return node.selected;
}
}
//========================attrAdapter 的相关补充==========================
if ( !support.attrHref ) {
//IE的getAttribute支持第二个参数,可以为 0,1,2,4
//0 是默认;1 区分属性的大小写;2取出源代码中的原字符串值(注,IE67对动态创建的节点没效)。
//IE 在取 href 的时候默认拿出来的是绝对路径,加参数2得到我们所需要的相对路径。
"href,src,width,height,colSpan,rowSpan".replace(dom.rword,function(method ) {
dom.attrAdapter[ method + ":get" ] = function( node,name ) {
var ret = node.getAttribute( name, 2);
return ret === null ? undefined : ret;
}
});
}
//IE67是没有style特性(特性的值的类型为文本),只有el.style(CSSStyleDeclaration)(bug)
if ( !support.attrStyle ) {
dom.attrAdapter[ "style:get" ] = function( node ) {
return node.style.cssText.toLowerCase();
}
dom.attrAdapter[ "style:set" ] = function( node, value ) {
return (node.style.cssText = "" + value);
}
}
//=========================valAdapter 的相关补充==========================
//checkbox的value默认为on,唯有Chrome 返回空字符串
if ( !support.checkOn ) {
"radio,checkbox".replace(dom.rword,function(name) {
dom.valAdapter[ name + ":get" ] = function( node ) {
return node.getAttribute("value") === null ? "on" : node.value;
}
});
}
//处理单选框,复选框在设值后checked的值
"radio,checkbox".replace(dom.rword,function(name) {
dom.valAdapter[ name + ":set" ] = function( node, name, value) {
if ( Array.isArray( value ) ) {
return node.checked = !!~value.indexOf(node.value ) ;
}
}
});
//=========================nodeAdapter 的相关补充=========================
//如果我们不能通过el.getAttribute("class")取得className,必须使用el.getAttribute("className")
if(!support.attrProp){
dom.attrMap = dom.propMap;
var fixSpecified = dom.oneObject("name,id")
//注意formElement[name] 相等于formElement.elements[name],会返回其辖下的表单元素
nodeAdapter = dom.mix({
"_default:get": function( node, name ) {
var ret = node.getAttributeNode( name );
return ret && (fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified) ? ret.nodeValue : undefined;
},
"_default:set": function( node, name, value ) {
var ret = node.getAttributeNode( name );
if ( !ret ) {
ret = node.ownerDocument.createAttribute( name );
node.setAttributeNode( ret );
}
return (ret.nodeValue = value + "");
}
},dom.attrAdapter,false);
"width,height".replace(dom.rword,function(attr){
nodeAdapter[attr+":set"] = function(node, name, value){
node.setAttribute( attr, value === "" ? "auto" : value+"");
}
});
dom.valAdapter["button:get"] = nodeAdapter["_default:get"];
dom.valAdapter["button:set"] = nodeAdapter["_default:set"];
}
//=========================boolAdapter 的相关补充=========================
var boolAdapter = dom.mix({},dom.attrAdapter,false);
var bools = "async,autofocus,checked,declare,disabled,defer,defaultChecked,contentEditable,ismap,loop,multiple,noshade,noresize,readOnly,selected"
var boolOne = dom.oneObject( support.attrProp ? bools.toLowerCase() : bools );
bools.replace(dom.rword,function(method) {
//bool属性在attr方法中只会返回与属性同名的值或undefined
boolAdapter[method+":get"] = function(node,name){
var attrNode;
return dom.prop( node, name ) === true || ( attrNode = node.getAttributeNode( name ) ) && attrNode.nodeValue !== false ?
name.toLowerCase() :
undefined;
}
boolAdapter[method+":set"] = function(node,name,value){
if ( value === false ) {//如果设置为false,直接移除,如设置input的readOnly为false时,相当于没有只读限制
dom["@remove_attr"]( node, name, true );
} else {
if ( name in node ) {
node[ name ] = true;
}
node.setAttribute( name, name.toLowerCase() );
}
return name;
}
});
});
})(this, this.document);
/*
2011.8.2
将prop从attr分离出来
添加replaceClass方法
2011.8.5
重构val方法
2011.8.12
重构replaceClass方法
2011.10.11
重构attr prop方法
**/
浙公网安备 33010602011771号