之前我们为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的模型来说
 
【单页应用】理解MVC
【单页应用】理解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

相关文章:

  • 2021-04-07
  • 2022-12-23
  • 2022-12-23
  • 2021-12-13
猜你喜欢
  • 2022-12-23
  • 2021-10-05
  • 2022-03-02
  • 2021-05-24
  • 2021-09-23
  • 2021-09-12
相关资源
相似解决方案