自己写一个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 ...
随机推荐
- Socket.IO聊天室~简单实用
小编心语:大家过完圣诞准备迎元旦吧~小编在这里预祝大家元旦快乐!!这一次要分享的东西小编也不是很懂啊,总之小编把它拿出来是觉地比较稀奇,而且程序也没有那么难,是一个比较简单的程序,大家可以多多试试~ ...
- webapp开发调试环境--weinre配置
用谷歌调试工具中的手机模拟器模拟手机进行webapp的开发,与真机上的效果还是有些偏差,opera手机模拟器的效果亦不佳.有时在pc上开发出来的webapp效果良好,在部分真机上就出现了偏差,这时候就 ...
- [20141124]sql server密码过期,通过SSMS修改策略报错
背景: 新建了用户,没有取消掉强制密码策略 修改掉策略报错 错误: The CHECK_POLICY and CHECK_EXPIRATION options cannot be turned OFF ...
- Oracle分区表
先说句题外话- 欢迎成都天府软件园的小伙伴来面基交流经验~ 一:什么是分区(Partition)? 分区是将一个表或索引物理地分解为多个更小.更可管理的部分. 分区对应用透明,即对访问数据库的应用 ...
- MySQL SQL 注入
如果您通过网页获取用户输入的数据并将其插入一个MySQL数据库,那么就有可能发生SQL注入安全的问题. 本博文将为大家介绍如何防止SQL注入,并通过脚本来过滤SQL中注入的字符. 所谓SQL注入,就是 ...
- linux shell for循环使用命令中读取到的值实例
#!/bin/bash file="states" for state in `cat $file` do echo "Visit beautiful $state&qu ...
- Node Pm2 配置
系统环境:Centos7 第一步安装NodeJS 建议采用稳定编译过的版本,source code稍麻烦,编译过的直接可用,安装超级简单 下载完成后安装成功 node -v 显示版本号 npm -v ...
- JS导出PDF插件(支持中文、图片使用路径)
在WEB上想做一个导出PDF的功能,发现jsPDF比较多人推荐,遗憾的是不支持中文,最后找到pdfmake,很好地解决了此问题.它的效果可以先到http://pdfmake.org/playgroun ...
- NOIP模拟赛20161022
NOIP模拟赛2016-10-22 题目名 东风谷早苗 西行寺幽幽子 琪露诺 上白泽慧音 源文件 robot.cpp/c/pas spring.cpp/c/pas iceroad.cpp/c/pas ...
- 第5章 软件包管理(1)_RPM包安装
1. 软件包简介 1.1 软件包分类 (1)源码包:如C.C++源码包,脚本安装包执行后可以自动安装. (2)二进制包:Redhat系列(如CentOS):为RPM包,Debian系列(如ubuntu ...