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)!==- ? 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 )!=-) {
if( e.className.split(" ").indexOf( arg ) !== -) {
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 = ;
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[] || {},
i = ,
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[] || {};
// skip the boolean and the target
i = ;
}; // 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 = , ti; ti = types[i++];) {
if(typeof listener === "function") {
getListener(this, ti, true).push(listener);
}else{
for(var j= ;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 = , ti; ti = types[i++];) {
removeItem(getListener(this, ti) || [], listener);
}
}, /**
* 触发事件
* @name fireEvent
* @grammar
* @example
*/
fireEvent:function () {
var types = arguments[];
types = types.trim().split(' ');
for (var i = , 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 = , l = array.length; i < l; i++) {
if (array[i] === item) {
array.splice(i, );
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= ,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 === ? 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();
val = props[p];
switch (fc) {
case "#" :
tar.setAttribute("id", val);
break;
case "@":
tar.setAttribute(p.slice(), val);
break;
case "-":
tar.style.setProperty(p.slice(), val);
break;
case ".":
tar.className += val;
break;
case "!" :
//绑定事件;
this._assignEv( tar, p.slice(), 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 = , 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方法, 都可以单独拿出来使用, 还算方便吧;

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")[];
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" : , //model就是this.dom.vEl.model的引用;
"!click" : function ( ev, model ) {
//this.dom.vEl就是渲染的初始变量, 改变变量string的值;
this.dom.vEl.model.string+=;
//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;
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 #;
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")[];
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")[].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>

  如果你比较喜欢的可以直接把这个库作为模板引擎来用, 方便, 如果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= ;i < ; i++) {%>
<p>
<%=i%>
</p>
<%}%>
</script>
<script id="tpl2" type="text/tpl">
<% for (var i= ;i < ; 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" : ,
"!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 ;
}
}
}, 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

自己写一个JS单向数据流动库----one way binding的更多相关文章

  1. 让我们纯手写一个js继承吧

    继承在前端逻辑操作中是比较常见的,今天我们就从零开始写一个js的继承方式 在es5中继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上Parent.call(this),在es6中则 ...

  2. 从 0 到 1 到完美,写一个 js 库、node 库、前端组件库

    之前讲了很多关于项目工程化.前端架构.前端构建等方面的技术,这次说说怎么写一个完美的第三方库. 1. 选择合适的规范来写代码 js 模块化的发展大致有这样一个过程 iife => commonj ...

  3. 如何手写一个js工具库?同时发布到npm上

    自从工作以来,写项目的时候经常需要手写一些方法和引入一些js库 JS基础又十分重要,于是就萌生出自己创建一个JS工具库并发布到npm上的想法 于是就创建了一个名为learnjts的项目,在空余时间也写 ...

  4. 前端与编译原理——用JS写一个JS解释器

    说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...

  5. 如何写一个Js上传图片插件。

    项目里面需要一个上传图片的插件,找了半天没有找到满意的,算了 不找了,自己写一个吧,顺便复习一下js方面的知识.完成之后效果还不错,当然还要继续优化,源码在最后. 介绍一种常见的js插件的写法 ; ( ...

  6. 【转载】写一个js库需要怎样的知识储备和技术程度?

    作者:小爝链接:https://www.zhihu.com/question/30274750/answer/118846177来源:知乎著作权归作者所有,转载请联系作者获得授权. 1,如何编写健壮的 ...

  7. 利用epoll写一个"迷你"的网络事件库

    epoll是linux下高性能的IO复用技术,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取 ...

  8. 写一个js向左滑动删除 交互特效的插件——Html5 touchmove

    需求描述 需要实现类似QQ中对联系人的操作:向左滑动,滑出删除按钮.滑动超过一半时松开则自动滑到底,不到一半时松开则返回原处. 纯js实现 使用了h5的touchmove等事件,以及用js动态改变cs ...

  9. 用vue和layui简单写一个响应式数据展示表

    在创建项目之前,先把我们需要的文件打包处理 <!DOCTYPE html> <html lang="en"> <head> <meta c ...

随机推荐

  1. Android 图片压缩、照片选择、裁剪,上传、一整套图片解决方案

    1.Android一整套图片解决方案 http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650820998&idx=1& ...

  2. 实用控件分享:自定义逼真相机光圈View

    最近手机界开始流行双摄像头,大光圈功能也应用而生.所谓大光圈功能就是能够对照片进行后期重新对焦,其实现的原理主要是对拍照期间获取的深度图片与对焦无穷远的图像通过算法来实现重新对焦的效果. 在某双摄手机 ...

  3. swift实现饭否应用客户端源码

    swift 版 iOS 饭否客户端 源码下载:http://code.662p.com/view/13318.html 饭否是中国大陆地区第一家提供微博服务的网站,被称为中国版Twitter.用户可通 ...

  4. 一个3D ar打飞机的游戏iOS源码

    这是国内目前第一款集合了AR实景,3D游戏和人脸识别的射击游戏,通过旋转和改变手机的角度与位置,所有的射击操作都靠手势来完成,目前所有的源码全部都在这里.appStore地址:https://itun ...

  5. 敏捷遇上UML——软创基地马年大会(深圳站 2014-3-15)

    邀请函: 尊敬的阁下: 我们将在深圳为您奉献高端知识大餐,当敏捷遇上UML,会发生怎样的化学作用呢?首席专家张老师将会为您分享需求分析及软件设计方面的最佳实践,帮助您掌握敏捷.UML及两者相结合的实战 ...

  6. IntelliJ IDEA 配置运行程序

    IntelliJ IDEA 对于Javaer开发来说还是很nice的,就是第一次用可能配置项有点生疏,这里就记录一下IntelliJ IDEA 配置运行程序. 1. 点击Edit Config... ...

  7. Tomcat部署web项目

    在Myeclipse中,我们很容易做到这一步:把一个web项目生成war文件 其实在eclipse中,实现这样的功能,也是很简单的. 下面就看一下是怎样操作的吧! 新建一个web项目: 取名为:ecl ...

  8. Conquer and Divide经典例子之汉诺塔问题

    递归是许多经典算法的backbone, 是一种常用的高效的编程策略.简单的几行代码就能把一团遭的问题迎刃而解.这篇博客主要通过解决汉诺塔问题来理解递归的精髓. 汉诺塔问题简介: 在印度,有这么一个古老 ...

  9. mysql while,loop,repeat循环,符合条件跳出循环

    1.while循环 DELIMITER $$ DROP PROCEDURE IF EXISTS `sp_test_while`$$ CREATE PROCEDURE `sp_test_while`( ...

  10. Linux tcp黏包解决方案

    tcpip协议使用"流式"(套接字)进行数据的传输,就是说它保证数据的可达以及数据抵达的顺序,但并不保证数据是否在你接收的时候就到达,特别是为了提高效率,充分利用带宽,底层会使用缓 ...