Ruby's Louvre

每天学习一点点算法

导航

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

posted on 2011-08-15 10:16  司徒正美  阅读(1787)  评论(1编辑  收藏  举报