首页 > 代码库 > 【UI插件】简单的日历插件(下)—— 学习MVC思想

【UI插件】简单的日历插件(下)—— 学习MVC思想

前言

我们上次写了一个简单的日历插件,但是只是一个半成品,而且做完后发现一些问题,于是我们今天尝试来解决这些问题

PS:距离上次貌似很久了

上次,我们大概遇到哪些问题呢:

① 既然想做一套UI库,那么就应该考虑其它UI库的接入问题

这个意思就是,我们的系统中所有UI插件应该有一些统一行为,我们如果希望统一为所有的插件加一点什么东西,需要有位置可加

这个意味着,可能我们所有的插件需要继承至一个抽象的UI类,并且该类提供了通用的几个事件点

② 上次做的日历插件虽然说是简单,其耦合还是比较严重的(其实也说不上,但是人总有想装B的时候)

这个怎么说呢,就日历而言,我们可以将之分成三个部分

1 日历核心部分,用于生产静态html

2 日历数据部分,用于显示各个特殊信息,比如节日什么的

3 日历事件部分,现在的想法便是可以将事件相关给抽象出来

目的便是html/data/events 分开一点点,这个该怎么做呢?这是我们今天该思考的问题

事情多了就什么都不能解决,所以我们今天暂时便处理以上两个问题即可

MVC的学习

由于我们会依赖于underscore,所以,我们这里有一个underscore的扩展,加一些我们自己需要的东西

 1 (function () {
 2 
 3   // @description 全局可能用到的变量
 4   var arr = [];
 5   var slice = arr.slice;
 6 
 7   var method = method || {};
 8 
 9   /**
10   * @description inherit方法,js的继承,默认为两个参数
11   * @param {function} supClass 可选,要继承的类
12   * @param {object} subProperty 被创建类的成员
13   * @return {function} 被创建的类
14   */
15   method.inherit = function () {
16 
17     // @description 参数检测,该继承方法,只支持一个参数创建类,或者两个参数继承类
18     if (arguments.length === 0 || arguments.length > 2) throw ‘参数错误‘;
19 
20     var parent = null;
21 
22     // @description 将参数转换为数组
23     var properties = slice.call(arguments);
24 
25     // @description 如果第一个参数为类(function),那么就将之取出
26     if (typeof properties[0] === ‘function‘)
27       parent = properties.shift();
28     properties = properties[0];
29 
30     // @description 创建新类用于返回
31     function klass() {
32       if (_.isFunction(this.initialize))
33         this.initialize.apply(this, arguments);
34     }
35 
36     klass.superclass = parent;
37     // parent.subclasses = [];
38 
39     if (parent) {
40       // @description 中间过渡类,防止parent的构造函数被执行
41       var subclass = function () { };
42       subclass.prototype = parent.prototype;
43       klass.prototype = new subclass();
44       // parent.subclasses.push(klass);
45     }
46 
47     var ancestor = klass.superclass && klass.superclass.prototype;
48     for (var k in properties) {
49       var value =http://www.mamicode.com/ properties[k];
50 
51       //满足条件就重写
52       if (ancestor && typeof value =http://www.mamicode.com/= ‘function‘) {
53         var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, ‘‘).split(‘,‘);
54         //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
55         if (argslist[0] === ‘$super‘ && ancestor[k]) {
56           value = http://www.mamicode.com/(function (methodName, fn) {
57             return function () {
58               var scope = this;
59               var args = [function () {
60                 return ancestor[methodName].apply(scope, arguments);
61               } ];
62               return fn.apply(this, args.concat(slice.call(arguments)));
63             };
64           })(k, value);
65         }
66       }
67 
68       //此处对对象进行扩展,当前原型链已经存在该对象,便进行扩展
69       if (_.isObject(klass.prototype[k]) && _.isObject(value) && (typeof klass.prototype[k] != ‘function‘ && typeof value != ‘fuction‘)) {
70         //原型链是共享的,这里不好办
71         var temp = {};
72         _.extend(temp, klass.prototype[k]);
73         _.extend(temp, value);
74         klass.prototype[k] = temp;
75       } else {
76         klass.prototype[k] = value;
77       }
78 
79     }
80 
81     if (!klass.prototype.initialize)
82       klass.prototype.initialize = function () { };
83 
84     klass.prototype.constructor = klass;
85 
86     return klass;
87   };
88 
89   _.extend(_, method);
90 
91 })(window);
View Code

对的,以上是我们前面实现的继承,我们将之扩展至underscore上,以后以此实现继承

其次,我们便需要思考如何分离我们的数据/模板/事件了

View/Adapter/ViewController

俗话说,大树底下好乘凉,事实上我一些想法来自于我的老大,我老大又借鉴了原来的ios开发,所以这里形成了一些东西,不知道是否合理,我们拿出来看看

View

首先,无论如何我们的应用都会有一个view的存在,我们认为view只做简单的页面渲染就好,与之有关的数据/事件什么的,我们不予关注

 1 // @description 正式的声明Dalmatian框架的命名空间
 2 var Dalmatian = Dalmatian || {};
 3 
 4 // @description 定义默认的template方法来自于underscore
 5 Dalmatian.template = _.template;
 6 Dalmatian.View = _.inherit({
 7   // @description 构造函数入口
 8   initialize: function(options) {
 9     this._initialize();
10     this.handleOptions(options);
11 
12   },
13 
14   // @description 设置默认属性
15   _initialize: function() {
16 
17     var DEFAULT_CONTAINER_TEMPLATE = ‘<section class="view" id="<%=viewid%>"><%=html%></section>‘;
18 
19     // @description view状态机
20     // this.statusSet = {};
21 
22     this.defaultContainerTemplate = DEFAULT_CONTAINER_TEMPLATE;
23 
24     // @override
25     // @description template集合,根据status做template的map
26     // @example
27     //    { 0: ‘<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>‘ }
28     // this.templateSet = {};
29 
30     this.viewid = _.uniqueId(‘dalmatian-view-‘);
31 
32   },
33 
34   // @description 操作构造函数传入操作
35   handleOptions: function(options) {
36     // @description 从形参中获取key和value绑定在this上
37     if (_.isObject(options)) _.extend(this, options);
38 
39   },
40 
41   // @description 通过模板和数据渲染具体的View
42   // @param status {enum} View的状态参数
43   // @param data {object} 匹配View的数据格式的具体数据
44   // @param callback {functiion} 执行完成之后的回调
45   render: function(status, data, callback) {
46 
47     var templateSelected = this.templateSet[status];
48     if (templateSelected) {
49 
50       try {
51         // @description 渲染view
52         var templateFn = Dalmatian.template(templateSelected);
53         this.html = templateFn(data);
54 
55         // @description 在view外层加入外壳
56         templateFn = Dalmatian.template(this.defaultContainerTemplate);
57         this.html = templateFn({
58           viewid: this.viewid,
59           html: this.html
60         });
61 
62         this.currentStatus = status;
63 
64         _.callmethod(callback, this);
65 
66         return true;
67 
68       } catch (e) {
69 
70         throw e;
71 
72       } finally {
73 
74         return false;
75       }
76     }
77   },
78 
79   // @override
80   // @description 可以被复写,当status和data分别发生变化时候
81   // @param status {enum} view的状态值
82   // @param data {object} viewmodel的数据
83   update: function(status, data) {
84 
85     if (!this.currentStatus || this.currentStatus !== status) {
86       return this.render(status, data);
87     }
88 
89     // @override
90     // @description 可复写部分,当数据发生变化但是状态没有发生变化时,页面仅仅变化的可以是局部显示
91     //              可以通过获取this.html进行修改
92     _.callmethod(this.onUpdate, this);
93   }
94 });
View Code

