【UI插件】簡單的日曆插件(下)—— 學習MVC思想

前言

咱們上次寫了一個簡單的日曆插件,可是隻是一個半成品,並且作完後發現一些問題,因而咱們今天嘗試來解決這些問題javascript

PS:距離上次貌似好久了css

上次,咱們大概遇到哪些問題呢:html

① 既然想作一套UI庫,那麼就應該考慮其它UI庫的接入問題java

這個意思就是,咱們的系統中全部UI插件應該有一些統一行爲,咱們若是但願統一爲全部的插件加一點什麼東西,須要有位置可加ios

這個意味着,可能咱們全部的插件須要繼承至一個抽象的UI類,而且該類提供了通用的幾個事件點git

② 上次作的日曆插件雖說是簡單,其耦合仍是比較嚴重的(其實也說不上,可是人總有想裝B的時候)github

這個怎麼說呢,就日曆而言,咱們能夠將之分紅三個部分express

1 日曆核心部分,用於生產靜態htmlbootstrap

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 = properties[k];
50 
51       //知足條件就重寫
52       if (ancestor && typeof value == '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 = (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 = 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 = 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 = 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 = 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 = properties[k];
 51 
 52       //知足條件就重寫
 53       if (ancestor && typeof value == '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 = (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 = 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 = {
      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 = {
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 = 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 = 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 = $('#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 = 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 = $('#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的東東搞了出來,事實證實仍是須要有一些優化的......

今天狀態不是太好,今天暫時到此,剩下的咱們後面點來,這塊還有不少東西要清理呢。。。。。。

相關文章
相關標籤/搜索