JS單向流動其實就是數據到視圖的過程, 這幾天突發奇想,想着弄一個插件, 把DOM結構使用JS進行描述;css
由於DOM中的Class , content, id, attribute, 事件, 子元素所有經過JS進行描述, 最後把這些元素拼接在一塊兒, 生成一個DOM樹, 若是有些DOM數是能夠複用的,咱們就把他包裝成組件(Component), 方便複用, 可是使用結構化的JS描述DOM並不形象, 不如直接使用HTML生成,若是可以準確把握JS結構, 而後封裝各個JS組件, 可以極大的下降耦合, 讓代碼邏輯更加清楚。html
由於DOM是樹形結構, 必須有子元素, 因此要迭代渲染, 生成各類各樣的結構, 爲了減低耦合度, 使用了自定義事件, 讓元素之間的關係變低;node
爲了簡化model到view的過程, 複用underscore的template, 結合model, 自動生成view, 自動渲染;express
由於存在組件這個玩意兒, 咱們複用jQuery 的extend方法, 深度複製組件的屬性, 讓Component組件之間不會相互影響( 由於使用原型繼承的話, 修改單個組件的屬性值會致使全部複用該組件的組件屬性值發生改變);bootstrap
視覺模型以下:api
總體源代碼以下:數組
(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 = /\\|'|\r|\n|\t|\u2028|\u2029/g; var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; util.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/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 += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } index = offset + match.length; return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + "return __p;\n"; 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') + '){\n' + 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; })();
這個小庫中包含了幾個工具方法,好比addClass, hasClass, removeClass,closest方法, 以及jQ的extend,和underscore的template方法, 均可以單獨拿出來使用, 還算方便吧;緩存
經過這個庫實現了一個顯示和隱藏目標元素的demo:app
<!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>
只要更新模型中的數據, 並return true, 就會自動更新dom節點;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的話會 阻止默認行爲以及冒泡;
使用該插件先實現了一個簡單的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; width: 70px; height:30px; border-radius: 4px; } .div0{ position: relative; } .content{ position: absolute; width: 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>
使用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>
若是你比較喜歡的能夠直接把這個庫做爲模板引擎來用, 方便, 若是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