zoukankan      html  css  js  c++  java
  • 【UI插件】简单的日历插件(下)—— 学习MVC思想

    前言

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

    PS:距离上次貌似很久了

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

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

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

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

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

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

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

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

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

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

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

    MVC的学习

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

     1 (function () {
     2 
     3   // @description 全局可能用到的变量
     4   var arr = [];
     5   var slice = arr.slice;
     6 
     7   var method = method || {};
     8 
     9   /**
    10   * @description inherit方法,js的继承,默认为两个参数
    11   * @param {function} supClass 可选,要继承的类
    12   * @param {object} subProperty 被创建类的成员
    13   * @return {function} 被创建的类
    14   */
    15   method.inherit = function () {
    16 
    17     // @description 参数检测,该继承方法,只支持一个参数创建类,或者两个参数继承类
    18     if (arguments.length === 0 || arguments.length > 2) throw '参数错误';
    19 
    20     var parent = null;
    21 
    22     // @description 将参数转换为数组
    23     var properties = slice.call(arguments);
    24 
    25     // @description 如果第一个参数为类(function),那么就将之取出
    26     if (typeof properties[0] === 'function')
    27       parent = properties.shift();
    28     properties = properties[0];
    29 
    30     // @description 创建新类用于返回
    31     function klass() {
    32       if (_.isFunction(this.initialize))
    33         this.initialize.apply(this, arguments);
    34     }
    35 
    36     klass.superclass = parent;
    37     // parent.subclasses = [];
    38 
    39     if (parent) {
    40       // @description 中间过渡类,防止parent的构造函数被执行
    41       var subclass = function () { };
    42       subclass.prototype = parent.prototype;
    43       klass.prototype = new subclass();
    44       // parent.subclasses.push(klass);
    45     }
    46 
    47     var ancestor = klass.superclass && klass.superclass.prototype;
    48     for (var k in properties) {
    49       var value = properties[k];
    50 
    51       //满足条件就重写
    52       if (ancestor && typeof value == 'function') {
    53         var argslist = /^s*functions*(([^()]*?))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*functions*(([^()]*?))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的东东搞了出来,事实证明还是需要有一些优化的......

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

  • 相关阅读:
    ORACLE 查看进程数,已执行任务数, 剩余任务数,删除指定任务
    ORACLE 收集统计整个用户数据
    解决Hystrix dashboard Turbine 一直 Loading…… 及其他坑
    利用 Maven 构造 Spring Cloud 微服务架构 模块使用 spring Boot构建
    AES加解密
    JAVA POI XSSFWorkbook导出扩展名为xlsx的Excel,附带weblogic 项目导出Excel文件错误的解决方案
    JAVA 文件的上传下载
    shell启停服务脚本模板
    JAVA 设计模式之 原型模式详解
    JAVA 设计模式之 工厂模式详解
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3705123.html
Copyright © 2011-2022 走看看