自己写一个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)!==- ? 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的更多相关文章
- 让我们纯手写一个js继承吧
继承在前端逻辑操作中是比较常见的,今天我们就从零开始写一个js的继承方式 在es5中继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上Parent.call(this),在es6中则 ...
- 从 0 到 1 到完美,写一个 js 库、node 库、前端组件库
之前讲了很多关于项目工程化.前端架构.前端构建等方面的技术,这次说说怎么写一个完美的第三方库. 1. 选择合适的规范来写代码 js 模块化的发展大致有这样一个过程 iife => commonj ...
- 如何手写一个js工具库?同时发布到npm上
自从工作以来,写项目的时候经常需要手写一些方法和引入一些js库 JS基础又十分重要,于是就萌生出自己创建一个JS工具库并发布到npm上的想法 于是就创建了一个名为learnjts的项目,在空余时间也写 ...
- 前端与编译原理——用JS写一个JS解释器
说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...
- 如何写一个Js上传图片插件。
项目里面需要一个上传图片的插件,找了半天没有找到满意的,算了 不找了,自己写一个吧,顺便复习一下js方面的知识.完成之后效果还不错,当然还要继续优化,源码在最后. 介绍一种常见的js插件的写法 ; ( ...
- 【转载】写一个js库需要怎样的知识储备和技术程度?
作者:小爝链接:https://www.zhihu.com/question/30274750/answer/118846177来源:知乎著作权归作者所有,转载请联系作者获得授权. 1,如何编写健壮的 ...
- 利用epoll写一个"迷你"的网络事件库
epoll是linux下高性能的IO复用技术,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取 ...
- 写一个js向左滑动删除 交互特效的插件——Html5 touchmove
需求描述 需要实现类似QQ中对联系人的操作:向左滑动,滑出删除按钮.滑动超过一半时松开则自动滑到底,不到一半时松开则返回原处. 纯js实现 使用了h5的touchmove等事件,以及用js动态改变cs ...
- 用vue和layui简单写一个响应式数据展示表
在创建项目之前,先把我们需要的文件打包处理 <!DOCTYPE html> <html lang="en"> <head> <meta c ...
随机推荐
- WWDC 后苹果最新 App Store 审核条款!
WWDC 2016 大会之后,苹果公司发布了四个全新平台:iOS,macOS,watchOS 和 tvOS.并且在此之后,苹果应用商店审核条款也同时进行了更新——貌似不算进行了更新,简直就是重 ...
- php面向对象编程 设计模式
面向对象编程的基本原则: 单一职责:一个类,只需要做好一件事 开放封闭:一个类,应该是可扩展的,而不是可修改的 依赖倒置:一个类,不应该强依赖另一个类.每个类对应另外一个类都是可替换的 配置化:尽可能 ...
- 下一代Asp.net开发规范OWIN(1)—— OWIN产生的背景以及简单介绍
随着VS2013的发布,微软在Asp.Net中引入了很多新的特性,比如使用新的权限验证模块Identity, 使用Async来提高Web服务器的吞吐量和效率等.其中一个不得不提的是OWIN和Katan ...
- Linux make: g++: Command not found
Linux使用make命令时遇到"make: g++: Command not found",这个主要是没有安装gcc-c++.x86_64,如下所示 [root@localh ...
- 深度解析mysql登录原理
使用mysql数据库的第一步必然是建立连接登录,然后在上面执行SQL命令.无论是通过mysql的客户端,还是通过C-API,JDBC标准接口连接数据库,这个过程一定少不了.今天我们聊一聊mysql登陆 ...
- 执行shell脚本,报错坏的解释器
在windows下面用editplus编写了一个shell脚本.执行时报错 "/bin/bash^M: 坏的解释器: 没有那个文件或目录 解决方法: 在终端输入sed -i 's/\r$// ...
- 0024 Java学习笔记-面向对象-包装类、对象的比较、String常量池问题
包装类 基本类型-->包装类 byte-->Byte short-->Short int-->Integer long-->Long char-->Characte ...
- 原生 CSS 网格布局学习笔记
下是来自Oliver Williams的帖子. Oliver已经学习了相当长时间的原生CSS网格,可以说是在CSS网格方面有一定的发言权.在这篇文章中,他将以非同寻常的思路分析自己的CSS网格布局学习 ...
- 使用SignalR实现消息提醒
Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务器通信,随着Html5中W ...
- jar命令的用法详解
本文详细讲述了JAR命令的用法,对于大家学习和总结jar命令的使用有一定的帮助作用.具体如下: JAR包是Java中所特有一种压缩文档,其实大家就可以把它理解为.zip包.当然也是有区别的,JAR包中 ...