从代码上看,我们需要注意几个事情:

① View会生成静态HTML

② View会根据当前状态、当前数据生成静态HTML

所以,我们的View事实上只会生成静态HTML,不同的是他会根据不同的状态生成不同的HTML,比如初始状态和加载结束状态

有了View便缺不了数据,也就是所谓的Model,我们这里给他取一个名字,Adapter

Adapter

 1 Dalmatian.Adapter = _.inherit({
 2 
 3   // @description 构造函数入口
 4   initialize: function(options) {
 5     this._initialize();
 6     this.handleOptions(options);
 7 
 8   },
 9 
10   // @description 设置默认属性
11   _initialize: function() {
12     this.observers = [];
13     this.viewmodel = {};
14     this.datamodel = {};
15   },
16 
17   // @description 操作构造函数传入操作
18   handleOptions: function(options) {
19     // @description 从形参中获取key和value绑定在this上
20     if (_.isObject(options)) _.extend(this, options);
21   },
22 
23   // @description 设置
24   format: function(origindata){
25     this.datamodel = origindata;
26     this.viewmodel = this.parse(origindata);
27     return this.viewmodel;
28   },
29 
30   // @override
31   // @description parse方法用来将datamodel转化为viewmodel,必须被重写
32   parse: function(origindata) {
33     throw Error(‘方法必须被重写‘);
34   },
35 
36   registerObserver: function(viewcontroller) {
37     // @description 检查队列中如果没有viewcontroller,从队列尾部推入
38     if (!_.contains(this.observers, viewcontroller)) {
39       this.observers.push(viewcontroller);
40     }
41   },
42 
43   unregisterObserver: function(viewcontroller) {
44     // @description 从observers的队列中剔除viewcontroller
45     this.observers = _.without(this.observers, viewcontroller);
46   },
47 
48   notifyDataChanged: function() {
49     // @description 通知所有注册的观察者被观察者的数据发生变化
50     var data = http://www.mamicode.com/this.format(this.datamodel);
51     _.each(this.observers, function(viewcontroller) {
52       if (_.isObject(viewcontroller))
53         _.callmethod(viewcontroller.update, viewcontroller, [data]);
54     });
55   }
56 });
View Code

Adapter由以下几个关键组成:

① View观察者

② 数据模型,便是原始的数据

③ viewModel,便是view实际需要的数据

并且每一次数据的改变会通知观察的view,触发其update,所以Adapter的组成也比较干脆,并不复杂

但是,我们的view仍然没有事件,而且Adapter也没有与view联系起来,这个时候我们缺少一个要件,他的名字是Controller

ViewController

