前情回顾
根据之前的学习,我们形成了一个view与一个messageCenter
view这块来说又内建了一套mvc的东西,我们这里来理一下首先View一层由三部分组成:① view② dataAdpter③ viewControllerview一块两个重要数据是模板以及对应data,一个状态机status
这里view只负责根据状态取出对应的模板,而后根据传入的数据返回组装好的html这里一个view多种状态是什么意思呢?比如我有一个组件,但是里面有一圈数据是需要Ajax请求的,所以我的view可能就分为两个状态了init->ajaxSuccess 这样的话首次加载默认的dom结构,数据加载结束后便再次渲染PS:这里再次渲染的时候暂时图方便是采用将整个DOM结构换掉的手法,虽然简单粗暴却不合适,这块后期优化这里数据的变化不由view负责,负责他的是dataAdapter
dataAdpter属于一个独立的模块,可用与多个viewController,dataAdpter内部首先维护着一个观察者数组,然后是两个关键的datamodel以及viewmodeldatamodel用于操作,viewmodel会根据datamodel生成最终,然后使用viewmodel进行页面render,这个就是传入的data若是我某一个datamodel对象发生变化便会通知观察者们,然后对应的view就会得到更新,该过程的发生点控制于viewControllerviewController是连接view与dataAdpter的枢纽
viewController必须具有view,却可以没有dataAdpter,因为不是所有view都需要data才能渲染我们实际工作中的大量业务逻辑会在viewController中定义完成,然后viewController也分了几个事件点① create 触发onViewBeforeCreate、onViewAfterCreate事件② show会实际将dom结构转入并且显示出来 触发onViewBeforeShow、onViewAfterShow事件show的时候会绑定相关事件,事件借鉴于Backbone事件机制,每次注册前会先移除③ 而后便是hide事件,他会隐藏我们的dom却不会移除,对应会有onViewBeforeHide、onViewAfterHide④ destroy事件,会移除dom结构,并且删除实例、释放自身资源以上是主流功能,还有一些功能不一定常用,比如我们任务view隐藏后,其所有状态事件全部应该移除,在show时重新绑定messageCenter
现在没有什么大问题,却有一个小隐忧,这个消息中心会全局分发,一旦注册后,在触发时皆会触发,这个就有一个问题
我有一个alert组件,我自己内部在初始化时候注册了一个onShow的事件,我在show的时候真正的执行之这个看上去没有什么问题,但是以下场景会有不一样的感受我一个页面上有两个alert实例的话,我调用其中一个的时候,另一个alert的onShow也会被触发,这个是我们不愿意看见的换个例子,我们一个页面上有两个IScroll,我们如使用messageCenter的话,一个滑动结束触发对应键值事件,很有可能两边会同时被触发所以,这些都是我们需要关注的问题下面让我们来详细整理View相关梳理
现在View相关的功能点还不完全成熟,主要纠结点在于modelView改变后,view应该作何反应
若是一小点数据的改变却会引起整个dom结构的重组,这一点也是致命的,其次一个view不同的状态会组成不同的view,但是一个view组成的html应该有一个容器,此“容器”现阶段我们概念感不是很强所谓容器,不过是有模板嵌套的场景,后加载出来的html需要放入之前的某一个位置若是子模板改变只会改变对应部分的dom、若是主模板改变就只能全部dom重组了!!!于是我们简单整理后的代码如下:
首先来看看我们的view
1 Dalmatian.View = _.inherit({ 2 3 // @description 设置默认属性 4 _initialize: function () { 5 6 var DEFAULT_CONTAINER_TEMPLATE = ' '; 7 var VIEW_ID = 'dalmatian-view-'; 8 9 // @override 10 // @description template集合,根据status做template的map 11 // @example 12 /* 13 { 14 init: '
- <%_.each(list, function(item){%>
- <%=item.name%> <%});%>
view基本只负责根据模板和数据生成html字符串,有一个不同的点是他需要记录自己的根元素,这个对我们后续操作有帮助
其中比较关键的是templateSet以及wrapperSet,这里的wrapperSet会被注入给dataAdpter的datamodel,后期便于调整
然后是我们的Adapter
1 Dalmatian.Adapter = _.inherit({ 2 3 // @description 构造函数入口 4 initialize: function (options) { 5 this._initialize(); 6 this.handleOptions(options); 7 }, 8 9 // @description 设置默认属性10 _initialize: function () {11 this.observers = [];12 // this.viewmodel = {};13 this.datamodel = {};14 },15 16 // @description 操作构造函数传入操作17 handleOptions: function (options) {18 // @description 从形参中获取key和value绑定在this上19 if (_.isObject(options)) _.extend(this, options);20 },21 22 // @override23 // @description 操作datamodel返回一个data对象形成viewmodel24 format: function (datamodel) {25 return datamodel;26 },27 28 getViewModel: function () {29 return this.format(this.datamodel);30 },31 32 registerObserver: function (viewcontroller) {33 // @description 检查队列中如果没有viewcontroller,从队列尾部推入34 if (!_.contains(this.observers, viewcontroller)) {35 this.observers.push(viewcontroller);36 }37 },38 39 setStatus: function (status) {40 _.each(this.observers, function (viewcontroller) {41 if (_.isObject(viewcontroller))42 viewcontroller.setViewStatus(status);43 });44 },45 46 unregisterObserver: function (viewcontroller) {47 // @description 从observers的队列中剔除viewcontroller48 this.observers = _.without(this.observers, viewcontroller);49 },50 51 notifyDataChanged: function () {52 // @description 通知所有注册的观察者被观察者的数据发生变化53 // this.viewmodel = this.format(this.datamodel);54 var data = this.getViewModel();55 _.each(this.observers, function (viewcontroller) {56 if (_.isObject(viewcontroller))57 _.callmethod(viewcontroller.update, viewcontroller, [data]);58 });59 }60 });
他只负责更新数据,并在数据变化时候通知ViewController处理变化,接下来就是我们的viewController了
1 Dalmatian.ViewController = _.inherit({ 2 3 // @description 构造函数入口 4 initialize: function (options) { 5 this._initialize(); 6 this.handleOptions(options); 7 8 //处理datamodel 9 this._handleDataModel(); 10 this.create(); 11 }, 12 13 // @description 默认属性设置点,根据该函数,我可以知道该类具有哪些this属性 14 _initialize: function () { 15 16 //用户设置的容器选择器,或者dom结构 17 this.containe; 18 //根元素 19 this.$el; 20 //默认容器 21 this.root = $('body'); 22 23 //一定会出现 24 this.view; 25 //可能会出现 26 this.adapter; 27 //初始化的时候便需要设置view的状态,否则会渲染失败,这里给一个默认值 28 this.viewstatus = 'init'; 29 30 }, 31 32 setViewStatus: function (status) { 33 this.viewstatus = status; 34 }, 35 36 // @description 操作构造函数传入操作 37 handleOptions: function (options) { 38 if (!options) return; 39 40 this._verify(options); 41 42 // @description 从形参中获取key和value绑定在this上 43 if (_.isObject(options)) _.extend(this, options); 44 }, 45 46 //处理dataAdpter中的datamodel,为其注入view的默认容器数据 47 _handleDataModel: function () { 48 //不存在就不予理睬 49 if (!this.adapter) return; 50 this.adapter.datamodel.wrapperSet = this.view.wrapperSet; 51 this.adapter.registerObserver(this); 52 }, 53 54 // @description 验证参数 55 _verify: function (options) { 56 //这个underscore方法新框架在报错 57 // if (!_.property('view')(options) && (!this.view)) throw Error('view必须在实例化的时候传入ViewController'); 58 if (options.view && (!this.view)) throw Error('view必须在实例化的时候传入ViewController'); 59 }, 60 61 // @description 当数据发生变化时调用onViewUpdate,如果onViewUpdate方法不存在的话,直接调用render方法重绘 62 update: function (data) { 63 64 //这样虽然减少回流,但会隐藏页面跳动 65 // _.callmethod(this.hide, this); 66 67 if (!_.callmethod(this.onViewUpdate, this, [data])) { 68 this.render(); 69 } 70 71 // _.callmethod(this.show, this); 72 }, 73 74 /** 75 * @override 76 * 77 */ 78 render: function () { 79 // @notation 这个方法需要被复写 80 // var data = this.adapter.format(this.origindata); 81 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel()); 82 }, 83 84 // @description 返回基于当前view下的某节点 85 find: function (selector) { 86 if (!this.$el) return null; 87 return this.$el.find(selector); 88 }, 89 90 _create: function () { 91 this.render(); 92 93 //render 结束后构建好根元素dom结构 94 this.$el = $(this.view.html); 95 }, 96 97 create: function () { 98 99 //l_wang 这段代码没有看懂************100 // var $element = this.find(this.view.viewid);101 // if ($element) return _.callmethod(this.recreate, this);102 //l_wang 这段代码没有看懂************103 104 // @notation 在create方法调用前后设置onViewBeforeCreate和onViewAfterCreate两个回调105 _.wrapmethod(this._create, 'onViewBeforeCreate', 'onViewAfterCreate', this);106 107 },108 109 /**110 * @description 如果进入create判断是否需要update一下页面,sync view和viewcontroller的数据111 */112 _recreate: function () {113 this.update();114 },115 116 recreate: function () {117 _.wrapmethod(this._recreate, 'onViewBeforeRecreate', 'onViewAfterRecreate', this);118 },119 120 //事件注册点121 bindEvents: function (events) {122 if (!(events || (events = _.result(this, 'events')))) return this;123 this.unBindEvents();124 125 // @description 解析event参数的正则126 var delegateEventSplitter = /^(\S+)\s*(.*)$/;127 var key, method, match, eventName, selector;128 129 //注意,此处做简单的字符串数据解析即可,不做实际业务130 for (key in events) {131 method = events[key];132 if (!_.isFunction(method)) method = this[events[key]];133 if (!method) continue;134 135 match = key.match(delegateEventSplitter);136 eventName = match[1], selector = match[2];137 method = _.bind(method, this);138 eventName += '.delegateEvents' + this.view.viewid;139 140 if (selector === '') {141 this.$el.on(eventName, method);142 } else {143 this.$el.on(eventName, selector, method);144 }145 }146 147 return this;148 },149 150 //取消所有事件151 unBindEvents: function () {152 this.$el.off('.delegateEvents' + this.view.viewid);153 return this;154 },155 156 _show: function () {157 this.bindEvents();158 this.root = $(this.container);159 this.root.append(this.$el);160 this.$el.show();161 },162 163 show: function () {164 _.wrapmethod(this._show, 'onViewBeforeShow', 'onViewAfterShow', this);165 },166 167 _hide: function () {168 this.forze();169 this.$el.hide();170 },171 172 hide: function () {173 _.wrapmethod(this._hide, 'onViewBeforeHide', 'onViewAfterHide', this);174 },175 176 _forze: function () {177 this.unBindEvents();178 },179 180 forze: function () {181 _.wrapmethod(this._forze, 'onViewBeforeForzen', 'onViewAfterForzen', this);182 },183 184 _destory: function () {185 this.unBindEvents();186 this.$el.remove();187 // delete this;188 },189 190 destory: function () {191 _.wrapmethod(this._destory, 'onViewBeforeDestory', 'onViewAfterDestory', this);192 }193 });
这个控制器是连接view以及Adapter的桥梁,三者合一便可以处理一些问题,接下来看一个简单的demo
Ajax例子
1 2 3 4 5ToDoList 6 7 8 9 10 17 18 1920 21 38 46 47 50 51 52 53 54 55 56 57 152 153
这段代码的核心在此
1 //模拟Ajax请求 2 function getAjaxData(callback, data) { 3 setTimeout(function () { 4 if (!data) { 5 data = []; 6 for (var i = 0; i < 5; i++) { 7 data.push({ title: '我是标题_' + i }); 8 } 9 }10 callback(data);11 }, 1000);12 }13 14 var AjaxView = _.inherit(Dalmatian.View, {15 _initialize: function ($super) {16 //设置默认属性17 $super();18 19 this.templateSet = {20 init: $('#template-ajax-init').html(),21 loading: $('#template-ajax-loading').html(),22 ajaxSuc: $('#template-ajax-suc').html()23 };24 25 this.wrapperSet = {26 loading: '.cui-error-tips',27 ajaxSuc: '.cui-error-tips'28 };29 }30 });31 32 var AjaxAdapter = _.inherit(Dalmatian.Adapter, {33 _initialize: function ($super) {34 $super();35 this.datamodel = {36 title: '标题',37 confirm: '刷新数据'38 };39 this.datamodel.ajaxData = {};40 },41 42 format: function (datamodel) {43 //处理datamodel生成viewModel的逻辑44 return datamodel;45 },46 47 ajaxLoading: function () {48 this.setStatus('loading');49 this.notifyDataChanged();50 },51 52 ajaxSuc: function (data) {53 this.datamodel.ajaxData = data;54 this.setStatus('ajaxSuc');55 this.notifyDataChanged();56 }57 });58 59 var AjaxViewController = _.inherit(Dalmatian.ViewController, {60 _initialize: function ($super) {61 $super();62 //设置基本的属性63 this.view = new AjaxView();64 this.adapter = new AjaxAdapter();65 this.viewstatus = 'init';66 this.container = '#container';67 },68 69 //显示后Ajax请求数据70 onViewAfterShow: function () {71 this._handleAjax();72 },73 74 _handleAjax: function (data) {75 this.adapter.ajaxLoading();76 getAjaxData($.proxy(function (data) {77 this.adapter.ajaxSuc(data);78 }, this), data);79 },80 81 events: {82 'click .cui-btns-sure': function () {83 var data = this.$el.find('#ajax_data').val();84 data = eval('(' + data + ')');85 this._handleAjax(data);86 }87 }88 });89 90 var a = new AjaxViewController();91 a.show();
首先定义view
其次定义数据处理层
最后将两者合一
重点放到了数据处理中,实际上的逻辑由Controller处理,真正的html又view生成,整个代码如上......
结语
今天对之前的学习进行了一些整理,由于过程中多数时间在编码,所以描述少了一点,整个这块还是有一些问题,我们留待后期解决吧