自己写一个JS单向数据流动库----one way binding

  JS单向流动其实就是数据到视图的过程, 这几天突发奇想,想着弄一个插件, 把DOM结构使用JS进行描述;

  因为DOM中的Class , content, id, attribute, 事件, 子元素全部通过JS进行描述, 最后把这些元素拼接在一起, 生成一个DOM树, 如果有些DOM数是可以复用的,我们就把他包装成组件(Component), 方便复用, 但是使用结构化的JS描述DOM并不形象, 不如直接使用HTML生成,如果能够准确把握JS结构, 然后封装各个JS组件, 能够极大的降低耦合, 让代码逻辑更加清楚。

  因为DOM是树形结构, 必须有子元素, 所以要迭代渲染, 生成各种各样的结构, 为了减低耦合度, 使用了自定义事件, 让元素之间的关系变低;

  为了简化model到view的过程, 复用underscore的template, 结合model, 自动生成view, 自动渲染;

  因为存在组件这个玩意儿, 我们复用jQuery 的extend方法, 深度复制组件的属性, 让Component组件之间不会相互影响( 因为使用原型继承的话, 修改单个组件的属性值会导致所有复用该组件的组件属性值发生改变);

  视觉模型如下:

  整体源代码如下:

(function() {
    window.util = {};

    util.shallowClone =  function (obj) {
        var c = Object.create(Object.getPrototypeOf(obj));
        Object.getOwnPropertyNames(obj).forEach(function (k) {
            return c[k] = obj[k];
        });
        return c;
    };

    //class操作;
    util.hasClass = function(e, arg) {
        return e.className.indexOf(arg)!==-1 ? true : false;
    };

    //添加class;
    util.addClass = function(e, arg) {
        if( !util.hasClass(e, arg) ) {
            e.className = e.className+" "+arg;
        };
    };

    //删除class
    util.removeClass = function(e, arg) {
        if(!arg) {
            e.className = "";
        }else{
            if( !util.hasClass(e, arg) )return;
            if(e.className.indexOf( arg )!=-1) {
                if( e.className.split(" ").indexOf( arg ) !== -1) {
                    e.className = e.className.replace(new RegExp(arg,"gi"), "");
                };
            };
        };
    };

    //匹配className匹配的父级节点;
    util.closest = function (obj, className ) {
        if(!obj||!className)return;
        if(obj.nodeName.toLowerCase() === "body") return;
        if( util.hasClass(obj.parentNode, className) ) {
            return obj.parentNode;
        }else{
            return util.closest(obj.parentNode, className);
        };
    };

    //underscore抄的模板引擎;
    var escaper = /\|'|
|
|	|u2028|u2029/g;

    var escapes = {
        "'":      "'",
        '\':     '\',
        '
':     'r',
        '
':     'n',
        '	':     't',
        'u2028': 'u2028',
        'u2029': 'u2029'
    };

    util.templateSettings = {
        evaluate    : /<%([sS]+?)%>/g,
        interpolate : /<%=([sS]+?)%>/g,
        escape      : /<%-([sS]+?)%>/g
    };

    util.template = function(text, data) {
        var render;
        settings = util.templateSettings;

        // Combine delimiters into one regular expression via alternation.
        var matcher = new RegExp([
            (settings.escape || noMatch).source,
            (settings.interpolate || noMatch).source,
            (settings.evaluate || noMatch).source
        ].join('|') + '|$', 'g');

        // Compile the template source, escaping string literals appropriately.
        var index = 0;
        var source = "__p+='";
        text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
            source += text.slice(index, offset)
                .replace(escaper, function(match) { return '\' + escapes[match]; });

            if (escape) {
                source += "'+
((__t=(" + escape + "))==null?'':_.escape(__t))+
'";
            }
            if (interpolate) {
                source += "'+
((__t=(" + interpolate + "))==null?'':__t)+
'";
            }
            if (evaluate) {
                source += "';
" + evaluate + "
__p+='";
            }
            index = offset + match.length;
            return match;
        });
        source += "';
";

        // If a variable is not specified, place data values in local scope.
        if (!settings.variable) source = 'with(obj||{}){
' + source + '}
';

        source = "var __t,__p='',__j=Array.prototype.join," +
            "print=function(){__p+=__j.call(arguments,'');};
" +
            source + "return __p;
";

        try {
            render = new Function(settings.variable || 'obj', '_', source);
        } catch (e) {
            e.source = source;
            throw e;
        }

        var template = function(data) {
            return render.call(this, data);
        };

        // Provide the compiled function source as a convenience for precompilation.
        template.source = 'function(' + (settings.variable || 'obj') + '){
' + source + '}';

        return template;
    };

    /**
     * @desc 从jQuery里面拷贝了一个extends;
     * @desc 当第一个参数为boolean值时候,可以实现深度继承;
     * @param (boolean, result, obj)
     * @param (result, obj, obj, obj)
     * @return result;
     */
    util.cloneProps = function () {
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false,
            isArray = function( arr ){
                return Object.prototype.toString.call( arr ) === "[object Array]";
            },
            core_hasOwn = {}.hasOwnProperty,
            isPlainObject = function( obj ) {
                if ( !obj || (typeof obj !== "object") || obj.nodeType ) {
                    return false;
                }

                try {
                    // Not own constructor property must be Object
                    if ( obj.constructor &&
                        !core_hasOwn.call(obj, "constructor") &&
                        !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
                        return false;
                    }
                } catch ( e ) {
                    // IE8,9 Will throw exceptions on certain host objects #9897
                    return false;
                }

                // Own properties are enumerated firstly, so to speed up,
                // if last one is own, then all properties are own.

                var key;
                for ( key in obj ) {}

                return key === undefined || core_hasOwn.call( obj, key );
            };
        // Handle a deep copy situation
        if ( typeof target === "boolean" ) {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
        };

        // Handle case when target is a string or something (possible in deep copy)
        if ( typeof target !== "object" && typeof target !== "function" ) {
            target = {};
        }

        // extend jQuery itself if only one argument is passed
        if ( length === i ) {
            target = this;
            --i;
        }

        for ( ; i < length; i++ ) {
            // Only deal with non-null/undefined values
            if ( (options = arguments[ i ]) != null ) {
                // Extend the base object
                for ( name in options ) {
                    src = target[ name ];
                    copy = options[ name ];

                    // Prevent never-ending loop
                    if ( target === copy ) {
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if ( deep && copy && ( isPlainObject(copy) || (copyIsArray =  isArray(copy) ) )) {
                        if ( copyIsArray ) {
                            copyIsArray = false;
                            clone = src && isArray(src) ? src : [];

                        } else {
                            clone = (src && (typeof src === "object")) ? src : {};
                        }

                        // Never move original objects, clone them
                        target[ name ] = util.cloneProps( deep, clone, copy );

                        // Don't bring in undefined values
                    } else if ( copy !== undefined ) {
                        target[ name ] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    };

    //EventBase;
    /**
     * @example
     var obj = Object.create( new EventBase )
     obj.addListener("click", function(type) {
            console.log(type)
         })
     obj.fireEvent("click");
     * */
    var EventBase = function () {};

    EventBase.prototype = {
        /**
         * 注册事件监听器
         * @name addListener
         * @grammar editor.addListener(types,fn)  //types为事件名称,多个可用空格分隔
         * @example
         * })
         * editor.addListener('beforegetcontent aftergetcontent',function(type){
             *         if(type == 'beforegetcontent'){
             *             //do something
             *         }else{
             *             //do something
             *         }
             *         console.log(this.getContent) // this是注册的事件的编辑器实例
             * })
         */
        addListener:function (types, listener) {
            types = types.split(' ');
            for (var i = 0, ti; ti = types[i++];) {
                if(typeof listener === "function") {
                    getListener(this, ti, true).push(listener);
                }else{
                    for(var j=0 ;j<listener.length; j++) {
                        getListener(this, ti, true).push(listener[j]);
                    };
                };
            };
        },

        /**
         * 移除事件监听器
         * @name removeListener
         * @grammar editor.removeListener(types,fn)  //types为事件名称,多个可用空格分隔
         * @example
         * //changeCallback为方法体
         */
        removeListener:function (types, listener) {
            types = types.trim().split(' ');
            for (var i = 0, ti; ti = types[i++];) {
                removeItem(getListener(this, ti) || [], listener);
            }
        },

        /**
         * 触发事件
         * @name fireEvent
         * @grammar
         * @example
         */
        fireEvent:function () {
            var types = arguments[0];
            types = types.trim().split(' ');
            for (var i = 0, ti; ti = types[i++];) {
                var listeners = getListener(this, ti),
                    r, t, k;
                if (listeners) {
                    k = listeners.length;
                    while (k--) {
                        if(!listeners[k])continue;
                        t = listeners[k].apply(this, arguments);
                        if(t === true){
                            return t;
                        }
                        if (t !== undefined) {
                            r = t;
                        }
                    }
                }
                if (t = this['on' + ti.toLowerCase()]) {
                    r = t.apply(this, arguments);
                }
            }
            return r;
        }
    };
    /**
     * 获得对象所拥有监听类型的所有监听器
     * @public
     * @function
     * @param {Object} obj  查询监听器的对象
     * @param {String} type 事件类型
     * @param {Boolean} force  为true且当前所有type类型的侦听器不存在时,创建一个空监听器数组
     * @returns {Array} 监听器数组
     */
    function getListener(obj, type, force) {
        var allListeners;
        type = type.toLowerCase();
        return ( ( allListeners = ( obj.__allListeners || force && ( obj.__allListeners = {} ) ) )
            && ( allListeners[type] || force && ( allListeners[type] = [] ) ) );
    };

    function removeItem(array, item) {
        for (var i = 0, l = array.length; i < l; i++) {
            if (array[i] === item) {
                array.splice(i, 1);
                i--;
            };
        };
    };

    /**
     * 继承的基本类;
     * */
    var __extends = (this && this.__extends) || function (d, b) {
        for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
        function __() {
            this.constructor = d;
        }

        __.prototype = b.prototype;
        d.prototype = new __();
    };

    var nono =  {};
    /**
     * 组件
     * */
    nono.Dom = function( opt ) {
        opt = opt || {};
        //继承eventBase;
        EventBase.apply(this, arguments);
        this.doc = opt&&opt.doc || document;
        this.opt = opt || {};
    };
    //继承EventBase的原型;
    __extends( nono.Dom, EventBase);

    /**
     * @desc 绑定自定义事件, Dom初始化即可绑定自定义事件;;
     *
     * */
    nono.Dom.prototype.initEmiter = function (evs) {
        for(var e in evs) {
            this.addListener(e, evs[e]);
        };
    };

    /**
     * 主逻辑, 渲染界面;
     * @param 虚拟DOM
     * @param 目标节点
     * @param true的是时候不会绑定事件和属性
     * @return 虚拟DOM
     * */
    nono.Dom.prototype.render = function( vEl, tar , flag) {
        if( tar ) {
            //把目标内部的所有节点删除;
            this._assignChildren( tar );
        };
        return this._render( vEl, tar ,flag);
    };

    /**
     * @desc 更新dom的时候调用改方法;
     * */
    nono.Dom.prototype.update = function ( tar ) {
        if( tar ) {
            //把目标内部的所有节点删除;
            this._assignChildren( tar );
        };
        this.render(this.vEl, tar , true);
    };

    /**
     * @desc 迭代并生成子元素;
     * @return void;
     * */
    nono.Dom.prototype.renderKids = function ( kids, tar ) {
        for(var i=0 ,len = kids.length; i< len ;i++ ) {
            var dom = new nono.Dom();
            //dom.render(kids[i], tar);
            //this._render( kids[i] , tar);
            dom._render(kids[i], tar);
        };
    };

    /**
     * @desc 内部用的渲染;
     * @param 虚拟DOM
     * @param 目标节点
     * @param true的是时候不会绑定事件和属性
     * */
    nono.Dom.prototype._render = function(  vEl, tar , flag) {
        //缓存虚拟元素和目标节点;
        if(vEl) this.vEl = vEl;
        if(tar) this.tar = tar;

        var nNode, tag;
        //初始化要渲染到的父级节点;
        tar = (tar&&tar.nodeType === 1 ? tar : undefined );

        //如果是字符串的话
        this.fireEvent("beforerender", tar);
        if( typeof vEl === "string" || typeof vEl === "number" ) {

            var string = "";
            try{
                string = util.template( vEl )( tar&&tar.dom&&tar.dom.vEl&&tar.dom.vEl.model );
            }catch(e) {
                string = "util.template string error";
            };
            nNode = document.createTextNode( string );

            //如果是一个可以渲染的组件
        }else if( typeof vEl === "object" && vEl.Class ){

            //通过组件渲染; 组件渲染属于迭代渲染, 会自动渲染子组件;
            //生成新元素, 该元素要添加到目标节点中;
            nNode = this.addComponent( vEl );

            //如果只是一个单纯的对象, 我们认为这是一个元素;
        }else if( typeof vEl === "object" ) {

            //tag的名称;
            tag = vEl.name || "div";
            nNode = document.createElement( tag );

            //绑定属性, 事件, 自定义事件;
            if( !flag ) {
                this._assignProps( nNode, vEl&&vEl.model );
            };
            nNode.dom = this;
            nNode.dom.nNode = nNode;

            //如果有子元素的话, 就迭代渲染子元素;;
            if( nNode&&vEl&&vEl.kids ) {
                this.renderKids( vEl.kids ,nNode );
            };

        }else if(typeof vEl === "undefined"){

            return

        };

        //如果有目标元素, 那就把所有的子元素先删了吧;
        if( tar ) {
            this.fireEvent("beforeappend", nNode, tar);
            tar.appendChild( nNode );
            this.fireEvent("afterappend", nNode, tar);
        };
        this.fireEvent("afterrender", tar);

        return tar || nNode;

    };

    /**
     * @public
     * @desc 通过组件渲染;
     * @param vEle 虚拟DOM
     * @return DOM;
     * */
    nono.Dom.prototype.addComponent = function ( vEle ) {
        var Class = vEle.Class;
        var kids = Array.prototype.concat.call([],Class.settings.kids || [],  vEle.kids||  []);
        //把Component中的配置加载到vEle上;
        vEle.kids = kids;

        vEle.model = vEle.model || {};
        util.cloneProps(true, vEle.model , Class.settings.model);

        vEle.name = vEle.name || Class.settings.name;
        Class.init&&Class.init();
        var dom = new nono.Dom();
        //delete vEle.Class;
        vEle.Class = undefined;
        return dom.render(vEle);
    };

    /**
     * 添加属性到虚拟DOM中;
     * @param target
     * @param { key : value };
     * */
    nono.Dom.prototype._assignProps = function(tar, props) {
        var fc, val;
        for( var p in props ) {
            fc = p.charAt(0);
            val = props[p];
            switch (fc) {
                case "#" :
                    tar.setAttribute("id", val);
                    break;
                case "@":
                    tar.setAttribute(p.slice(1), val);
                    break;
                case "-":
                    tar.style.setProperty(p.slice(1), val);
                    break;
                case ".":
                    tar.className += val;
                    break;
                case "!" :
                    //绑定事件;
                    this._assignEv( tar,  p.slice(1), props[p] );
                    break;
                case "*" :
                    this.initEmiter( props[p] || [] );
                    break;
                default:
                    props.tplData = props.tplData || {};
                    //把数据保存到tplData这个对象里面;
                    props.tplData[p] = props[p];
            };
        };
    };

    /**
     * 添加绑定事件;
     *
     * */
    nono.Dom.prototype._assignEv = function(tar,e, fn) {
        eventHandlers(tar, e, fn ,false);

        function cancel(ev) {
            ev.returnValue = false;
            ev.cancelBubble = true;
            ev.preventDefault&&ev.preventDefault();
            ev.stopPropagation&&ev.stopPropagation();
        };

        /**
         * @desc 事件绑定;
         * @param 元素
         * @param 事件名字
         * @param 绑定的事件或者事件数组
         * @param 是否捕获
         * */
        function eventHandlers(realElem, evName, fns, capture) {
            if (typeof fns === "object" ) {
                for (var i = 0, n = fns.length; i < n; i++) {
                    (function(i) {
                        fns[i] && realElem.addEventListener(evName, function(ev) {
                            //如果返回false就不自动刷新界面;
                            if( !fns[i].apply(realElem, Array.prototype.slice.apply(arguments).concat( realElem.dom.vEl )) ) {
                                cancel(ev);
                                return
                            };
                            //作用域被我们捕获;
                            try{
                                realElem.dom.update(realElem);
                            }catch(e) {
                                console.log("realElem.dom.update(); error");
                            };
                        }, capture);
                    })(i);
                };

            }else if (fns && (typeof fns === "function")) {
                realElem.addEventListener(evName, function(ev) {
                    //如果返回false就不自动刷新界面;
                    if( !fns.apply(realElem, Array.prototype.slice.apply(arguments).concat( realElem.dom.vEl )) ) {
                        cancel(ev);
                        return;
                    };
                    //每次执行事件的时候都会重新刷新dom, 作用域被我们捕获;
                    try{
                        realElem.dom.update(realElem);
                    }catch(e) {
                        console.log("realElem.dom.update(); error");
                    };
                }, capture);
            };
        };
    };

    /**
     * @desc 要把目标元素中节点全部删除;
     * @param tar 目标节点;
     * */
    nono.Dom.prototype._assignChildren = function( tar ) {
        //所有的NODE节点;
        var child, name;
        while(child = tar.lastChild) {
            name = (child.tagName || child.nodeName || "").toLocaleLowerCase();
            if(name === "script" || name === "link" || name === "style") break;
            this.fireEvent("beforeremovechild" ,child);
            //如果fireEvent返回值为false,那么就不删除元素;
            if( this.fireEvent("removechild" ,child) !== false ) {
                tar.removeChild( child );
            };
            this.fireEvent("afterremovechild" ,child);
        };
    };

    /**
     * @desc更新model模型, 到view中?
     *
     * */
    nono.Dom.prototype.setState = function( key, value) {

    };

    /**
     * @desc 创建DOM组件, 可以进行复用, COM组件主要是用来保存参数;
     * @return Constructor;
     * */
    nono.Component = function ( settings ) {
        //这样可以使用无new的方式使用Component组件
        if( this === window) {
            return new nono.Component( settings );
        };
        this.settings = util.cloneProps(true, {}, settings);//util.shallowClone(settings);
    };

    /**
     * @desc 初始化设置;
     * */
    nono.Component.prototype.init = function(  ) {
    };

    /**
     * @desc 为元素附加视图;
     * @param 参数为函数或者一个对象;
     * @return this;
     * */
    nono.Component.prototype.extendView = function ( obj ) {
        if( typeof obj === "function") {
            obj.call(this,this.settings.kids);
        }else if( typeof obj === "object" ) {
            this.setting.kids.push( obj );
        };
        return this;
    };

    window.nono = nono;
})();
View Code

   这个小库中包含了几个工具方法,比如addClass, hasClass, removeClass,closest方法, 以及jQ的extend,和underscore的template方法, 都可以单独拿出来使用, 还算方便吧;

DEMO0

  通过这个库实现了一个显示和隐藏目标元素的demo:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="http://files.cnblogs.com/files/diligenceday/ui.js"></script>
    <style>
        .div1{
            border:1px solid #9482ff;
            padding:100px;
            margin:100px;
        }
    </style>
</head>
<body>
</body>
<script>
    /*
     <div id="div0">
         <button>按钮</button>
         <div class="div1">
            我是内容;
         </div>
     </div>
     */

    
    /**
     * @descption EXAMPLE
     * name为元素的tag名字
     * model包含了这个元素作用域内部的变量,如果以特殊符号开头的key有特殊的作用, 当以
     *  ==>> . 开头的表示该元素的class;
     *  ==>> # 开头表示的是元素的id;
     *  ==>> @ 开头表示的是元素的自定义属性;
     *  ==>> !开头的表示元素的事件;
     *  ==>> *开头表示元素的自定义事件;
     *
     *  kids表示该元素内部的所有子元素, 值为一个数组, 值可以为另外的Compnent组件;
     * */
    var dom = new nono.Dom();
    dom.render({

        "name" : "div",
        model : {
            val : true,
            "*" : {
                "showOrHide" : function ( name, value ) {
                    var div1 = dom.nNode.getElementsByClassName("div1")[0];
                    div1.style.display = value ? "block" : "none";
                }
            }
        },
        kids : [
            {
                "name" : "button",
                kids : [
                        "button"
                ],
                model : {
                    "!click" : function() {
                        dom.vEl.model.value = !dom.vEl.model.value;
                        dom.fireEvent("showOrHide",dom.vEl.model.value);
                    }
                }
            },
            {
                "name" : "div",
                model : {
                    "." : "div1"
                },
                kids : [
                        "我是内容"
                ]
            }
        ]
    }, document.body);
</script>
</html>

DEMO1:

  只要更新模型中的数据, 并return true, 就会自动更新dom节点;

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="http://files.cnblogs.com/files/diligenceday/ui.js"></script>
</head>
<body>
    <div id="div0"></div>
</body>
<script>
    var dom = new nono.Dom();
    dom.render({
        name : "p",
        model : {
            "string" : 0,             //model就是this.dom.vEl.model的引用;
            "!click" : function ( ev, model ) {
                //this.dom.vEl就是渲染的初始变量, 改变变量string的值;
                this.dom.vEl.model.string+=1;
                //return true的话会更新当前的dom, return false会自动取消冒泡和默认行为;
                return true;
            }
        },
        kids : [
           "<div><%=string%></div>"
        ]
    }, document.getElementById("div0"));
</script>
</html>

  因为可以绑定事件, 如果元素发生点击事件的话,如果事件函数的return值为true,插件会自动刷新当前的DOM,

  return false的话会 阻止默认行为以及冒泡;

DEMO2:

  使用该插件先实现了一个简单的TIP插件:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="http://files.cnblogs.com/files/diligenceday/ui.js"></script>
</head>
<style>
    body{
        padding: 40px;
    }
    button{
        margin:10px;
         70px;
        height:30px;
        border-radius: 4px;
    }
    .div0{
        position: relative;
    }
    .content{
        position: absolute;
         200px;
        height: 200px;
        border: 1px solid #eee;
        box-shadow: 1px 1px 1px 1px #666;
        background:#fefefe;
    }
</style>
<body>

</body>
<script>
    var Com = function( con ,callback, callback2 ) {
        return new nono.Component({
            "name" : "button",
            model : {
                "!mouseover" : function ( ev ) {
                    callback( this, ev );
                },
                "!mouseout" : function ( ev ) {
                    callback2( this , ev);
                }
            },
            kids:[ con ]
        });
    };

    var ComContent = function (  ) {
        return new nono.Component({
            model : {
                "." : "content",
                "con" : "con"
            },
            kids:["<%=con%>"]
        });
    };
    /*
     <div class="div0">
         <button>btn0</button>
         <button>btn1</button>
         <button>btn2</button>
         <button>btn3</button>
        <div class="content"></div>
     </div>
     */

    var dom = new nono.Dom();

    dom.render({
        kids : [
            {
                Class : new Com( "button0" , function (el, ev) {
                    dom.fireEvent("mouseoverFn",dom,ev,"one--"+Math.random());
                }, function() {
                    dom.fireEvent("mouseoutFn", dom);
                })
            },
            {
                Class : new Com( "button0" , function (el, ev) {
                    dom.fireEvent("mouseoverFn",dom,ev,"two--"+Math.random());
                }, function() {
                    dom.fireEvent("mouseoutFn", dom);
                })
            },
            {
                Class : new Com( "button0" , function (el, ev) {
                    dom.fireEvent("mouseoverFn",dom,ev,"thr--"+Math.random());
                }, function() {
                    dom.fireEvent("mouseoutFn", dom);
                })
            },{
                Class : new ComContent("content")
            }
        ],
        model : {
            "*" : {
                //鼠标移入和移出的事件函数
                mouseoverFn : function (name, dom, ev, text) {
                    var node = dom.nNode.getElementsByClassName("content")[0];
                    node.style.display = "block";
                    node.style.left = ev.clientX + "px";
                    node.style.top = ev.clientY + "px";
                    node.innerText = text;
                },
                mouseoutFn : function () {
                    dom.nNode.getElementsByClassName("content")[0].style.display = "none"
                }
            }
        }
    }, document.body);
</script>
</html>

 DEMO2:

  使用ui.js也可以实现TAB页切换效果:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="http://files.cnblogs.com/files/diligenceday/ui.js"></script>
    <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"/>
</head>
<body>
    <div class="container">
        <div class="row">
            <div id="div0">

            </div>
        </div>
    </div>
    <script>
        //基于JS描述型的导航栏组件;
        function ComNavButton(content, index) {
            return new nono.Component({
                model : {
                    "." : "btn-group",
                    "@role" : "group"
                },
                kids : [
                    {
                        name : "button",
                        model : {
                            "." : "btn btn-default",
                            "@type" : "button",
                            "!click" : function() {
                                util.closest(this,"master").dom.fireEvent("showPanel",index)
                            }
                        },
                        kids : [content]
                    }
                ]
            });
        }

        //导航栏组件;
        var ComNav = new nono.Component({
            model : {
                "." : "navi btn-group btn-group-justified",
                "@role" : "group"
            },
            kids : [
                 //调用ComNavButton并生成不同参数的组件;
                {Class:ComNavButton("L",0)},
                {Class:ComNavButton("M",1)},
                {Class:ComNavButton("R",2)}
            ]
        });

        //内容组件;
        var Content = function( content ) {
            return new nono.Component({
                model: {
                    "." : "panel panel-default panel-e",
                    "*" : {
                        "show":function() {
                            this.nNode.style.display = "block"
                        }
                    }
                },
                kids : [
                    {
                        model : {
                            "." : "panel-body"
                        },
                        kids : [ content ]
                    }
                ]
            })
        };

        //内容区域的组件;
        var ConContent = new nono.Component({
            model : {
                "." : "content-group"
            },
            kids : [
                {
                    Class : Content("heheda")
                },
                {
                    Class : Content("lallalal")
                },
                {
                    Class : Content("ooooooo")
                }
            ]
        });

        //基于JS描述型的结构化语言;
/*
        <div class="btn-group btn-group-justified" role="group">
                <div class="btn-group" role="group">
                <button type="button" class="btn btn-default">L</button>
        </div>
        <div class="btn-group" role="group">
                <button type="button" class="btn btn-default">M</button>
        </div>
        <div class="btn-group" role="group">
                <button type="button" class="btn btn-default">R</button>
        </div>
        </div>

        <div class="content-group">
                <div class="panel panel-default">
                <div class="panel-body">
                Panel content0
        </div>
        </div>
        <div class="panel panel-default">
                <div class="panel-body">
                Panel content1
        </div>
        </div>
        <div class="panel panel-default">
                <div class="panel-body">
                Panel content2
        </div>
        </div>
        </div>
*/
        var dom = new nono.Dom();

        dom.render( {
            kids : [
                {
                    Class : ComNav,
                    model : {
                    }
                },
                {
                    Class : ConContent
                }
            ],
            model : {
                "." : "master",
                //绑定自定义事件;
                "*" :  {
                    "hideAllPanle" : function() {
                        var bodys = document.getElementsByClassName("panel-e")
                        for(var i=0 ;i< bodys.length; i++ ) {
                            bodys[i].style.display = "none";
                        };
                    },
                    "showPanel" : function (eventName, i) {
                        dom.fireEvent("hideAllPanle");
                        dom.nNode.getElementsByClassName("panel-e")[i].dom.fireEvent("show");
                    }
                }
            }
        }, document.getElementById("div0") );

    </script>
</body>
</html>
View Code

  如果你比较喜欢的可以直接把这个库作为模板引擎来用, 方便, 如果kids里面有##开头的字符串, 那么这个库就认为这是一个模板标签, 会去读取模板内容, 比如这样:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="http://files.cnblogs.com/files/diligenceday/ui.js"></script>
</head>
<body>
<script id="tpl" type="text/tpl">
        <% for (var i=0 ;i < 10; i++) {%>
            <p>
                <%=i%>
            </p>
        <%}%>
    </script>
    <script id="tpl2" type="text/tpl">
        <% for (var i=0 ;i < 4; i++) {%>
            <p><%=i%></p>
        <%}%>
    </script>
    <div id="div0">

    </div>
</body>
<script>
    var dom = new nono.Dom();
    dom.render({
        kids : [
            "##tpl",
            "##tpl2"
        ]
    }, document.getElementById("div0"));
</script>
</html>

  在事件触发的时候可以通过调用this.dom.vEl.model获取model的引用, 或者获取事件函数的第二个参数model,这个model就是this.dom.vEl.model的引用 , 这也是让父元素和子元素解耦的好方法, DEMO也有咯:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="http://files.cnblogs.com/files/diligenceday/ui.js"></script>
</head>
<body>
    <div id="div0">

    </div>
</body>
<script>
    var Com = new nono.Component({
        name : "p",
        model : {
            "name" : "name---0",
            "age" : 17,
            "!click" : function ( ev , scope ) {
                this.dom.vEl.model.age += parseInt( this.dom.vEl.model.click() );
                return true;
            }
        },
        kids : [
            "<div><%=name%></div>",
            "<p style='color:#f00'><%=age%></p>"
        ]
    });

    var dom = new nono.Dom();
    dom.render({
        Class : Com,
        model : {
            "click" : function() {
                return 1;
            }
        }
    }, document.getElementById("div0"));
</script>
</html>

  使用UI.js的优势是:

    1:JS到HTML组件化的优势可以提现出来,各个单元耦合降低;

    2:你完全可以把这个插件当做一个模板引擎来用, 因为复用了底线库的template方法, 但是功能更加多样化;

    3:如果元素dom.model下的属性发生改变, 会自动刷新DOM结构, 不用人为地去设置innerHTML或者innerText;

  缺点:

    1:不好学啊, 又要学习新API..;

    2:自定义事件是绑定到指定元素的dom上, 用起来会不习惯;

  这个插件仅供参考, 希望可以慢慢优化;

作者: NONO
出处:http://www.cnblogs.com/diligenceday/
QQ:287101329 

原文地址:https://www.cnblogs.com/diligenceday/p/4963172.html