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

 **/

你可能感兴趣的:(framework)