前言
之前我们为view引入了wrapperSet的概念,想以此解决view局部刷新问题,后来发现这个方案不太合理
view里面插入了业务相关的代码,事实上这个是应该剥离出去,业务的需求千奇百怪,我们不应该去处理
view现在只提供最基础的功能:
① 定义各个状态的模板
② 渲染模板
整个view的逻辑便该结束了,有一个比较特殊的情况是,当状态值不变的情况就应该是更新,这个可能会有不一样的逻辑也应该划出去
Adapter的意义在于存储view渲染过程中需要的data数据,从组成上分为
① datamodel
② viewmodel
datamodel用于具体操作,viewmodel被干掉了,提供一个getViewModel的方法替换,并且对外提供一个format方法用于用户继承
format的参数便是datamodel,这里通过处理返回的数据便是我们所谓的viewModel,他将会用于view生成对应html
然后datamodel的改变会引起对应view的变化,这个变化发起端与控制端皆在viewController,最后viewController会通知到view重新渲染
Controller依旧是交互的核心,他是连接Adapter以及view的桥梁
view与Adapter本身并没有关联,Controller将之联系到了一起:
① 在实例化时候便会关联一个Adapter以及view的实例,这里Adapter不是必须的
② viewController会保留一个view的根节点,view的根节点只会存在一个
③ viewController会在Adapter实例上监听自身,在adpter
datamodel发生变化时候通知到自己,便会触发update事件
④ 传入初始状态的status以及Adapter的datamodel,调用view的render方法,会生成当前状态的view的html
⑤ 将之装入view的根节点,并且为viewController的this.$el赋值,create的逻辑结束
⑥ 触发viewController show事件,将events绑定到根节点,将$el
append到container容器中并显示,初步逻辑结束
⑦ viewController有几个事件点用于用户注册,本身也具有很多一系列dom事件,可能导致datamodel的变化
⑧
若是Adapter的datamodel发生变化便会触发dataAdpter改变的notify事件,这个时候viewController便会有所反应
⑨ datamodel的改变会触发viewController的update事件,默认会再次触发render事件重新新渲染结构
由于render会放给view自定义,所以其中需要执行的逻辑便不需要我们的关注了
实例
这个便是一个标准的MVC模型,借IOS MVC的模型来说
核心操控点在于Controller,Controller会组织model以及view
由于Controller上注册的各系列事件,会引起model的变化,
每次model产生变化后会重新通知,Controller 通知view update操作
这个时候view会获取viewController的状态值以及model的数据渲染出新的view
view只负责html渲染
model只负责操作数据,并且通知观察者改变事件
Controller将view以及model联系起来,所有的事件全部注册至Controller
PS:传统的View会包含事件交互,这里放到了Controller上
模型会把datamodel的改变通知到控制器,控制器会更新视图信息,控制器根据view组成dom结构,并且注册各种UI事件,又会触发datamodel的各种改变
这就达到了理想情况的view与model的分离,一个model(adpter可用于多个viewController),一个dataAdpter的改变会影响两个视图的改变
这个MVC可以完全解耦view以及model,view的变化相当频繁,若是model控制view渲染便会降低model的重用
这里首先举一个例子做简单说明:
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 id="container">
20 </article>
21 <script type="text/underscore-template" id="template-ajax-init">
22 <div class="cui-alert" >
23 <div class="cui-pop-box">
24 <div class="cui-hd">
25 <%=title%>
26 </div>
27 <div class="cui-bd">
28 <div class="cui-error-tips">
29 </div>
30 <div class="cui-roller-btns" style="padding: 4px; "><input type="text" placeholder="设置最低价 {day: ‘‘, price: ‘‘}" style="margin: 2px; width: 100%; " id="ajax_data" class="txt" value="{day: , price: }"></div>
31 <div class="cui-roller-btns">
32 <div class="cui-flexbd cui-btns-sure"><%=confirm%></div>
33 </div>
34 </div>
35 </div>
36 </div>
37 </script>
38 <script type="text/underscore-template" id="template-ajax-suc">
39 <ul>
40 <li>最低价:本月<%=ajaxData.day %>号,价格:<%=ajaxData.price %> 元</li>
41 </ul>
42 </script>
43
44 <script type="text/underscore-template" id="template-ajax-loading">
45 <span>loading....</span>
46 </script>
47
48 <script src="../../vendor/underscore-min.js" type="text/javascript"></script>
49 <script src="../../vendor/zepto.min.js" type="text/javascript"></script>
50 <script src="../../src/underscore-extend.js" type="text/javascript"></script>
51 <script src="../../src/util.js" type="text/javascript"></script>
52 <script src="../../src/mvc.js" type="text/javascript"></script>
53 <script type="text/javascript">
54
55 //模拟Ajax请求
56 function getAjaxData(callback, data) {
57 setTimeout(function () {
58 if (!data) {
59 data = {day: 3, price: 20};
60 }
61 callback(data);
62 }, 1000);
63 }
64
65 var AjaxView = _.inherit(Dalmatian.View, {
66 _initialize: function ($super) {
67 //设置默认属性
68 $super();
69
70 this.templateSet = {
71 init: $(‘#template-ajax-init‘).html(),
72 loading: $(‘#template-ajax-loading‘).html(),
73 ajaxSuc: $(‘#template-ajax-suc‘).html()
74 };
75
76 }
77 });
78
79 var AjaxAdapter = _.inherit(Dalmatian.Adapter, {
80 _initialize: function ($super) {
81 $super();
82 this.datamodel = {
83 title: ‘标题‘,
84 confirm: ‘刷新数据‘
85 };
86 this.datamodel.ajaxData = {};
87 },
88
89 format: function (datamodel) {
90 //处理datamodel生成viewModel的逻辑
91 return datamodel;
92 },
93
94 ajaxLoading: function () {
95 this.notifyDataChanged();
96 },
97
98 ajaxSuc: function (data) {
99 this.datamodel.ajaxData = data;
100 this.notifyDataChanged();
101 }
102 });
103
104 var AjaxViewController = _.inherit(Dalmatian.ViewController, {
105 _initialize: function ($super) {
106 $super();
107 //设置基本的属性
108 this.view = new AjaxView();
109 this.adapter = new AjaxAdapter();
110 this.viewstatus = ‘init‘;
111 this.container = ‘#container‘;
112 },
113
114 //处理datamodel变化引起的dom改变
115 render: function (data) {
116 //这里用户明确知道自己有没有viewdata
117 var viewdata = this.adapter.getViewModel();
118 var wrapperSet = {
119 loading: ‘.cui-error-tips‘,
120 ajaxSuc: ‘.cui-error-tips‘
121 };
122 //view具有唯一包裹器
123 var root = this.view.root;
124 var selector = wrapperSet[this.viewstatus];
125
126 if (selector) {
127 root = root.find(selector);
128 }
129
130 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel());
131
132 root.html(this.view.html);
133
134 },
135
136 //显示后Ajax请求数据
137 onViewAfterShow: function () {
138 this._handleAjax();
139 },
140
141 _handleAjax: function (data) {
142 this.setViewStatus(‘loading‘);
143 this.adapter.ajaxLoading();
144 getAjaxData($.proxy(function (data) {
145 this.setViewStatus(‘ajaxSuc‘);
146 this.adapter.ajaxSuc(data);
147 }, this), data);
148 },
149
150 events: {
151 ‘click .cui-btns-sure‘: function () {
152 var data = this.$el.find(‘#ajax_data‘).val();
153 data = eval(‘(‘ + data + ‘)‘);
154 this._handleAjax(data);
155 }
156 }
157 });
158
159 var a = new AjaxViewController();
160 a.show();
161
162 </script>
163 </body>
164 </html>
完成HTML
1 (function () {
2 var _ = window._;
3 if (typeof require === ‘function‘ && !_) {
4 _ = require(‘underscore‘);
5 };
6
7 // @description 全局可能用到的变量
8 var arr = [];
9 var slice = arr.slice;
10
11 var method = method || {};
12
13
14 /**
15 * @description inherit方法,js的继承,默认为两个参数
16 * @param {function} supClass 可选,要继承的类
17 * @param {object} subProperty 被创建类的成员
18 * @return {function} 被创建的类
19 */
20 method.inherit = function () {
21
22 // @description 参数检测,该继承方法,只支持一个参数创建类,或者两个参数继承类
23 if (arguments.length === 0 || arguments.length > 2) throw ‘参数错误‘;
24
25 var parent = null;
26
27 // @description 将参数转换为数组
28 var properties = slice.call(arguments);
29
30 // @description 如果第一个参数为类(function),那么就将之取出
31 if (typeof properties[0] === ‘function‘)
32 parent = properties.shift();
33 properties = properties[0];
34
35 // @description 创建新类用于返回
36 function klass() {
37 if (_.isFunction(this.initialize))
38 this.initialize.apply(this, arguments);
39 }
40
41 klass.superclass = parent;
42 // parent.subclasses = [];
43
44 if (parent) {
45 // @description 中间过渡类,防止parent的构造函数被执行
46 var subclass = function () { };
47 subclass.prototype = parent.prototype;
48 klass.prototype = new subclass();
49 // parent.subclasses.push(klass);
50 }
51
52 var ancestor = klass.superclass && klass.superclass.prototype;
53 for (var k in properties) {
54 var value = properties[k];
55
56 //满足条件就重写
57 if (ancestor && typeof value == ‘function‘) {
58 var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, ‘‘).split(‘,‘);
59 //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
60 if (argslist[0] === ‘$super‘ && ancestor[k]) {
61 value = (function (methodName, fn) {
62 return function () {
63 var scope = this;
64 var args = [function () {
65 return ancestor[methodName].apply(scope, arguments);
66 } ];
67 return fn.apply(this, args.concat(slice.call(arguments)));
68 };
69 })(k, value);
70 }
71 }
72
73 //此处对对象进行扩展,当前原型链已经存在该对象,便进行扩展
74 if (_.isObject(klass.prototype[k]) && _.isObject(value) && (typeof klass.prototype[k] != ‘function‘ && typeof value != ‘fuction‘)) {
75 //原型链是共享的,这里不好办
76 var temp = {};
77 _.extend(temp, klass.prototype[k]);
78 _.extend(temp, value);
79 klass.prototype[k] = temp;
80 } else {
81 klass.prototype[k] = value;
82 }
83
84 }
85
86 if (!klass.prototype.initialize)
87 klass.prototype.initialize = function () { };
88
89 klass.prototype.constructor = klass;
90
91 return klass;
92 };
93
94 // @description 返回需要的函数
95 method.getNeedFn = function (key, scope) {
96 scope = scope || window;
97 if (_.isFunction(key)) return key;
98 if (_.isFunction(scope[key])) return scope[key];
99 return function () { };
100 };
101
102 method.callmethod = function (method, scope, params) {
103 scope = scope || this;
104 if (_.isFunction(method)) {
105 return _.isArray(params) ? method.apply(scope, params) : method.call(scope, params);
106 }
107
108 return false;
109 };
110
111 //获取url参数
112 method.getUrlParam = function (url, name) {
113 var i, arrQuery, _tmp, query = {};
114 var index = url.lastIndexOf(‘//‘);
115 var http = url.substr(index, url.length);
116
117 url = url.substr(0, index);
118 arrQuery = url.split(‘&‘);
119
120 for (i = 0, len = arrQuery.length; i < len; i++) {
121 _tmp = arrQuery[i].split(‘=‘);
122 if (i == len - 1) _tmp[1] += http;
123 query[_tmp[0]] = _tmp[1];
124 }
125
126 return name ? query[name] : query;
127 };
128
129
130 /**
131 * @description 在fn方法的前后通过键值设置两个传入的回调
132 * @param fn {function} 调用的方法
133 * @param beforeFnKey {string} 从context对象中获得的函数指针的键值,该函数在fn前执行
134 * @param afterFnKey {string} 从context对象中获得的函数指针的键值,该函数在fn后执行
135 * @param context {object} 执行环节的上下文
136 * @return {function}
137 */
138 method.wrapmethod = method.insert = function (fn, beforeFnKey, afterFnKey, context) {
139
140 var scope = context || this;
141 var action = _.wrap(fn, function (func) {
142
143 _.callmethod(_.getNeedFn(beforeFnKey, scope), scope);
144
145 func.call(scope);
146
147 _.callmethod(_.getNeedFn(afterFnKey, scope), scope);
148 });
149
150 return _.callmethod(action, scope);
151 };
152
153 method.Hash = method.inherit({
154 inisilize: function (opts) {
155 this.keys = [];
156 this.values = [];
157 },
158
159 length: function () {
160 return this.keys.length;
161 },
162
163 //传入order,若是数组中存在的话会将之放到最后,保证数组的唯一性,因为这个是hash,不能存在重复的键
164 push: function (key, value, order) {
165 if (_.isObject(key)) {
166 for (var i in key) {
167 if (key.hasOwnProperty(i)) this.push(i, key[i], order);
168 }
169 return;
170 }
171
172 var index = _.indexOf(key, this.keys);
173
174 if (index != -1 && !order) {
175 this.values[index] = value;
176 } else {
177 if (order) this.remove(key);
178 this.keys.push(key);
179 this.vaules.push(value);
180 }
181
182 },
183
184 remove: function (key) {
185 return this.removeByIndex(_.indexOf(key, this.keys));
186 },
187
188 removeByIndex: function (index) {
189 if (index == -1) return this;
190
191 this.keys.splice(index, 1);
192 this.values.splice(index, 1);
193
194 return this;
195 },
196
197 pop: function () {
198 if (!this.length()) return;
199
200 this.keys.pop();
201 return this.values.pop();
202 },
203
204 //根据索引返回对应键值
205 indexOf: function (value) {
206 var index = _.indexOf(value, this.vaules);
207 if (index != -1) return this.keys[index];
208 return -1;
209 },
210
211 //移出栈底值
212 shift: function () {
213 if (!this.length()) return;
214
215 this.keys.shift();
216 return this.values.shift();
217 },
218
219 //往栈顶压入值
220 unShift: function (key, vaule, order) {
221 if (_.isObject(key)) {
222 for (var i in key) {
223 if (key.hasOwnProperty(i)) this.unShift(i, key[i], order);
224 }
225 return;
226 }
227 if (order) this.remove(key);
228 this.keys.unshift(key);
229 this.vaules.unshift(value);
230 },
231
232 //返回hash表的一段数据
233 //
234 slice: function (start, end) {
235 var keys = this.keys.slice(start, end || null);
236 var values = this.values.slice(start, end || null);
237 var hash = new _.Hash();
238
239 for (var i = 0; i < keys.length; i++) {
240 hash.push(keys[i], values[i]);
241 }
242
243 return obj;
244 },
245
246 //由start开始,移除元素
247 splice: function (start, count) {
248 var keys = this.keys.splice(start, end || null);
249 var values = this.values.splice(start, end || null);
250 var hash = new _.Hash();
251
252 for (var i = 0; i < keys.length; i++) {
253 hash.push(keys[i], values[i]);
254 }
255
256 return obj;
257 },
258
259 exist: function (key, value) {
260 var b = true;
261
262 if (_.indexOf(key, this.keys) == -1) b = false;
263
264 if (!_.isUndefined(value) && _.indexOf(value, this.values) == -1) b = false;
265
266 return b;
267 },
268
269
270 filter: function () {
271
272 }
273
274 });
275
276 _.extend(_, method);
277
278
279 // if (module && module.exports)
280 // module.exports = _;
281
282 }).call(this);
underscore-extend
1 /**
2 * @description 静态日期操作类,封装系列日期操作方法
3 * @description 输入时候月份自动减一,输出时候自动加一
4 * @return {object} 返回操作方法
5 */
6 var dateUtil = {
7 /**
8 * @description 数字操作,
9 * @return {string} 返回处理后的数字
10 */
11 formatNum: function (n) {
12 if (n < 10) return ‘0‘ + n;
13 return n;
14 },
15 /**
16 * @description 将字符串转换为日期,支持格式y-m-d ymd (y m r)以及标准的
17 * @return {Date} 返回日期对象
18 */
19 parse: function (dateStr, formatStr) {
20 if (typeof dateStr === ‘undefined‘) return null;
21 if (typeof formatStr === ‘string‘) {
22 var _d = new Date(formatStr);
23 //首先取得顺序相关字符串
24 var arrStr = formatStr.replace(/[^ymd]/g, ‘‘).split(‘‘);
25 if (!arrStr && arrStr.length != 3) return null;
26
27 var formatStr = formatStr.replace(/y|m|d/g, function (k) {
28 switch (k) {
29 case ‘y‘: return ‘(\\d{4})‘;
30 case ‘m‘: ;
31 case ‘d‘: return ‘(\\d{1,2})‘;
32 }
33 });
34
35 var reg = new RegExp(formatStr, ‘g‘);
36 var arr = reg.exec(dateStr)
37
38 var dateObj = {};
39 for (var i = 0, len = arrStr.length; i < len; i++) {
40 dateObj[arrStr[i]] = arr[i + 1];
41 }
42 return new Date(dateObj[‘y‘], dateObj[‘m‘] - 1, dateObj[‘d‘]);
43 }
44 return null;
45 },
46 /**
47 * @description将日期格式化为字符串
48 * @return {string} 常用格式化字符串
49 */
50 format: function (date, format) {
51 if (arguments.length < 2 && !date.getTime) {
52 format = date;
53 date = new Date();
54 }
55 typeof format != ‘string‘ && (format = ‘Y年M月D日 H时F分S秒‘);
56 return format.replace(/Y|y|M|m|D|d|H|h|F|f|S|s/g, function (a) {
57 switch (a) {
58 case "y": return (date.getFullYear() + "").slice(2);
59 case "Y": return date.getFullYear();
60 case "m": return date.getMonth() + 1;
61 case "M": return dateUtil.formatNum(date.getMonth() + 1);
62 case "d": return date.getDate();
63 case "D": return dateUtil.formatNum(date.getDate());
64 case "h": return date.getHours();
65 case "H": return dateUtil.formatNum(date.getHours());
66 case "f": return date.getMinutes();
67 case "F": return dateUtil.formatNum(date.getMinutes());
68 case "s": return date.getSeconds();
69 case "S": return dateUtil.formatNum(date.getSeconds());
70 }
71 });
72 },
73 // @description 是否为为日期对象,该方法可能有坑,使用需要慎重
74 // @param year {num} 日期对象
75 // @return {boolean} 返回值
76 isDate: function (d) {
77 if ((typeof d == ‘object‘) && (d instanceof Date)) return true;
78 return false;
79 },
80 // @description 是否为闰年
81 // @param year {num} 可能是年份或者为一个date时间
82 // @return {boolean} 返回值
83 isLeapYear: function (year) {
84 //传入为时间格式需要处理
85 if (dateUtil.isDate(year)) year = year.getFullYear()
86 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true;
87 else return false;
88 },
89
90 // @description 获取一个月份的天数
91 // @param year {num} 可能是年份或者为一个date时间
92 // @param year {num} 月份
93 // @return {num} 返回天数
94 getDaysOfMonth: function (year, month) {
95 //自动减一以便操作
96 month--;
97 if (dateUtil.isDate(year)) {
98 month = year.getMonth(); //注意此处月份要加1,所以我们要减一
99 year = year.getFullYear();
100 }
101 return [31, dateUtil.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
102 },
103
104 // @description 获取一个月份1号是星期几,注意此时的月份传入时需要自主减一
105 // @param year {num} 可能是年份或者为一个date时间
106 // @param year {num} 月份
107 // @return {num} 当月一号为星期几0-6
108 getBeginDayOfMouth: function (year, month) {
109 //自动减一以便操作
110 month--;
111 if ((typeof year == ‘object‘) && (year instanceof Date)) {
112 month = year.getMonth();
113 year = year.getFullYear();
114 }
115 var d = new Date(year, month, 1);
116 return d.getDay();
117 }
118 };
Util
1 "use strict";
2
3 // ------------------华丽的分割线--------------------- //
4
5 // @description 正式的声明Dalmatian框架的命名空间
6 var Dalmatian = Dalmatian || {};
7
8 // @description 定义默认的template方法来自于underscore
9 Dalmatian.template = _.template;
10 Dalmatian.View = _.inherit({
11 // @description 构造函数入口
12 initialize: function (options) {
13 this._initialize();
14 this.handleOptions(options);
15 this._initRoot();
16
17 },
18
19 _initRoot: function () {
20 //根据html生成的dom包装对象
21 //有一种场景是用户的view本身就是一个只有一个包裹器的结构,他不想要多余的包裹器
22 this.root = $(this.defaultContainerTemplate);
23 this.root.attr(‘id‘, this.viewid);
24 },
25
26 // @description 设置默认属性
27 _initialize: function () {
28
29 var DEFAULT_CONTAINER_TEMPLATE = ‘<section class="view" id="<%=viewid%>"><%=html%></section>‘;
30
31 // @description view状态机
32 // this.statusSet = {};
33
34 this.defaultContainerTemplate = DEFAULT_CONTAINER_TEMPLATE;
35
36 // @override
37 // @description template集合,根据status做template的map
38 // @example
39 // { 0: ‘<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>‘ }
40 // this.templateSet = {};
41
42 this.viewid = _.uniqueId(‘dalmatian-view-‘);
43
44 },
45
46 // @description 操作构造函数传入操作
47 handleOptions: function (options) {
48 // @description 从形参中获取key和value绑定在this上
49 if (_.isObject(options)) _.extend(this, options);
50
51 },
52
53 // @description 通过模板和数据渲染具体的View
54 // @param status {enum} View的状态参数
55 // @param data {object} 匹配View的数据格式的具体数据
56 // @param callback {functiion} 执行完成之后的回调
57 render: function (status, data, callback) {
58
59 var templateSelected = this.templateSet[status];
60 if (templateSelected) {
61
62 // @description 渲染view
63 var templateFn = Dalmatian.template(templateSelected);
64 this.html = templateFn(data);
65
66 //这里减少一次js编译
67 // this.root.html(‘‘);
68 // this.root.append(this.html);
69
70 this.currentStatus = status;
71
72 _.callmethod(callback, this);
73
74 return this.html;
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, data);
93 }
94 });
95
96 Dalmatian.Adapter = _.inherit({
97
98 // @description 构造函数入口
99 initialize: function (options) {
100 this._initialize();
101 this.handleOptions(options);
102
103 },
104
105 // @description 设置默认属性
106 _initialize: function () {
107 this.observers = [];
108 // this.viewmodel = {};
109 this.datamodel = {};
110 },
111
112 // @description 操作构造函数传入操作
113 handleOptions: function (options) {
114 // @description 从形参中获取key和value绑定在this上
115 if (_.isObject(options)) _.extend(this, options);
116 },
117
118 // @override
119 // @description 设置
120 format: function (datamodel) {
121 return datamodel;
122 },
123
124 getViewModel: function () {
125 return this.format(this.datamodel);
126 },
127
128 registerObserver: function (viewcontroller) {
129 // @description 检查队列中如果没有viewcontroller,从队列尾部推入
130 if (!_.contains(this.observers, viewcontroller)) {
131 this.observers.push(viewcontroller);
132 }
133 },
134
135 unregisterObserver: function (viewcontroller) {
136 // @description 从observers的队列中剔除viewcontroller
137 this.observers = _.without(this.observers, viewcontroller);
138 },
139
140 //统一设置所有观察者的状态,因为对应观察者也许根本不具备相关状态,所以这里需要处理
141 // setStatus: function (status) {
142 // _.each(this.observers, function (viewcontroller) {
143 // if (_.isObject(viewcontroller))
144 // viewcontroller.setViewStatus(status);
145 // });
146 // },
147
148 notifyDataChanged: function () {
149 // @description 通知所有注册的观察者被观察者的数据发生变化
150 var data = this.getViewModel();
151 _.each(this.observers, function (viewcontroller) {
152 if (_.isObject(viewcontroller))
153 _.callmethod(viewcontroller.update, viewcontroller, [data]);
154 });
155 }
156 });
157
158 Dalmatian.ViewController = _.inherit({
159
160 _initialize: function () {
161
162 //用户设置的容器选择器,或者dom结构
163 this.container;
164 //根元素
165 this.$el;
166
167 //一定会出现
168 this.view;
169 //可能会出现
170 this.adapter;
171 //初始化的时候便需要设置view的状态,否则会渲染失败,这里给一个默认值
172 this.viewstatus = ‘init‘;
173
174 },
175
176 // @description 构造函数入口
177 initialize: function (options) {
178 this._initialize();
179 this.handleOptions(options);
180 this._handleAdapter();
181 this.create();
182 },
183
184 //处理dataAdpter中的datamodel,为其注入view的默认容器数据
185 _handleAdapter: function () {
186 //不存在就不予理睬
187 if (!this.adapter) return;
188 this.adapter.registerObserver(this);
189 },
190
191 // @description 操作构造函数传入操作
192 handleOptions: function (options) {
193 if (!options) return;
194
195 this._verify(options);
196
197 // @description 从形参中获取key和value绑定在this上
198 if (_.isObject(options)) _.extend(this, options);
199 },
200
201 setViewStatus: function (status) {
202 this.viewstatus = status;
203 },
204
205 // @description 验证参数
206 _verify: function (options) {
207 //这个underscore方法新框架在报错
208 // if (!_.property(‘view‘)(options) && (!this.view)) throw Error(‘view必须在实例化的时候传入ViewController‘);
209 if (options.view && (!this.view)) throw Error(‘view必须在实例化的时候传入ViewController‘);
210 },
211
212 // @description 当数据发生变化时调用onViewUpdate,如果onViewUpdate方法不存在的话,直接调用render方法重绘
213 update: function (data) {
214
215 // _.callmethod(this.hide, this);
216
217 if (this.onViewUpdate) {
218 _.callmethod(this.onViewUpdate, this, [data]);
219 return;
220 }
221 this.render();
222
223 // _.callmethod(this.show, this);
224 },
225
226 /**
227 * @override
228 */
229 render: function () {
230 // @notation 这个方法需要被复写
231 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel());
232 this.view.root.html(this.view.html);
233 },
234
235 _create: function () {
236 this.render();
237
238 //render 结束后构建好根元素dom结构
239 this.view.root.html(this.view.html);
240 this.$el = this.view.root;
241 },
242
243 create: function () {
244
245 //l_wang这块不是很明白
246 //是否检查映射关系,不存在则recreate,但是在这里dom结构未必在document上
247 // if (!$(‘#‘ + this.view.viewid)[0]) {
248 // return _.callmethod(this.recreate, this);
249 // }
250
251 // @notation 在create方法调用前后设置onViewBeforeCreate和onViewAfterCreate两个回调
252 _.wrapmethod(this._create, ‘onViewBeforeCreate‘, ‘onViewAfterCreate‘, this);
253
254 },
255
256 /**
257 * @description 如果进入create判断是否需要update一下页面,sync view和viewcontroller的数据
258 */
259 _recreate: function () {
260 this.update();
261 },
262
263 recreate: function () {
264 _.wrapmethod(this._recreate, ‘onViewBeforeRecreate‘, ‘onViewAfterRecreate‘, this);
265 },
266
267 //事件注册点
268 bindEvents: function (events) {
269 if (!(events || (events = _.result(this, ‘events‘)))) return this;
270 this.unBindEvents();
271
272 // @description 解析event参数的正则
273 var delegateEventSplitter = /^(\S+)\s*(.*)$/;
274 var key, method, match, eventName, selector;
275
276 //注意,此处做简单的字符串数据解析即可,不做实际业务
277 for (key in events) {
278 method = events[key];
279 if (!_.isFunction(method)) method = this[events[key]];
280 if (!method) continue;
281
282 match = key.match(delegateEventSplitter);
283 eventName = match[1], selector = match[2];
284 method = _.bind(method, this);
285 eventName += ‘.delegateEvents‘ + this.view.viewid;
286
287 if (selector === ‘‘) {
288 this.$el.on(eventName, method);
289 } else {
290 this.$el.on(eventName, selector, method);
291 }
292 }
293
294 return this;
295 },
296
297 //取消所有事件
298 unBindEvents: function () {
299 this.$el.off(‘.delegateEvents‘ + this.view.viewid);
300 return this;
301 },
302
303 _show: function () {
304 this.bindEvents();
305 $(this.container).append(this.$el);
306 this.$el.show();
307 },
308
309 show: function () {
310 _.wrapmethod(this._show, ‘onViewBeforeShow‘, ‘onViewAfterShow‘, this);
311 },
312
313 _hide: function () {
314 this.forze();
315 this.$el.hide();
316 },
317
318 hide: function () {
319 _.wrapmethod(this._hide, ‘onViewBeforeHide‘, ‘onViewAfterHide‘, this);
320 },
321
322 _forze: function () {
323 this.unBindEvents();
324 },
325
326 forze: function () {
327 _.wrapmethod(this._forze, ‘onViewBeforeForzen‘, ‘onViewAfterForzen‘, this);
328 },
329
330 _destory: function () {
331 this.unBindEvents();
332 this.$el.remove();
333 // delete this;
334 },
335
336 destory: function () {
337 _.wrapmethod(this._destory, ‘onViewBeforeDestory‘, ‘onViewAfterDestory‘, this);
338 }
339 });
完整MVC想法
代码效果如下:
每次点击刷新数据时候会模拟一次Ajax操作,将datamodel中的数据改变,然后会触发视图改变
1 events: {
2 ‘click .cui-btns-sure‘: function () {
3 var data = this.$el.find(‘#ajax_data‘).val();
4 data = eval(‘(‘ + data + ‘)‘);
5 this._handleAjax(data);
6 }
7 }
1 _handleAjax: function (data) {
2 this.setViewStatus(‘loading‘);
3 this.adapter.ajaxLoading();
4 getAjaxData($.proxy(function (data) {
5 this.setViewStatus(‘ajaxSuc‘);
6 this.adapter.ajaxSuc(data);
7 }, this), data);
8 },
ajaxSuc: function (data) {
this.datamodel.ajaxData = data;
this.notifyDataChanged();
}
中间又一次状态变化,将视图变为loading状态,一次数据请求成功,我们要做的是,重写viewController的render方法
1 render: function (data) {
2 //这里用户明确知道自己有没有viewdata
3 //var viewdata = this.adapter.getViewModel();
4 var wrapperSet = {
5 loading: ‘.cui-error-tips‘,
6 ajaxSuc: ‘.cui-error-tips‘
7 };
8 //view具有唯一包裹器
9 var root = this.view.root;
10 var selector = wrapperSet[this.viewstatus];
11
12 if (selector) {
13 root = root.find(selector);
14 }
15
16 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel());
17
18 root.html(this.view.html);
19
20 },
这块逻辑需要被用户重写,因为具体每次渲染后,形成的html装载在什么位置,我们并不能确定
这里我们再写一个例子,看一看共享一个Adapter的效果
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
14 <div style=" border: 1px solid black; margin: 10px; padding: 10px; ">上下组件共享Adapter</div>
15
16 <article class="list">
17 </article>
18
19 <script type="text/underscore-template" id="template-todolist">
20 <section class="row">
21 <div class="col-xs-9">
22 <form action="">
23 <legend>To Do List -- Input</legend>
24 <input type="text" placeholer="ToDoList" id="todoinput">
25 <button class="btn btn-primary" data-action="add">添加</button>
26 </form>
27 <ul id="todolist">
28 <%_.each(list, function(item){%>
29 <li><%=item.content %></li>
30 <%})%>
31 </ul>
32 </div>
33 </section>
34 </script>
35
36 <script type="text/underscore-template" id="template-list">
37 <ul>
38 <%for(var i =0, len = list.length; i < len; i++) {%>
39 <li><%=list[i].content %> - <span index="<%=i %>">删除</span></li>
40 <%}%>
41 </ul>
42 </script>
43
44 <script type="text/javascript" src="../../vendor/underscore.js"></script>
45 <script type="text/javascript" src="../../vendor/zepto.js"></script>
46 <script src="../../src/underscore-extend.js" type="text/javascript"></script>
47 <script src="../../src/mvc.js" type="text/javascript"></script>
48 <script type="text/javascript">
49
50 var View = _.inherit(Dalmatian.View, {
51 _initialize: function ($super) {
52 //设置默认属性
53 $super();
54 this.templateSet = {
55 init: $(‘#template-todolist‘).html()
56 };
57 }
58 });
59
60 var Adapter = _.inherit(Dalmatian.Adapter, {
61 _initialize: function ($super) {
62 $super();
63 this.datamodel = {
64 list: [{content: ‘测试数据01‘}, {content: ‘测试数据02‘}]
65 };
66 },
67
68 addItem: function (item) {
69 this.datamodel.list.push(item);
70 this.notifyDataChanged();
71 },
72
73 removeItem: function (index) {
74 this.datamodel.list.splice(index, 1);
75 this.notifyDataChanged();
76 }
77
78 });
79
80 var adapter = new Adapter();
81
82 var Controller = _.inherit(Dalmatian.ViewController, {
83 _initialize: function ($super) {
84 $super();
85 this.view = new View();
86 this.adapter = adapter;
87 this.container = ‘.container‘;
88 },
89
90 render: function () {
91 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel());
92 this.view.root.html(this.view.html);
93 },
94
95 events: {
96 ‘click button‘: ‘action‘
97 },
98 action: function (e) {
99 e.preventDefault();
100 var target = $(e.currentTarget).attr(‘data-action‘);
101 var strategy = {
102 ‘add‘: function (e) {
103 var value = $(‘#todoinput‘).val();
104 this.adapter.addItem({ content: value });
105 var s = ‘‘;
106 }
107 }
108 strategy[target].apply(this, [e]);
109 }
110 });
111
112 var controller = new Controller();
113 controller.show();
114
115
116 var LView = _.inherit(Dalmatian.View, {
117 _initialize: function ($super) {
118 //设置默认属性
119 $super();
120 this.templateSet = {
121 init: $(‘#template-list‘).html()
122 };
123 }
124 });
125
126 var LController = _.inherit(Dalmatian.ViewController, {
127 _initialize: function ($super) {
128 $super();
129 this.view = new LView();
130 this.adapter = adapter;
131 this.container = ‘.list‘;
132 },
133
134 render: function () {
135 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel());
136 this.view.root.html(this.view.html);
137 },
138
139 events: {
140 ‘click span‘: ‘action‘
141 },
142 action: function (e) {
143 var index = $(e.currentTarget).attr(‘index‘);
144 this.adapter.removeItem(index);
145
146 }
147 });
148
149 var lcontroller = new LController();
150 lcontroller.show();
151
152 </script>
153 </body>
154 </html>
可以看到,上下的变化根源是数据操作,每次数据的变化是共享的
结语
今天更正了上一次留下来的wrapperSet思维,这里对自己所知的MVC做了一次梳理
然后框架在一次次修改中逐步成型了,是个好现象,慢慢来吧