如何实现一个数据驱动的Web客户端库

WEB前端程序员的主要工作是实现数据到HTML5视图的绑定和控制视图和数据之间的切换,DOM在其中扮演了重要的角色,不过DOM代码也是程序员的噩梦,大量的DOM繁琐而丑陋,能不能把视图的状态信息保存在数据里,并且使用数据驱动的方式来操作视图呢?下面就是一个关于这方面的实现,特点:延迟响应,数据驱动,简而言之就是通过模型的改变控制视图的改变,从模型里获取数据就是从视图获取最新的人机交互数据。看代码:

  1 "use strict";
  2 
  3 //定义变量
  4 var JXR;
  5 //闭包
  6 (function () {
  7     Function.prototype.f = function (name, fun) {
  8         this.prototype[name] = fun;
  9     };
 10 
 11     //定义只读属性
 12     Object.prototype.readonly = function (property, value) {
 13         Object.defineProperty(this, property, {
 14             configurable: false,
 15             enumeratable: false,
 16             writable: false,
 17             value: value
 18         });
 19     };
 20 
 21     //定义读写属性
 22     Object.prototype.readwrite = function (property, value) {
 23         Object.defineProperty(this, property, {
 24             configurable: false,
 25             enumeratable: false,
 26             writable: true,
 27             value: value
 28         });
 29     };
 30 
 31     //定义读写器属性
 32     Object.prototype.setget = function (property, set, get) {
 33         Object.defineProperty(this, property, {
 34             configurable: false,
 35             enumeratable: true,
 36             set: set,
 37             get: get
 38         });
 39     };
 40 
 41     //定义读写器属性
 42     Object.prototype.getset = function (property, get, set) {
 43         Object.defineProperty(this, property, {
 44             configurable: false,
 45             enumeratable: true,
 46             set: set,
 47             get: get
 48         });
 49     };
 50 
 51     //bind函数
 52     Function.prototype.bind = Function.prototype.bind || function (context) {
 53         //外部参数
 54         var outterArgs = Array.prototype.slice.call(arguments, 1);
 55         var self = this;
 56         return function () {
 57             //内部参数:在Html事件就是event对象
 58             var innerArgs = Array.prototype.slice.call(arguments);
 59             //最终参数:最终的事件事件函数的参数
 60             var finalArgs = outterArgs.concat(innerArgs);
 61             return self.apply(context, finalArgs);
 62         }
 63     };
 64 
 65     //对象浅复制
 66     Object.create = Object.create || function (object, properties) {
 67         var F = function () {
 68         };
 69         F.prototype = object;
 70         var newObj = new F();
 71         //定义新属性
 72         this.defineProperties(newObj, properties);
 73         return newObj;
 74     };
 75 
 76     //在函数原型里绑定一个extend方法,用于类式继承
 77     Function.prototype.extend = function (parent) {
 78         //超类的原型拷贝
 79         var clone = Object.create(parent.prototype);
 80         this.prototype = clone;
 81         //修改构造器
 82         this.prototype.constructor = this;
 83 
 84     };
 85     /*判断对象的类型*/
 86     Object.prototype.is = function (constructor) {
 87         return this.constructor === constructor;
 88     };
 89 
 90     /*是否原生类型*/
 91     Object.primitive = function (val) {
 92         return typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || typeof val === 'undefined';
 93     };
 94 
 95     /*对象是否拥有属性*/
 96     Object.prototype.has = function (property) {
 97         return property in this;
 98     };
 99 
100     /*判空*/
101     Object.empty = function (val) {
102         return val === undefined || val === null;
103     };
104 
105     /*字符串到原生类型的转换*/
106     String.prototype.to = function (type) {
107         switch (type) {
108             case 'number':
109                 return Number(this);
110             case 'boolean':
111                 return Boolean(this);
112             default:
113                 return this;
114         }
115     };
116 
117 })();
118 
119 /*
120     DOM的扩展
121     * */
122 (function () {
123     /*判断是否空白文本节点*/
124     Node.f('blank', function () {
125         return this.nodeType === Node.TEXT_NODE && (Object.empty(this.data) || this.data.trim() === '');
126     });
127 
128     /*属性结点的数据绑定
129     绑定一个简单数据类型
130     * */
131     function AttrNode(el, attr, data) {
132         var ret = parse(attr.value, data);
133         if (Object.empty(ret.obj) || Object.empty(ret.name))
134             return;
135         if (Object.empty(ret.value))
136             ret.value = '';
137         //绑定到属性值
138         attr.value = String(ret.value);
139         attr[JXR.TYPE_PRIMITIVE] = typeof ret.value;
140         ret.obj.setget(ret.name, function (v) {
141             //保存类型
142             attr[JXR.TYPE_PRIMITIVE] = typeof v;
143             attr.value = String(v);
144         }, function () {
145             var type = attr[JXR.TYPE_PRIMITIVE];
146             //原生属性
147             if (el[attr.name])
148                 return el[attr.name].to(type);
149             else
150             //自定义属性
151                 return attr.value.to(type);
152 
153         });
154     }
155 
156     /*文本结点的数据绑定
157     绑定一个简单数据类型
158     * */
159     function TextNode(node, data) {
160         var text=node.data.trim();
161         //脚本节点
162         if(/\$(\S+)\$/gi.test(text))
163         {
164             node.data=eval(JXR.DATA+'.'+/\$(\S+)\$/gi.exec(text)[1]);
165         }
166         else if(isExp(text))
167         {
168             var ret = parse(text, data);
169             if (Object.empty(ret.obj) || Object.empty(ret.name))
170                 return;
171             if (Object.empty(ret.value))
172                 ret.value = '';
173             //绑定到文本
174             node.data = String(ret.value);
175             node[JXR.TYPE_PRIMITIVE] = typeof ret.value;
176 
177             ret.obj.setget(ret.name, function (v) {
178                 //保存类型
179                 node[JXR.TYPE_PRIMITIVE] = typeof v;
180                 node.data = String(v);
181             }, function () {
182                 var type = node[JXR.TYPE_PRIMITIVE];
183                 return node.data.trim().to(type);
184             });
185 
186         }
187 
188     }
189 
190     /*
191     *主类:参数obj={
192     *    el:HTMLElement || string(css selector),
193     *    data:Object  (数据),
194     *    on:Object    (事件函数)
195     * }
196     *
197     * */
198     JXR = function () {
199 
200         if (arguments.length == 1 && arguments[0].constructor == Object) {
201             //参数
202             var obj = arguments[0];
203             this.readwrite('el',obj.el instanceof HTMLElement ? obj.el : document.querySelector(obj.el));
204             this.el.normalize();
205             //保存父节点
206             this.readonly('parent',this.el.parentElement);
207              //保存模板
208             this.readonly('_el',this.el.cloneNode(true));
209             //数据
210             this._data = obj.data;
211             //复制事件对象
212             this.on=obj.on;
213             bind(this.el, this._data, this);
214             var self=this;
215             this.setget(JXR.DATA,function (v) {
216 
217                 if (Object.empty(v)) {
218                     this.parent.removeChild(this.el);
219                 }
220                 /*else if(v===this._data)
221                 {
222                     alert('set');
223                     set(this._data,v);
224 
225                 }*/
226                 else {
227                     self.on=clearEvents(self.on);
228                     var newChild = this._el.cloneNode(true);
229                     bind(newChild, v, self);
230                     this.parent.replaceChild(newChild, this.el);
231                     //清除事件句柄
232                     clearEvents(this.el);
233                     this.el = newChild;
234                 }
235                 this._data = v;
236             },function () {
237                 alert('get');
238                 return this._data;
239             });
240             
241         }
242         else {
243             throw  new Error('参数必须是对象.');
244         }
245     };
246 
247     /*
248     清除事件
249      */
250     function clearEvents(el)
251     {
252        //所有属性
253         var attrs=el.attributes;
254         for(var i=0;i<attrs.length;i++)
255             {
256             var attr = attrs[i];
257             var name = attr.name;
258             var value = attr.value;
259             if(/on:(\S+)/gi.test(name))
260                 {
261                 //获取事件类型
262                  var type=/on:(\S+)/gi.exec(name)[1];
263                  el[JXR.EVENT_ATTR_PREFIX+type]=null
264                 }
265             }
266         //所有子元素
267         var children=el.children;
268         for(var i=0;i<children.length;i++)
269             {
270             clearEvents(children[i])
271             }
272     }
273 
274     /*实例方法
275     更新UI
276     * */
277     JXR.f('update', function (data) {
278         this.data = data || this._data;
279     });
280 
281     /*
282     实例方法,获取最新的界面数据
283      */
284     JXR.f('getValue', function (data) {
285         return get(data || this.data);
286     });
287 
288     /*
289     工具函数,绑定数据到视图
290      */
291     JXR.bind = function (view, obj) {
292         var domParser = new DOMParser();
293         var el = domParser.parseFromString(view, 'text/html').body.firstElementChild;
294         return new JXR({
295             el:el,
296             data:obj.data,
297             on:obj.on
298         });
299     };
300 
301     /*
302     *工具函数:获取对象的拷贝,里面是最新的从界面收集到的数据
303     * */
304     function get(obj) {
305         var ret = null;
306         if (typeof obj === 'object') {
307             if (Array.isArray(obj))
308                 ret = [];
309             else
310                 ret = {};
311             for (var i in obj) {
312                 var val = obj[i];
313                 if (!obj.hasOwnProperty(i) || Object.prototype.has(i) || typeof val === 'function' || i === JXR.EVENT_ATTR_PREFIX) {
314                     continue;
315                 }
316                 else if (typeof val === 'object') {
317                     ret[i] = get(val);
318                 }
319                 else if (Object.primitive(val)) {
320                     ret[i] = val;
321                 }
322             }
323         }
324         return ret;
325 
326     };
327 
328     function set(obj, data) {
329         if (typeof obj === 'object') {
330             for (var i in data) {
331                 var val = data[i];
332                 //没有i属性
333                 if(!obj.has(i))
334                 {
335                     throw new Error('"+i+"属性不存在!');
336                 }
337                 else if (typeof val === 'object') {
338                     set(obj[i], val);
339                 }
340                 else if (Object.primitive(val)) {
341                     obj[i] = val;
342                 }
343             }
344         }
345     }
346 
347     /*工具函数*/
348     JXR.toString = function d(value) {
349         var ret = '';
350         if (typeof value === 'object') {
351             ret += Array.isArray(value) ? '[' : '{';
352             for (var i in value) {
353                 if (!value.hasOwnProperty(i))
354                     continue;
355                 var val = value[i];
356                 if (typeof val === 'object') {
357                     if (Array.isArray(value))
358                         ret += d(val) + ',';
359                     else
360                         ret += i + ':' + d(val) + ',';
361                 }
362                 else if (Object.primitive(val)) {
363                     if (typeof val === 'string')
364                         ret += i + ':"' + val + '",';
365                     else
366                         ret += i + ':' + val + ',';
367                 }
368 
369             }
370             if (ret.lastIndexOf(',') > 0)
371                 ret = ret.substring(0, ret.length - 1);
372             ret += Array.isArray(value) ? ']' : '}';
373         }
374         return ret;
375 
376     };
377 
378     /*常量的定义*/
379     JXR.EXP = '$';
380     JXR.DATA = 'data';
381     JXR.FOR_ATTR = 'jxr:for';
382     JXR.IF_ATTR = 'jxr:if';
383     JXR.DATA_BIND_ATTR = 'jxr:bind';
384     JXR.EVENT_ATTR_PREFIX = 'on';
385     JXR.TYPE_PRIMITIVE = 'primitive-type';
386     JXR.SCRIPT='jxr:script';
387 
388 
389     /*工具函数,处理data:bind属性*/
390     function DataBind(el, attr, data, self) {
391         var ret = parse(attr.value, data);
392         if (Object.empty(ret.obj) || Object.empty(ret.name))
393             return null;
394         if (Object.empty(ret.value)) {
395             //移除该元素节点
396             el.parentElement.removeChild(el);
397            
398         }
399         return ret.value;
400 
401     }
402 
403     /*工具函数,处理for控制*/
404     function For(el, attr, data, self) {
405         var ret = parse(attr.value, data);
406         if (Object.empty(ret.obj) || Object.empty(ret.name))
407             return null;
408         else if (!Object.empty(ret.value)) {
409             if(typeof ret.value==='number')
410             {
411                 var loop=ret.value;
412             }
413             else if(!Array.isArray(ret.value))
414             {
415                 throw new Error('for循环必须绑定一个数组或者一个数字.');
416             }
417 
418         }
419 
420         //数组长度
421         var size=0;
422         //清空
423         if(Object.empty(ret.value))
424         {
425             size=0;
426         }
427         //简单循环
428         else if(loop)
429         {
430             size=loop;
431         }
432         //集合循环
433         else
434         {
435             size=ret.value.length;
436         }
437         //要循环的子元素,目前只支持一个子元素
438         var child = el.firstElementChild;
439         el.removeChild(child);
440          //循环
441         for (var i = 0; i < size; i++) {
442             var clone = child.cloneNode(true);
443             //是个简单循环
444             if(loop)
445             {
446                 var val={index:i,count:i+1};
447             }
448             else
449             {
450                 var val=ret.value[i];
451             }
452             //追加循环节点
453             el.appendChild(clone);
454             bind(clone,val,self);
455         }
456         return ret.value;
457     }
458 
459     /*工具函数,处理if控制*/
460     function If(el, attr, data) {
461         var ret = parse(attr.value, data);
462         if (Object.empty(ret.obj) || Object.empty(ret.value) || Object.empty(ret.name))
463             return null;
464         else if(typeof ret.value!=='boolean')
465         {
466             throw new Error('if:test值必须是boolean.');
467         }
468         //绑定到属性
469         attr.value = ret.value;
470         //重新定义成读写器属性
471         if (!ret.obj.has(ret.name)) {
472             ret.obj.setget(ret.name,function (v) {
473                 attr.value = String(v);
474                 //控制元素的隐藏和显示
475                 if (!v) {
476                     //元素隐藏
477                     el.style.display = 'none';
478                 }
479             },function () {
480                 return Boolean(attr.value);
481             });
482 
483         }
484 
485         if (!ret.value) {
486             //元素隐藏
487             el.style.display = 'none';
488         }
489         return true;
490     }
491 
492     /*工具函数:判断是否是一个带$的数据绑定*/
493     function isExp(text) {
494         return text.trim().charAt(0) === JXR.EXP;
495     }
496 
497     /*
498     工具函数用来解析$a.b.c.d的数据绑定,返回:{obj:对象,value:d的值,name:'d'}
499     args:$a.b.c.d
500     _data:真实数据
501     data:影子数据
502      * */
503     function parse(args, data) {
504         if (Object.empty(data)) {
505             throw new Error('data:(null|undefined)');
506         }
507         else {
508             try {
509                 //去掉空格和$
510                 var d = args.trim().replace(JXR.EXP, '');
511                 //处理形如:a.b.c.d
512                 var s = d.split('.');
513                 var o = data;
514                 for (var i = 0; i < s.length - 1; i++) {
515                     o = o[s[i]];
516                     if (Object.empty(o)) {
517                         throw new Error('data:(null|undefined)');
518                     }
519                 }
520                 var n = s[s.length - 1];
521                 //属性d的值
522                 var v = o[n];
523                 return {obj: o, value: v, name: n};
524             }
525             catch (e) {
526                 console.error(e);
527                 return null;
528             }
529 
530         }
531     }
532 
533     /*
534     处理事件属性
535      */
536     function handleEvent(el,data, name,value, self) {
537         //获取事件类型
538         var type=/on:(\S+)/gi.exec(name)[1];
539         //获得事件方法名
540         var method=value.replace(/\(\S*\)/gi,'');
541         //获取方法参数
542         var args=/(?<=\()\S*(?=\))/gi.exec(value)[0];
543         //收集参数
544         var array=[];
545         if(!Object.empty(args) && args!=='')
546         {
547 
548             //处理方法参数
549             args=args.split(',');
550             for(var i=0;i<args.length;i++)
551             {
552                 var arg=args[i];
553                 if(isExp(arg))
554                 {
555                     arg=parse(arg, data).value;
556 
557                 }
558                 array.push(arg);
559             }
560 
561         }
562          //清除事件
563         el[JXR.EVENT_ATTR_PREFIX + type] = null;
564         //绑定事件
565         el[JXR.EVENT_ATTR_PREFIX + type] = function(ev){
566             array.push(ev);
567             self[JXR.EVENT_ATTR_PREFIX][method].apply(self,array);
568         };
569     }
570 
571     /*处理元素的普通属性*/
572     function handleAttrs(el, data, self) {
573         var attrs = el.attributes;
574         for (var i = 0; i < attrs.length; i++) {
575             var attr = attrs[i];
576             var name = attr.name;
577             if (name === JXR.IF_ATTR || name === JXR.FOR_ATTR || name === JXR.DATA_BIND_ATTR) {
578                 continue;
579             }
580             var value = attr.value;
581             if(!Object.empty(value))
582             {
583                 value=value.trim();
584             }
585             if(value==='')
586                 {
587                 continue;
588                 }
589             //事件属性
590             if(/on:(\S+)/gi.test(name))
591             {
592                 handleEvent(el,data,name,value,self);
593             }
594            //脚本属性
595             else if(/\$(\S+)\$/gi.test(value))
596                 {
597                 attr.value=eval(JXR.DATA+'.'+/\$(\S+)\$/gi.exec(value)[1]);
598                 }
599             //普通属性绑定
600             else if (isExp(value)) {
601                 AttrNode(el, attr, data);
602             }
603 
604         }
605     }
606 
607     /*处理元素的所有子节点*/
608     function handleChildNodes(el, data, self) {
609 
610         //处理子节点
611         var childNodes = el.childNodes;
612         for (var j = 0; j < childNodes.length; j++) {
613             var node = childNodes[j];
614             if (node.blank())
615                 continue;
616             //是元素
617             if (node.nodeType === Node.ELEMENT_NODE) {
618                 //递归
619                 bind(node, data, self);
620             }
621             //是文本
622             else if (node.nodeType === Node.TEXT_NODE) {
623                 TextNode(node, data);
624             }
625         }
626     }
627 
628 
629     /*工具函数,绑定数据到元素*/
630     function bind(el, data, self) {
631         //处理属性
632         var attrs = el.attributes;
633         //条件控制
634         if (attrs[JXR.IF_ATTR]) {
635 
636             if (!If(el, attrs[JXR.IF_ATTR], data)) {
637                 return;
638             }
639         }
640         //循环控制
641         if (attrs[JXR.FOR_ATTR]) {
642             var forData = For(el, attrs[JXR.FOR_ATTR], data, self);
643             if (!forData) {
644                 return;
645             }
646         }
647         //数据绑定
648         else if (attrs[JXR.DATA_BIND_ATTR]) {
649             var data = DataBind(el, attrs[JXR.DATA_BIND_ATTR], data, self);
650             if (!data)
651                 return;
652         }
653         //处理其他属性
654         handleAttrs(el, data, self);
655         //循环已经处理
656         if(forData)
657         {
658             return;
659         }
660         //处理子节点
661         handleChildNodes(el,  data, self);
662     }
663 
664     Object.freeze(JXR);
665     Object.freeze(JXR.prototype);
666 })();
667 
668 //阻止往对象里面追加属性
669 //Object.preventExtensions($);
670 //delete $.n;允许
671 //console.info($.n);
672 // $.a='123';报错
673 //既阻止追加,也阻止删除属性
674 //Object.seal($);
675 //$.n=890;允许
676 //delete $.n;//不允许
677 //既阻止追加,也阻止删除,并且还不允许修改属性

