咱們上次寫了一個簡單的日曆插件,可是隻是一個半成品,並且作完後發現一些問題,因而咱們今天嘗試來解決這些問題javascript
PS:距離上次貌似好久了css
上次,咱們大概遇到哪些問題呢:html
① 既然想作一套UI庫,那麼就應該考慮其它UI庫的接入問題java
這個意思就是,咱們的系統中全部UI插件應該有一些統一行爲,咱們若是但願統一爲全部的插件加一點什麼東西,須要有位置可加ios
這個意味着,可能咱們全部的插件須要繼承至一個抽象的UI類,而且該類提供了通用的幾個事件點git
② 上次作的日曆插件雖說是簡單,其耦合仍是比較嚴重的(其實也說不上,可是人總有想裝B的時候)github
這個怎麼說呢,就日曆而言,咱們能夠將之分紅三個部分express
1 日曆核心部分,用於生產靜態htmlbootstrap
2 日曆數據部分,用於顯示各個特殊信息,好比節日什麼的數組
3 日曆事件部分,如今的想法即是能夠將事件相關給抽象出來
目的即是html/data/events 分開一點點,這個該怎麼作呢?這是咱們今天該思考的問題
事情多了就什麼都不能解決,因此咱們今天暫時便處理以上兩個問題便可
因爲咱們會依賴於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);
對的,以上是咱們前面實現的繼承,咱們將之擴展至underscore上,之後以此實現繼承
其次,咱們便須要思考如何分離咱們的數據/模板/事件了
俗話說,大樹底下好乘涼,事實上我一些想法來自於個人老大,我老大又借鑑了原來的ios開發,因此這裏造成了一些東西,不知道是否合理,咱們拿出來看看
首先,不管如何咱們的應用都會有一個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會生成靜態HTML
② View會根據當前狀態、當前數據生成靜態HTML
因此,咱們的View事實上只會生成靜態HTML,不一樣的是他會根據不一樣的狀態生成不一樣的HTML,好比初始狀態和加載結束狀態
有了View便缺不了數據,也就是所謂的Model,咱們這裏給他取一個名字,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 });
Adapter由如下幾個關鍵組成:
① View觀察者
② 數據模型,即是原始的數據
③ viewModel,即是view實際須要的數據
而且每一次數據的改變會通知觀察的view,觸發其update,因此Adapter的組成也比較乾脆,並不複雜
可是,咱們的view仍然沒有事件,並且Adapter也沒有與view聯繫起來,這個時候咱們缺乏一個要件,他的名字是Controller
控制器是連接模型與視圖的橋樑,咱們這裏也不例外,核心動做皆會在控制器處完成
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 });
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等不詳說了
說了這麼多都是扯淡,咱們下面以兩個簡單的例子作一次說明
有不對的地方請提出
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 });
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);
首先咱們來作一個簡單的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定義如此便可
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 });
這個例子結束後,咱們來寫另外一個例子
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>
其次是咱們的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();
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();
這樣的話,業務應該的代碼事實上寫到了類的幾個事件點中了,這些會在實例化時不一樣的狀態被觸發,因此根本沒必要在實例化時作任何操做
實例化時候應該有他的做用,由於繼承到這一層的時候,該業務類便專一於處理這個業務了
上次,咱們的日曆基本都成型了,今天咱們便根據前面的想法爲他作一次封裝......
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>
首次調整後,大概的東西出來了,這樣一次操做後就會發現以前定義的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的東東搞了出來,事實證實仍是須要有一些優化的......
今天狀態不是太好,今天暫時到此,剩下的咱們後面點來,這塊還有不少東西要清理呢。。。。。。