控制器是链接模型与视图的桥梁,我们这里也不例外,核心动作皆会在控制器处完成

  1 Dalmatian.ViewController = _.inherit({
  2 
  3   // @description 构造函数入口
  4   initialize: function (options) {
  5     this.handleOptions(options);
  6     this.create();
  7   },
  8 
  9   // @description 操作构造函数传入操作
 10   handleOptions: function (options) {
 11     this._verify(options);
 12 
 13     // @description 从形参中获取key和value绑定在this上
 14     if (_.isObject(options)) _.extend(this, options);
 15   },
 16 
 17   // @description 验证参数
 18   _verify: function (options) {
 19     if (!_.property(‘view‘)(options)) throw Error(‘view必须在实例化的时候传入ViewController‘);
 20   },
 21 
 22   // @description 当数据发生变化时调用onViewUpdate,如果onViewUpdate方法不存在的话,直接调用render方法重绘
 23   update: function (data) {
 24 
 25     _.callmethod(this.hide, this);
 26 
 27     if (!_.callmethod(this.onViewUpdate, this, [data])) {
 28       this.render();
 29     }
 30 
 31     _.callmethod(this.show, this);
 32   },
 33 
 34   /**
 35   * @description 传入事件对象,解析之,解析event,返回对象{events: [{target: ‘#btn‘, event:‘click‘, callback: handler}]}
 36   * @param events {obj} 事件对象,默认传入唯一id
 37   * @param namespace 事件命名空间
 38   * @return {obj}
 39   */
 40   parseEvents: function (events) {
 41 
 42     //用于返回的事件对象
 43     var eventArr = [];
 44     //注意,此处做简单的字符串数据解析即可,不做实际业务
 45     for (var key in events) {
 46       var method = events[key];
 47       if (!_.isFunction(method)) method = this[events[key]];
 48       if (!method) continue;
 49 
 50       var match = key.match(delegateEventSplitter);
 51       var eventName = match[1],
 52         selector = match[2];
 53       method = _.bind(method, this);
 54       eventName += ‘.delegateEvents‘ + this.view.viewid;
 55       eventArr.push({
 56         target: selector,
 57         event: eventName,
 58         callback: method
 59       });
 60     }
 61 
 62     return eventArr;
 63   },
 64 
 65   /**
 66    * @override
 67    *
 68    */
 69   render: function() {
 70     // @notation  这个方法需要被复写
 71     // var data = http://www.mamicode.com/this.adapter.format(this.origindata);
 72     // this.view.render(this.viewstatus, data);
 73   },
 74 
 75   _create: function () {
 76     this.render();
 77   },
 78 
 79   create: function () {
 80 
 81     var $element = selectDom(this.view.viewid);
 82     if (domImplement($element, ‘get‘, false, [0])) {
 83       return _.callmethod(this.recreate, this);
 84     }
 85 
 86     // @notation 在create方法调用前后设置onViewBeforeCreate和onViewAfterCreate两个回调
 87     _.wrapmethod(this._create, ‘onViewBeforeCreate‘, ‘onViewAfterCreate‘, this);
 88 
 89   },
 90 
 91   /**
 92   * @description 如果进入create判断是否需要update一下页面,sync view和viewcontroller的数据
 93   */
 94   _recreate: function () {
 95     this.update();
 96   },
 97 
 98   recreate: function () {
 99     _.wrapmethod(this._recreate, ‘onViewBeforeRecreate‘, ‘onViewAfterRecreate‘, this);
100   },
101 
102   _bind: function () {
103     this.viewcontent = createDom(this.view.html);
104 
105     var eventsList = this.parseEvents(this.events);
106 
107     var scope = this;
108     _.each(eventsList, function (item) {
109 
110       if (item.target === ‘‘) {
111         eventmethod(scope.viewcontent, ‘on‘, item.event, item.callback, scope);
112       } else {
113         eventmethod(scope.viewcontent, ‘on‘, item.event, item.callback, scope, item.target);
114       }
115 
116     });
117   },
118 
119   bind: function () {
120     _.wrapmethod(this._bind, ‘onViewBeforeBind‘, ‘onViewAfterBind‘, this);
121   },
122 
123   _show: function () {
124     var $element = selectDom(‘#‘ + this.view.viewid);
125 
126     // @notation 需要剔除码?
127     // if ((!$element || $element.length === 0) && this.viewcontent) {
128       var $container = selectDom(this.container);
129       domImplement($container, ‘html‘, false, [this.viewcontent]);
130     // }
131 
132     domImplement($element, ‘show‘);
133   },
134 
135   show: function () {
136     this.bind();
137 
138     _.wrapmethod(this._show, ‘onViewBeforeShow‘, ‘onViewAfterShow‘, this);
139   },
140 
141   _hide: function () {
142     var $element = selectDom(‘#‘ + this.view.viewid);
143     domImplement($element, ‘hide‘);
144   },
145 
146   hide: function () {
147     _.wrapmethod(this._hide, ‘onViewBeforeHide‘, ‘onViewAfterHide‘, this);
148 
149     this.forze();
150   },
151 
152   _forze: function () {
153     var $element = selectDom(‘#‘ + this.view.viewid);
154     domImplement($element, ‘off‘);
155   },
156 
157   forze: function () {
158     _.wrapmethod(this._forze, ‘onViewBeforeForzen‘, ‘onViewAfterForzen‘, this);
159   },
160 
161   _destory: function () {
162     var $element = selectDom(‘#‘ + this.view.viewid).remove();
163     domImplement($element, ‘remove‘);
164   },
165 
166   destory: function () {
167     _.wrapmethod(this._destory, ‘onViewBeforeDestory‘, ‘onViewAfterDestory‘, this);
168   }
169 });
View Code

control这里便有所不同,会稍微复杂一点点

① 首先,他会验证自己是否含有view参数,我们这里要求一个控制器必须对应一个view,如果没有指定的话便认为错误

if (!_.property(‘view‘)(options)) throw Error(‘view必须在实例化的时候传入ViewController‘);

② 然后主要有几个关键事件点,第一个是create

PS:这里会区分是否二次创建该View,这个判断事实上不应该通过dom是否存在来判断,这里后期优化

create调用便会调用view的render方法,然后便会构建相关的dom结构,并且append到container中

③ 第二个关键事件点为show,调用时,dom会真正的显示,并且绑定事件

PS:事件这块借鉴的Backbone的机制,全部绑定值根元素,具体优化后面再说吧

④ 除此之外还有hide、forze(解除事件句柄,释放资源)、destroy等不详说了

说了这么多都是扯淡,我们下面以两个简单的例子做一次说明

实例说明

MVC学习完整代码

有不对的地方请提出

  1 "use strict";
  2 
  3 // @notation 本框架默认是以来于zepto。这里构建了基础的方法层,当用户使用其他框架时,可能需要复写这几个基础方法
  4 
  5 // @description 解析event参数的正则
  6 var delegateEventSplitter = /^(\S+)\s*(.*)$/;
  7 // Regular expression used to split event strings.
  8 var eventSplitter = /\s+/;
  9 
 10 // ----------------------------------------------------
 11 // @notation 从backbone中借鉴而来,用来多事件绑定的events
 12 
 13 // Implement fancy features of the Events API such as multiple event
 14 // names `"change blur"` and jQuery-style event maps `{change: action}`
 15 // in terms of the existing API.
 16 var eventoperator = function(obj, action, name, rest) {
 17   if (!name) return true;
 18 
 19   // Handle event maps.
 20   if (typeof name === ‘object‘) {
 21     for (var key in name) {
 22       obj[action].apply(obj, [key, name[key]].concat(rest));
 23     }
 24     return false;
 25   }
 26 
 27   // Handle space separated event names.
 28   if (eventSplitter.test(name)) {
 29     var names = name.split(eventSplitter);
 30     for (var i = 0, length = names.length; i < length; i++) {
 31       obj[action].apply(obj, [names[i]].concat(rest));
 32     }
 33     return false;
 34   }
 35 
 36   return true;
 37 };
 38 // ----------------------------------------------------
 39 
 40 // @notation 默认使用zepto的事件委托机制
 41 function eventmethod(obj, action, name, callback, context, subobj) {
 42   // _.bind(callback, context || this);
 43 
 44   var delegate = function(target, eventName, eventCallback, subtarget) {
 45     if (subtarget) {
 46       target.on(eventName, subtarget, eventCallback);
 47     }else{
 48       target.on(eventName, eventCallback);
 49     }
 50   };
 51 
 52   var undelegate = function(target, eventName, eventCallback, subtarget) {
 53     if (subtarget) {
 54       target.off(eventName, subtarget, eventCallback);
 55     }else{
 56       target.off(eventName, eventCallback);
 57     }
 58   };
 59 
 60   var trigger = function(target, eventName, subtarget) {
 61     if (subtarget) {
 62       target.find(subtarget).trigger(eventName);
 63     }else{
 64       target.trigger(eventName);
 65     }
 66   };
 67 
 68   var map = {
 69     ‘on‘: delegate,
 70     ‘bind‘: delegate,
 71     ‘off‘: undelegate,
 72     ‘unbind‘: undelegate,
 73     ‘trigger‘: trigger
 74   };
 75 
 76   if (_.isFunction(map[action])) {
 77     map[action](obj, name, callback, subobj);
 78   }
 79 
 80 }
 81 
 82 // @description 选择器
 83 function selectDom(selector) {
 84   return $(selector);
 85 }
 86 
 87 function domImplement($element, action, context, param) {
 88   if (_.isFunction($element[action]))
 89     $element[action].apply(context || $element, param);
 90 }
 91 
 92 function createDom (html) {
 93   return $(html);
 94 }
 95 
 96 // --------------------------------------------------- //
 97 // ------------------华丽的分割线--------------------- //
 98 
 99 // @description 正式的声明Dalmatian框架的命名空间
100 var Dalmatian = Dalmatian || {};
101 
102 // @description 定义默认的template方法来自于underscore
103 Dalmatian.template = _.template;
104 Dalmatian.View = _.inherit({
105   // @description 构造函数入口
106   initialize: function(options) {
107     this._initialize();
108     this.handleOptions(options);
109 
110   },
111 
112   // @description 设置默认属性
113   _initialize: function() {
114 
115     var DEFAULT_CONTAINER_TEMPLATE = ‘<section class="view" id="<%=viewid%>"><%=html%></section>‘;
116 
117     // @description view状态机
118     // this.statusSet = {};
119 
120     this.defaultContainerTemplate = DEFAULT_CONTAINER_TEMPLATE;
121 
122     // @override
123     // @description template集合,根据status做template的map
124     // @example
125     //    { 0: ‘<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>‘ }
126     // this.templateSet = {};
127 
128     this.viewid = _.uniqueId(‘dalmatian-view-‘);
129 
130   },
131 
132   // @description 操作构造函数传入操作
133   handleOptions: function(options) {
134     // @description 从形参中获取key和value绑定在this上
135     if (_.isObject(options)) _.extend(this, options);
136 
137   },
138 
139   // @description 通过模板和数据渲染具体的View
140   // @param status {enum} View的状态参数
141   // @param data {object} 匹配View的数据格式的具体数据
142   // @param callback {functiion} 执行完成之后的回调
143   render: function(status, data, callback) {
144 
145     var templateSelected = this.templateSet[status];
146     if (templateSelected) {
147 
148       try {
149         // @description 渲染view
150         var templateFn = Dalmatian.template(templateSelected);
151         this.html = templateFn(data);
152 
153         // @description 在view外层加入外壳
154         templateFn = Dalmatian.template(this.defaultContainerTemplate);
155         this.html = templateFn({
156           viewid: this.viewid,
157           html: this.html
158         });
159 
160         this.currentStatus = status;
161 
162         _.callmethod(callback, this);
163 
164         return true;
165 
166       } catch (e) {
167 
168         throw e;
169 
170       } finally {
171 
172         return false;
173       }
174     }
175   },
176 
177   // @override
178   // @description 可以被复写,当status和data分别发生变化时候
179   // @param status {enum} view的状态值
180   // @param data {object} viewmodel的数据
181   update: function(status, data) {
182 
183     if (!this.currentStatus || this.currentStatus !== status) {
184       return this.render(status, data);
185     }
186 
187     // @override
188     // @description 可复写部分,当数据发生变化但是状态没有发生变化时,页面仅仅变化的可以是局部显示
189     //              可以通过获取this.html进行修改
190     _.callmethod(this.onUpdate, this);
191   }
192 });
193 
194 Dalmatian.Adapter = _.inherit({
195 
196   // @description 构造函数入口
197   initialize: function(options) {
198     this._initialize();
199     this.handleOptions(options);
200 
201   },
202 
203   // @description 设置默认属性
204   _initialize: function() {
205     this.observers = [];
206     this.viewmodel = {};
207     this.datamodel = {};
208   },
209 
210   // @description 操作构造函数传入操作
211   handleOptions: function(options) {
212     // @description 从形参中获取key和value绑定在this上
213     if (_.isObject(options)) _.extend(this, options);
214   },
215 
216   // @description 设置
217   format: function(origindata){
218     this.datamodel = origindata;
219     this.viewmodel = this.parse(origindata);
220     return this.viewmodel;
221   },
222 
223   // @override
224   // @description parse方法用来将datamodel转化为viewmodel,必须被重写
225   parse: function(origindata) {
226     throw Error(‘方法必须被重写‘);
227   },
228 
229   registerObserver: function(viewcontroller) {
230     // @description 检查队列中如果没有viewcontroller,从队列尾部推入
231     if (!_.contains(this.observers, viewcontroller)) {
232       this.observers.push(viewcontroller);
233     }
234   },
235 
236   unregisterObserver: function(viewcontroller) {
237     // @description 从observers的队列中剔除viewcontroller
238     this.observers = _.without(this.observers, viewcontroller);
239   },
240 
241   notifyDataChanged: function() {
242     // @description 通知所有注册的观察者被观察者的数据发生变化
243     var data = http://www.mamicode.com/this.format(this.datamodel);
244     _.each(this.observers, function(viewcontroller) {
245       if (_.isObject(viewcontroller))
246         _.callmethod(viewcontroller.update, viewcontroller, [data]);
247     });
248   }
249 });
250 
251 Dalmatian.ViewController = _.inherit({
252 
253   // @description 构造函数入口
254   initialize: function (options) {
255     this.handleOptions(options);
256     this.create();
257   },
258 
259   // @description 操作构造函数传入操作
260   handleOptions: function (options) {
261     this._verify(options);
262 
263     // @description 从形参中获取key和value绑定在this上
264     if (_.isObject(options)) _.extend(this, options);
265   },
266 
267   // @description 验证参数
268   _verify: function (options) {
269     if (!_.property(‘view‘)(options)) throw Error(‘view必须在实例化的时候传入ViewController‘);
270   },
271 
272   // @description 当数据发生变化时调用onViewUpdate,如果onViewUpdate方法不存在的话,直接调用render方法重绘
273   update: function (data) {
274 
275     _.callmethod(this.hide, this);
276 
277     if (!_.callmethod(this.onViewUpdate, this, [data])) {
278       this.render();
279     }
280 
281     _.callmethod(this.show, this);
282   },
283 
284   /**
285   * @description 传入事件对象,解析之,解析event,返回对象{events: [{target: ‘#btn‘, event:‘click‘, callback: handler}]}
286   * @param events {obj} 事件对象,默认传入唯一id
287   * @param namespace 事件命名空间
288   * @return {obj}
289   */
290   parseEvents: function (events) {
291 
292     //用于返回的事件对象
293     var eventArr = [];
294     //注意,此处做简单的字符串数据解析即可,不做实际业务
295     for (var key in events) {
296       var method = events[key];
297       if (!_.isFunction(method)) method = this[events[key]];
298       if (!method) continue;
299 
300       var match = key.match(delegateEventSplitter);
301       var eventName = match[1],
302         selector = match[2];
303       method = _.bind(method, this);
304       eventName += ‘.delegateEvents‘ + this.view.viewid;
305       eventArr.push({
306         target: selector,
307         event: eventName,
308         callback: method
309       });
310     }
311 
312     return eventArr;
313   },
314 
315   /**
316    * @override
317    *
318    */
319   render: function() {
320     // @notation  这个方法需要被复写
321     // var data = http://www.mamicode.com/this.adapter.format(this.origindata);
322     // this.view.render(this.viewstatus, data);
323   },
324 
325   _create: function () {
326     this.render();
327   },
328 
329   create: function () {
330 
331     var $element = selectDom(this.view.viewid);
332     if (domImplement($element, ‘get‘, false, [0])) {
333       return _.callmethod(this.recreate, this);
334     }
335 
336     // @notation 在create方法调用前后设置onViewBeforeCreate和onViewAfterCreate两个回调
337     _.wrapmethod(this._create, ‘onViewBeforeCreate‘, ‘onViewAfterCreate‘, this);
338 
339   },
340 
341   /**
342   * @description 如果进入create判断是否需要update一下页面,sync view和viewcontroller的数据
343   */
344   _recreate: function () {
345     this.update();
346   },
347 
348   recreate: function () {
349     _.wrapmethod(this._recreate, ‘onViewBeforeRecreate‘, ‘onViewAfterRecreate‘, this);
350   },
351 
352   _bind: function () {
353     this.viewcontent = createDom(this.view.html);
354 
355     var eventsList = this.parseEvents(this.events);
356 
357     var scope = this;
358     _.each(eventsList, function (item) {
359 
360       if (item.target === ‘‘) {
361         eventmethod(scope.viewcontent, ‘on‘, item.event, item.callback, scope);
362       } else {
363         eventmethod(scope.viewcontent, ‘on‘, item.event, item.callback, scope, item.target);
364       }
365 
366     });
367   },
368 
369   bind: function () {
370     _.wrapmethod(this._bind, ‘onViewBeforeBind‘, ‘onViewAfterBind‘, this);
371   },
372 
373   _show: function () {
374     var $element = selectDom(‘#‘ + this.view.viewid);
375 
376     // @notation 需要剔除码?
377     // if ((!$element || $element.length === 0) && this.viewcontent) {
378       var $container = selectDom(this.container);
379       domImplement($container, ‘html‘, false, [this.viewcontent]);
380     // }
381 
382     domImplement($element, ‘show‘);
383   },
384 
385   show: function () {
386     this.bind();
387 
388     _.wrapmethod(this._show, ‘onViewBeforeShow‘, ‘onViewAfterShow‘, this);
389   },
390 
391   _hide: function () {
392     var $element = selectDom(‘#‘ + this.view.viewid);
393     domImplement($element, ‘hide‘);
394   },
395 
396   hide: function () {
397     _.wrapmethod(this._hide, ‘onViewBeforeHide‘, ‘onViewAfterHide‘, this);
398 
399     this.forze();
400   },
401 
402   _forze: function () {
403     var $element = selectDom(‘#‘ + this.view.viewid);
404     domImplement($element, ‘off‘);
405   },
406 
407   forze: function () {
408     _.wrapmethod(this._forze, ‘onViewBeforeForzen‘, ‘onViewAfterForzen‘, this);
409   },
410 
411   _destory: function () {
412     var $element = selectDom(‘#‘ + this.view.viewid).remove();
413     domImplement($element, ‘remove‘);
414   },
415 
416   destory: function () {
417     _.wrapmethod(this._destory, ‘onViewBeforeDestory‘, ‘onViewAfterDestory‘, this);
418   }
419 });
View Code

underscore扩展

  1 (function () {
  2 
  3   // @description 全局可能用到的变量
  4   var arr = [];
  5   var slice = arr.slice;
  6 
  7   var method = method || {};
  8 
  9 
 10   /**
 11   * @description inherit方法,js的继承,默认为两个参数
 12   * @param {function} supClass 可选,要继承的类
 13   * @param {object} subProperty 被创建类的成员
 14   * @return {function} 被创建的类
 15   */
 16   method.inherit = function () {
 17 
 18     // @description 参数检测,该继承方法,只支持一个参数创建类,或者两个参数继承类
 19     if (arguments.length === 0 || arguments.length > 2) throw ‘参数错误‘;
 20 
 21     var parent = null;
 22 
 23     // @description 将参数转换为数组
 24     var properties = slice.call(arguments);
 25 
 26     // @description 如果第一个参数为类(function),那么就将之取出
 27     if (typeof properties[0] === ‘function‘)
 28       parent = properties.shift();
 29     properties = properties[0];
 30 
 31     // @description 创建新类用于返回
 32     function klass() {
 33       if (_.isFunction(this.initialize))
 34         this.initialize.apply(this, arguments);
 35     }
 36 
 37     klass.superclass = parent;
 38     // parent.subclasses = [];
 39 
 40     if (parent) {
 41       // @description 中间过渡类,防止parent的构造函数被执行
 42       var subclass = function () { };
 43       subclass.prototype = parent.prototype;
 44       klass.prototype = new subclass();
 45       // parent.subclasses.push(klass);
 46     }
 47 
 48     var ancestor = klass.superclass && klass.superclass.prototype;
 49     for (var k in properties) {
 50       var value =http://www.mamicode.com/ properties[k];
 51 
 52       //满足条件就重写
 53       if (ancestor && typeof value =http://www.mamicode.com/= ‘function‘) {
 54         var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, ‘‘).split(‘,‘);
 55         //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
 56         if (argslist[0] === ‘$super‘ && ancestor[k]) {
 57           value = http://www.mamicode.com/(function (methodName, fn) {
 58             return function () {
 59               var scope = this;
 60               var args = [function () {
 61                 return ancestor[methodName].apply(scope, arguments);
 62               } ];
 63               return fn.apply(this, args.concat(slice.call(arguments)));
 64             };
 65           })(k, value);
 66         }
 67       }
 68 
 69       //此处对对象进行扩展,当前原型链已经存在该对象,便进行扩展
 70       if (_.isObject(klass.prototype[k]) && _.isObject(value) && (typeof klass.prototype[k] != ‘function‘ && typeof value != ‘fuction‘)) {
 71         //原型链是共享的,这里不好办
 72         var temp = {};
 73         _.extend(temp, klass.prototype[k]);
 74         _.extend(temp, value);
 75         klass.prototype[k] = temp;
 76       } else {
 77         klass.prototype[k] = value;
 78       }
 79 
 80     }
 81 
 82     if (!klass.prototype.initialize)
 83       klass.prototype.initialize = function () { };
 84 
 85     klass.prototype.constructor = klass;
 86 
 87     return klass;
 88   };
 89 
 90   // @description 返回需要的函数
 91   method.getNeedFn = function (key, scope) {
 92     scope = scope || window;
 93     if (_.isFunction(key)) return key;
 94     if (_.isFunction(scope[key])) return scope[key];
 95     return function () { };
 96   };
 97 
 98   method.callmethod = function (method, scope, params) {
 99     scope = scope || this;
100     if (_.isFunction(method)) {
101       method.apply(scope, params);
102       return true;
103     }
104 
105     return false;
106   };
107 
108   /**
109   * @description 在fn方法的前后通过键值设置两个传入的回调
110   * @param fn {function} 调用的方法
111   * @param beforeFnKey {string} 从context对象中获得的函数指针的键值,该函数在fn前执行
112   * @param afterFnKey {string} 从context对象中获得的函数指针的键值,该函数在fn后执行
113   * @param context {object} 执行环节的上下文
114   * @return {function}
115   */
116   method.wrapmethod = method.insert = function (fn, beforeFnKey, afterFnKey, context) {
117 
118     var scope = context || this;
119     var action = _.wrap(fn, function (func) {
120 
121       _.callmethod(_.getNeedFn(beforeFnKey, scope), scope);
122 
123       func.call(scope);
124 
125       _.callmethod(_.getNeedFn(afterFnKey, scope), scope);
126     });
127 
128     return _.callmethod(action, scope);
129   }
130 
131 
132   _.extend(_, method);
133 
134 })(window);
View Code

简单alert框

首先我们来做一个简单的alert框,这个框在我们点击界面时候弹出一个提示,提示文字由文本框给出

第一步便是简单的HTML了

 1 <!doctype html>
 2 <html lang="en">
 3 <head>
 4   <meta charset="UTF-8">
 5   <title>ToDoList</title>
 6   <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7   <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/bootstrap/css/bootstrap.css">
 8   <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/css/flat-ui.css">
 9   <link href="../style/main.css" rel="stylesheet" type="text/css" />
10   <style type="text/css">
11   .cui-alert { width: auto; position: static; }
12   .txt { border: #cfcfcf 1px solid; margin: 10px 0; width: 80%; }
13   </style>
14 </head>
15 <body>
16   <article class="container">
17   </article>
18   <input type="text" id="addmsg" class="txt">
19   <button id="addbtn" class="btn">
20     show message</button>
21   <script type="text/underscore-template" id="template-alert">
22       <div class=" cui-alert" >
23         <div class="cui-pop-box">
24           <div class="cui-bd">
25             <p class="cui-error-tips"><%=content%></p>
26             <div class="cui-roller-btns">
27               <div class="cui-flexbd cui-btns-cancel"><%=cancel%></div>
28               <div class="cui-flexbd cui-btns-sure"><%=confirm%></div>
29             </div>
30           </div>
31         </div>
32       </div>
33   </script>
34   <script type="text/javascript" src="../../vendor/underscore-min.js"></script>
35   <script type="text/javascript" src="../../vendor/zepto.min.js"></script>
36   <script src="../../src/underscore.extend.js" type="text/javascript"></script>
37   <script src="../../src/mvc.js" type="text/javascript"></script>
38   <script type="text/javascript" src="ui.alert.js"></script>
39 </body>
40 </html>
View Code

因为该插件本身比较简单,不存在状态值便会,所以view定义如此即可

 1 var htmltemplate = $(‘#template-alert‘).html();
 2 
 3 var AlertView = _.inherit(Dalmatian.View, {
 4   templateSet: {
 5     0: htmltemplate
 6   },
 7 
 8   statusSet: {
 9     STATUS_INIT: 0
10   }
11 });

Adapter也比较简单

var Adapter = _.inherit(Dalmatian.Adapter, {
  parse: function (data) {
    return data;
  }
});

现在重点便是controller了

 1 var Controller = _.inherit(Dalmatian.ViewController, {
 2   render: function () {
 3     var data = http://www.mamicode.com/this.adapter.viewmodel;
 4     this.view.render(this.viewstatus, data);
 5   },
 6 
 7   set: function (options) {
 8     this.adapter.datamodel.content = options.content;
 9     this.adapter.notifyDataChanged();
10   },
11 
12   events: {
13     "click .cui-btns-cancel": "cancelaction"
14   },
15 
16   cancelaction: function () {
17     this.onCancelBtnClick();
18   },
19 
20   attr: function (key, value) {
21     this[key] = value;
22   }
23 });

这里有个不一样的地方便是,这里有一个Adapter的set方法,set之后会改变其状态,这里会发生一次通知view更新的动作

最后我们将之串联起来

var view = new AlertView()
var adapter = new Adapter();
var controller = new Controller({
  view: view,
  adapter: adapter,
  container: ‘.container‘,
  onViewBeforeCreate: function () {

    var origindata =http://www.mamicode.com/ {
      content: ‘fuck‘,
      confirm: ‘confirmbtn‘,
      cancel: ‘cancelbtn‘
    }

    this.adapter.format(origindata);

    this.adapter.registerObserver(this);
    this.viewstatus = this.view.statusSet.STATUS_INIT;
  },
  onCancelBtnClick: function () {
    alert(‘cancel 2‘)
  }
});

然后我们写一段业务代码

1 $(‘#addbtn‘).on(‘click‘, function (e) {
2   var content = $(‘#addmsg‘).val();
3   // adapter.datamodel.content = content;
4   // adapter.notifyDataChanged();
5   controller.set({ content: content });
6   controller.show();
7 });

基本完成我们的操作了

 

事实上,我对这段代码并不是十分满意,于是,我们这里做一次简单重构:

 1 var htmltemplate = $(‘#template-alert‘).html();
 2 
 3 var AlertView = _.inherit(Dalmatian.View, {
 4   templateSet: {
 5     0: htmltemplate
 6   },
 7   
 8   statusSet: {
 9     STATUS_INIT: 0
10   }
11 });
12 
13 
14 var Adapter = _.inherit(Dalmatian.Adapter, {
15   parse: function (data) {
16     return data;
17   }
18 });
19 
20 var Controller = _.inherit(Dalmatian.ViewController, {
21   //设置默认信息
22   _initialize: function () {
23     this.origindata =http://www.mamicode.com/ {
24       content: ‘‘,
25       confirm: ‘确定‘,
26       cancel: ‘取消‘
27     }
28   },
29 
30   initialize: function ($super, opts) {
31     this._initialize();
32     $super(opts);
33     this._init();
34   },
35 
36   //基础数据处理
37   _init: function () {
38     this.adapter.format(this.origindata);
39     this.adapter.registerObserver(this);
40     this.viewstatus = this.view.statusSet.STATUS_INIT;
41   },
42 
43   render: function () {
44     var data = http://www.mamicode.com/this.adapter.viewmodel;
45     this.view.render(this.viewstatus, data);
46   },
47 
48   set: function (options) {
49     _.extend(this.adapter.datamodel, options);
50 //    this.adapter.datamodel.content = options.content;
51     this.adapter.notifyDataChanged();
52   },
53 
54   events: {
55     "click .cui-btns-cancel": "cancelaction"
56   },
57 
58   cancelaction: function () {
59     this.onCancelBtnClick();
60   }
61 });
62 
63 var view = new AlertView()
64 var adapter = new Adapter();
65 
66 var controller = new Controller({
67   view: view,
68   adapter: adapter,
69   container: ‘.container‘,
70   onCancelBtnClick: function () {
71     alert(‘cancel 2‘)
72   }
73 });
74 
75 $(‘#addbtn‘).on(‘click‘, function (e) {
76   var content = $(‘#addmsg‘).val();
77   // adapter.datamodel.content = content;
78   // adapter.notifyDataChanged();
79   controller.set({ content: content, confirm: ‘确定1‘ });
80   controller.show();
81 });
View Code

这个例子结束后,我们来写另一个例子

todolist

Backbone有一个todoList,我们这里也来写一个阉割版的,因为若是今天全部时间来写这个,后面就没法继续了

这个例子事实上也比较简单了,首先看我们的HTML结构

 1 <!doctype html>
 2 <html lang="en">
 3 <head>
 4   <meta charset="UTF-8">
 5   <title>ToDoList</title>
 6   <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7   <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/bootstrap/css/bootstrap.css">
 8   <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/css/flat-ui.css">
 9 </head>
10 <body>
11   <article class="container">
12   </article>
13   <script type="text/underscore-template" id="template-todolist">
14     <section class="row">
15       <div class="col-xs-9">
16         <form action="">
17           <legend>To Do List -- Input</legend>
18           <input type="text" placeholer="ToDoList" id="todoinput">
19           <button class="btn btn-primary" data-action="add">添加</button>
20         </form>
21         <ul id="todolist">
22         <%_.each(list, function(item){%>
23           <li><%=item.content %></li>
24         <%})%>
25         </ul>
26       </div>
27     </section>
28   </script>
29   <script type="text/javascript" src="../../vendor/underscore-min.js"></script>
30   <script type="text/javascript" src="../../vendor/zepto.min.js"></script>
31   <script src="../../src/underscore.extend.js" type="text/javascript"></script>
32   <script src="../../src/mvc.js" type="text/javascript"></script>
33   <script type="text/javascript" src="demo.js"></script>
34 </body>
35 </html>
View Code

其次是我们的js

 1 var htmltemplate = $(‘#template-todolist‘).html();
 2 
 3 var view = new Dalmatian.View({
 4   templateSet: {
 5     0:htmltemplate
 6   },
 7   statusSet: {
 8     STATUS_INIT: 0
 9   }
10 });
11 
12 var Adapter = _.inherit(Dalmatian.Adapter, {
13   parse: function (origindata) {
14     return origindata;
15   }
16 });
17 
18 var Controller = _.inherit(Dalmatian.ViewController, {
19   render: function() {
20     console.log(‘controller-render‘)
21     var data = http://www.mamicode.com/this.adapter.viewmodel;
22     this.view.render(this.viewstatus, data);
23   },
24 
25   events: {
26     ‘click button‘: ‘action‘
27   },
28 
29   action: function(e) {
30     e.preventDefault();
31 
32     var target = $(e.currentTarget).attr(‘data-action‘);
33     var strategy = {
34       ‘add‘: function(e) {
35         var value = http://www.mamicode.com/$(‘#todoinput‘).val();
36         this.adapter.datamodel.list.push({ content: value });
37         // this.adapter.parse(this.adapter.datamodel);
38         this.adapter.notifyDataChanged();
39       }
40     }
41 
42     strategy[target].apply(this, [e]);
43   }
44 })
45 
46 var controller = new Controller({
47   view: view,
48   adapter:  new Adapter(),
49   container: ‘.container‘,
50   onViewBeforeCreate: function () {
51     this.adapter.format({
52       list: []
53     });
54     this.adapter.registerObserver(this);
55     this.viewstatus = this.view.statusSet.STATUS_INIT
56   }
57 });
58 
59 controller.show();
View Code

阶段总结

MVC的学习暂时到这里,我们下面继续日历的的东西,虽然我与老大商量后形成了一些自己觉得不错的东西,但是真正使用过程中还是发现一些问题

① 第一个我认为比较大的问题是viewController中的代码,比如

var controller = new Controller({
  view: view,
  adapter:  new Adapter(),
  container: ‘.container‘,
  onViewBeforeCreate: function () {
    this.adapter.format({
      list: []
    });
    this.adapter.registerObserver(this);
    this.viewstatus = this.view.statusSet.STATUS_INIT
  }
});

以及

var controller = new Controller({
  view: view,
  adapter: adapter,
  container: ‘.container‘,
  onCancelBtnClick: function () {
    alert(‘cancel 2‘)
  }
});

事实上这些代码不应该存在于此,真实情况下我所构想的viewController不会在实例化时候还有如此多的业务相关信息,viewController在实例化时候只应该包含系统级的东西

比如Controller释放出来的接口,比如全局消息监听什么的,显然我们上面代码中的做法是有问题的,这些东西事实上应该在定义ViewController类时,在继承处得到处理

不应该在实例化时候处理,我们viewController实例化时候应该有更重要的使命,这些留待下面解决

上面要表达的意思是,事实上我们ViewController是最后继承下来是需要干业务的事情,所以他应该在几个事件点将要干的事情做完,比如TodoList应该是这样的

 1 var htmltemplate = $(‘#template-todolist‘).html();
 2 
 3 var Adapter = _.inherit(Dalmatian.Adapter, {
 4   parse: function (origindata) {
 5     return origindata;
 6   }
 7 });
 8 
 9 var Controller = _.inherit(Dalmatian.ViewController, {
10 
11   //设置默认信息
12   _initialize: function () {
13     this.view = new Dalmatian.View({
14       templateSet: {
15         0: htmltemplate
16       },
17       statusSet: {
18         STATUS_INIT: 0
19       }
20     });
21     this.adapter = new Adapter();
22 
23   },
24 
25   initialize: function ($super, opts) {
26     this._initialize();
27     $super(opts);
28   },
29 
30   render: function () {
31     console.log(‘controller-render‘)
32     var data = http://www.mamicode.com/this.adapter.viewmodel;
33     this.view.render(this.viewstatus, data);
34   },
35 
36 
37   container: ‘.container‘,
38   onViewBeforeCreate: function () {
39     this.adapter.format({
40       list: []
41     });
42     this.adapter.registerObserver(this);
43     this.viewstatus = this.view.statusSet.STATUS_INIT
44   },
45 
46   events: {
47     ‘click button‘: ‘action‘
48   },
49 
50   action: function (e) {
51     e.preventDefault();
52 
53     var target = $(e.currentTarget).attr(‘data-action‘);
54     var strategy = {
55       ‘add‘: function (e) {
56         var value = http://www.mamicode.com/$(‘#todoinput‘).val();
57         this.adapter.datamodel.list.push({ content: value });
58         // this.adapter.parse(this.adapter.datamodel);
59         this.adapter.notifyDataChanged();
60       }
61     }
62 
63     strategy[target].apply(this, [e]);
64   }
65 })
66 
67 var controller = new Controller();
68 
69 controller.show();
View Code

这样的话,业务应该的代码事实上写到了类的几个事件点中了,这些会在实例化时不同的状态被触发,所以根本不必在实例化时做任何操作

实例化时候应该有他的作用,因为继承到这一层的时候,该业务类便专注于处理这个业务了

简单日历

上次,我们的日历基本都成型了,今天我们便根据前面的想法为他做一次封装......

PS:想想有点很傻很天真的感觉......现在的问题是要将原来一个基本算总体的东西,分成三个部分,说实话这样封装的结构首先是让人阅读上稍微困难了

首先仍然是定义view的事情,首先一来就遇到个比较烦的地方,因为之前我们将模板分的很细:

① 星期显示模板

② 月模板

③ 日模板

所以,我们这里便不太好区分,而且还有一定嵌套关系,这里小钗费了一点功夫......

由这块的操作,我们甚至可以调整原来view的逻辑,优化由此一点一点慢慢就开始了......

  1 <!doctype html>
  2 <html lang="en">
  3 <head>
  4   <meta charset="UTF-8">
  5   <title>ToDoList</title>
  6   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7   <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/bootstrap/css/bootstrap.css">
  8   <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/css/flat-ui.css">
  9   <link href="../style/main.css" rel="stylesheet" type="text/css" />
 10   <style type="text/css">
 11     .cui-alert { width: auto; position: static; }
 12     .txt { border: #cfcfcf 1px solid; margin: 10px 0; width: 80%; }
 13     ul, li { padding: 0; margin: 0; }
 14     .cui_calendar, .cui_week { list-style: none; }
 15     .cui_calendar li, .cui_week li { float: left; width: 14%; overflow: hidden; padding: 4px 0; text-align: center; }
 16   </style>
 17 </head>
 18 <body>
 19   <article class="container">
 20   </article>
 21   <script type="text/template" id="template-calendar">
 22     <ul class="cui_week">
 23       <% var i = 0, day = 0; %>
 24       <%for(day = 0; day < 7; day++) { %>
 25       <li>
 26         <%=weekDayItemTmpt[day] %></li>
 27       <%} %>
 28     </ul>
 29 
 30     <ul class="cui_calendar">
 31       <% for(i = 0; i < beginWeek; i++) { %>
 32         <li class="cui_invalid"></li>
 33       <% } %>
 34       <% for(i = 0; i < days; i++) { %>
 35         <% day = i + 1; %>
 36           <li class="cui_calendar_item" data-date="<%=year%>-<%=month + 1%>-<%=day%>"><%=day %></li>
 37       <% } %>
 38     </ul>
 39   </script>
 40   <script type="text/javascript" src="../../vendor/underscore-min.js"></script>
 41   <script type="text/javascript" src="../../vendor/zepto.min.js"></script>
 42   <script src="../../src/underscore.extend.js" type="text/javascript"></script>
 43   <script src="../../src/util.js" type="text/javascript"></script>
 44   <script src="../../src/mvc.js" type="text/javascript"></script>
 45   <script type="text/javascript">
 46     var tmpt = $(#template-calendar).html();
 47 
 48     var CalendarView = _.inherit(Dalmatian.View, {
 49       templateSet: {
 50         0: tmpt
 51       },
 52 
 53       statusSet: {
 54         STATUS_INIT: 0
 55       }
 56     });
 57 
 58     var CalendarAdapter = _.inherit(Dalmatian.Adapter, {
 59       _initialize: function ($super) {
 60         $super();
 61 
 62         //默认显示方案,可以根据参数修改
 63         //任意一个model发生改变皆会引起update
 64         this.weekDayItemTmpt = [, , , , , , ];
 65       },
 66 
 67       //该次重新,viewmodel的数据完全来源与parse中多定义
 68       parse: function (data) {
 69         return _.extend({
 70           weekDayItemTmpt: this.weekDayItemTmpt
 71         }, data);
 72       }
 73     });
 74 
 75     var CalendarController = _.inherit(Dalmatian.ViewController, {
 76 
 77       _initialize: function () {
 78         this.view = new CalendarView();
 79         this.adapter = new CalendarAdapter();
 80 
 81         //默认业务数据
 82         this.dateObj = new Date();
 83         this.container = .container;
 84 
 85         var s = ‘‘;
 86       },
 87 
 88       initialize: function ($super, opts) {
 89         this._initialize();
 90         $super(opts);
 91       },
 92 
 93       onViewBeforeCreate: function () {
 94 
 95         //使用adpter之前必须注册监听以及格式化viewModel,此操作应该封装起来
 96         this.adapter.registerObserver(this);
 97         this.adapter.format(this._getMonthData(this.dateObj.getFullYear(), this.dateObj.getMonth()));
 98 
 99         //view显示之前必定会给予状态,此应该封装
100         this.viewstatus = this.view.statusSet.STATUS_INIT;
101 
102         var s = ‘‘;
103       },
104 
105       render: function () {
106         //该操作可封装
107         var data = this.adapter.viewmodel;
108         this.view.render(this.viewstatus, data);
109       },
110 
111       //根据传入年月,返回该月相关数据
112       _getMonthData: function (year, month) {
113         this.date = new Date(year, month);
114         var d = new Date(year, month);
115         //description 获取天数
116         var days = dateUtil.getDaysOfMonth(d);
117         //description 获取那个月第一天时星期几
118         var _beginWeek = dateUtil.getBeginDayOfMouth(d);
119         return {
120           year: d.getFullYear(),
121           month: d.getMonth(),
122           beginWeek: _beginWeek,
123           days: days
124         };
125       }
126     });
127 
128     var calendar = new CalendarController();
129     calendar.show();
130   
131   </script>
132 </body>
133 </html>
View Code

首次调整后,大概的东西出来了,这样一次操作后就会发现之前定义的MVC一些不合理的地方

① 操作Adapter有parse与format两个地方,我们用着用着就会分不清,应该只对外暴露一个借口

② Controller处操作Adapter以及view也会有多个地方事实上有一些必定会发生的流程我们应该封装起来,类似:

//使用adpter之前必须注册监听以及格式化viewModel,此操作应该封装起来
this.adapter.registerObserver(this);
this.adapter.format(this._getMonthData(this.dateObj.getFullYear(), this.dateObj.getMonth()));

//view显示之前必定会给予状态,此应该封装
this.viewstatus = this.view.statusSet.STATUS_INIT;

③ 整个MVC的逻辑还是有一些不太清晰的地方,这个留待后续调整

这个时候我们将之前的一些借口加入进来,比如我们的handleDay

handleDay: function (dateStr, fn) {
  if (dateUtil.isDate(dateStr)) dateStr = dateUtil.format(dateStr, ‘Y-m-d‘);
  var el = this.viewcontent.find(‘[data-date="‘ + dateStr + ‘"]‘);

  if (typeof fn == ‘function‘) fn(el, dateUtil.parse(dateStr, ‘y-m-d‘), this);

}
var calendar = new CalendarController();
calendar.show();

calendar.handleDay(new Date(), function (el, date, calendar) {
  el.html(‘今天‘);
});

现在如果有事件绑定的话,便注册至viewController即可,我这里便暂时结束了

结语

通过今天的学习,我将与我老大研究出来的MVC的东东搞了出来,事实证明还是需要有一些优化的......

今天状态不是太好,今天暂时到此,剩下的我们后面点来,这块还有很多东西要清理呢。。。。。。