测试页面如下:

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>用户管理案例</title>
  6     <style>
  7         table,tr,th,td{
  8             border: 1px solid green; /*边框*/
  9         }
 10         #main{
 11             margin: 10% 20% auto;
 12         }
 13         caption{
 14             font-size: large;
 15             font-weight: bold;
 16         }
 17         table{
 18             border-collapse:collapse;
 19             table-layout:fixed;
 20             width: 600px;
 21             height: 400px;
 22         }
 23         a{
 24             text-decoration: none;
 25         }
 26         tr{
 27             background-color: lightgray;
 28 
 29         }
 30         td{
 31             width:100px;             /*单元格宽度*/
 32             height:50px;             /*单元格高度*/
 33             text-align:center;       /*单元格文字居中对齐*/
 34         }
 35         .currentPage{
 36             background-color: red;
 37         }
 38 
 39     </style>
 40 </head>
 41 <body>
 42 <div id="main">
 43     <table>
 44         <caption>用户管理</caption>
 45         <thead>
 46         <tr>
 47             <th colspan="3"  class="toolbar">
 48                 <button on:click="addUser($add)">$add</button>&nbsp;<button on:click="delUser()">$del</button>&nbsp;<button on:click="updateUser()">$update</button>&nbsp;<button on:click="saveUser()">$save</button>
 49             </th>
 50         </tr>
 51         <tr>
 52             <th>用户名</th>
 53             <th>联系方式</th>
 54             <th>性别</th>
 55         </tr>
 56         </thead>
 57         <tbody class="content" jxr:for="$rows" on:click="onclick()">
 58         <tr  data-userid="$user.userid" data-checked="$checked">
 59             <td><input type="text" value="$user.name"></td>
 60             <td><input type="text" value="$user.mobile"></td>
 61             <td>$user.gender?'男':'女'$</td>
 62         </tr>
 63         </tbody>
 64         <tfoot>
 65         <tr>
 66             <th colspan="3" class="pagination">
 67                 总共<span>$pages</span>&nbsp;<span class="pages" jxr:for="$pages"><a href="javascript:void(0)" on:click="search()">$count&nbsp;&nbsp;&nbsp;&nbsp;</a></span> 68             </th>
 69         </tr>
 70         </tfoot>
 71     </table>
 72 </div>
 73 <script type="text/javascript" src="lib.js"></script>
 74 <script type="text/javascript">
 75 //处理工具栏
 76     var Toolbar=new JXR({
 77         el:'.toolbar',
 78         data:{
 79             add:'追加用户',
 80             update:'修改用户',
 81             del:'删除用户',
 82             save:'保存用户'
 83         },
 84         on:{
 85             addUser:function (a) {
 86                 alert('add:'+a);
 87                 //this.data.add='add';
 88             },
 89             delUser:function (ev) {
 90                 alert('del');
 91                 
 92             },
 93             updateUser:function (ev) {
 94                 alert('update');
 95             },
 96             saveUser:function (ev) {
 97                 alert('save');
 98             },
 99         }
100      });
101     //内容
102     var Content=new JXR({
103         el:'.content',
104         data:{
105             rows:[{
106                 checked:false,
107                 editable:true,
108                 user:{
109                     userid:'1',
110                     name:'张三',
111                     mobile:'1234',
112                     gender:false
113                 }
114             }],
115         },
116         on:{
117             onclick:function(ev){
118                 //alert('click');
119                 this.data=null;
120             }
121         }
122     });
123     //分页
124     var Pagination=new JXR({
125         el:'.pagination',
126         data:{
127             pages:5
128         },
129         on:{
130             search:function (ev) {
131                 alert('search');
132             }
133         }
134     });
135     var data={
136         data:{
137             hello:'你好'
138         },
139         on:{
140             hit:function (ev) {
141                 //this.hello='不好';
142                 alert(this.data.hello);
143             }
144         }
145     };
146    
147     var obj=JXR.bind('<div on:click="hit()"><input type="radio" checked="$hello$" value="$hello"></div>',data);
148     document.getElementById('main').appendChild(obj.el);
149    </script>
150 </body>
151 </html>

大家可以看看效果,有不清楚代码的可以加我qq讨论:412383550

猜你喜欢

转载自www.cnblogs.com/jjh-java/p/10281715.html
今日推荐