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的东东搞了出来,事实证明还是需要有一些优化的......

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

  • 相关阅读:
    HTML5 Application Cache
    一个页面多个bootstrip轮播以及一个页面多个swiper轮播 冲突问题
    jquery中attr和prop的区别
    eval函数的工作原理
    JSON.parse 函数
    JS知识体系
    闭包
    io输入输出与反射机制2
    IO输入输出与反射机制1
    项目-超市会员管理系统
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3705123.html
Copyright © 2011-2022 走看看