【单页应用】view与model相关梳理(转载)

【单页应用】view与model相关梳理

前情回顾

根据之前的学习,我们形成了一个view与一个messageCenter
view这块来说又内建了一套mvc的东西,我们这里来理一下
首先View一层由三部分组成:
① view
② dataAdpter
③ viewController

view一块两个重要数据是模板以及对应data,一个状态机status
这里view只负责根据状态取出对应的模板,而后根据传入的数据返回组装好的html
这里一个view多种状态是什么意思呢?
比如我有一个组件,但是里面有一圈数据是需要Ajax请求的,所以我的view可能就分为两个状态了
init->ajaxSuccess 这样的话首次加载默认的dom结构,数据加载结束后便再次渲染
PS:这里再次渲染的时候暂时图方便是采用将整个DOM结构换掉的手法,虽然简单粗暴却不合适,这块后期优化

这里数据的变化不由view负责,负责他的是dataAdapter
dataAdpter属于一个独立的模块,可用与多个viewController,dataAdpter内部首先维护着一个观察者数组,
然后是两个关键的datamodel以及viewmodel
datamodel用于操作,viewmodel会根据datamodel生成最终,然后使用viewmodel进行页面render,这个就是传入的data
若是我某一个datamodel对象发生变化便会通知观察者们,然后对应的view就会得到更新,该过程的发生点控制于viewController

viewController是连接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

 Dalmatian.View = _.inherit({

   // @description 设置默认属性
_initialize: function () { var DEFAULT_CONTAINER_TEMPLATE = '<div class="view"></div>';
var VIEW_ID = 'dalmatian-view-'; // @override
// @description template集合,根据status做template的map
// @example
/*
{
init: '<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>'//若是字符串表明全局性
ajaxLoading: 'loading',
ajaxSuc: 'success'
}
*/
this.templateSet = {}; // @override
/*
***这块我没有考虑清楚,一般情况下view是不需要在改变的,若是需要改变其实该设置到datamodel中***
这个可以考虑默认由viewController注入给dataModel,然后后面就可操作了......
这里的包裹器可能存在一定时序关系,这块后续再整理 与模板做映射关系,每个状态的模板对象可能对应一个容器,默认为根容器,后期可能会被修改
ajaxLoading: el,
ajaxSuc: selector
*/
this.wrapperSet = {}; this.viewid = _.uniqueId(VIEW_ID);
this.currentStatus = null;
this.defaultContainer = DEFAULT_CONTAINER_TEMPLATE;
this.isNoWrapper = false; //全局根元素
this.root = null;
//当前包裹器
this.curWrapper = null;
//当前模板对应解析后的html结构 }, _initRoot: function () {
//根据html生成的dom包装对象
//有一种场景是用户的view本身就是一个只有一个包裹器的结构,他不想要多余的包裹器
if (!this.isNoWrapper) {
this.root = $(this.defaultContainer);
this.root.attr('id', this.viewid);
}
}, // @description 构造函数入口
initialize: function (options) {
this._initialize();
this.handleOptions(options);
this._initRoot(); }, // @override
// @description 操作构造函数传入操作
handleOptions: function (options) {
// @description 从形参中获取key和value绑定在this上
// l_wang options可能不是纯净的对象,而是函数什么的,这样需要注意
if (_.isObject(options)) _.extend(this, options); }, //处理包裹器,暂时不予理睬
_handleNoWrapper: function (html) {
//...不予理睬
}, //根据状态值获取当前包裹器
_getCurWrapper: function (status, data) {
//处理root不存在的情况
this._handleNoWrapper(); //若是以下逻辑无用,那么这个便是根元素
if (!data.wrapperSet || !data.wrapperSet[status]) { return this.root; }
if (_.isString(data.wrapperSet[status])) { return this.root.find(data.wrapperSet[status]); } }, // @description 通过模板和数据渲染具体的View
// @param status {enum} View的状态参数
// @param data {object} 匹配View的数据格式的具体数据
// @param callback {functiion} 执行完成之后的回调
render: function (status, data, callback) { var templateFn, wrapper;
var template = this.templateSet[status]; //默认将view中设置的默认wrapper注入值datamodel,datamodel会带入viewModel
wrapper = this._getCurWrapper(status, data); if (!wrapper[0]) throw '包裹器参数错误';
if (!template) return false; //解析当前状态模板,编译成函数
templateFn = Dalmatian.template(template);
wrapper.html(templateFn(data));
this.html = wrapper; this.currentStatus = status; _.callmethod(callback, this);
return true; }, // @override
// @description 可以被复写,当status和data分别发生变化时候
// @param status {enum} view的状态值
// @param data {object} viewmodel的数据
update: function (status, data) { if (!this.currentStatus || this.currentStatus !== status) {
return this.render(status, data);
} // @override
// @description 可复写部分,当数据发生变化但是状态没有发生变化时,页面仅仅变化的可以是局部显示
// 可以通过获取this.html进行修改
_.callmethod(this.onUpdate, this);
}
});

view基本只负责根据模板和数据生成html字符串,有一个不同的点是他需要记录自己的根元素,这个对我们后续操作有帮助

其中比较关键的是templateSet以及wrapperSet,这里的wrapperSet会被注入给dataAdpter的datamodel,后期便于调整

然后是我们的Adapter

 Dalmatian.Adapter = _.inherit({

   // @description 构造函数入口
initialize: function (options) {
this._initialize();
this.handleOptions(options);
}, // @description 设置默认属性
_initialize: function () {
this.observers = [];
// this.viewmodel = {};
this.datamodel = {};
}, // @description 操作构造函数传入操作
handleOptions: function (options) {
// @description 从形参中获取key和value绑定在this上
if (_.isObject(options)) _.extend(this, options);
}, // @override
// @description 操作datamodel返回一个data对象形成viewmodel
format: function (datamodel) {
return datamodel;
}, getViewModel: function () {
return this.format(this.datamodel);
}, registerObserver: function (viewcontroller) {
// @description 检查队列中如果没有viewcontroller,从队列尾部推入
if (!_.contains(this.observers, viewcontroller)) {
this.observers.push(viewcontroller);
}
}, setStatus: function (status) {
_.each(this.observers, function (viewcontroller) {
if (_.isObject(viewcontroller))
viewcontroller.setViewStatus(status);
});
}, unregisterObserver: function (viewcontroller) {
// @description 从observers的队列中剔除viewcontroller
this.observers = _.without(this.observers, viewcontroller);
}, notifyDataChanged: function () {
// @description 通知所有注册的观察者被观察者的数据发生变化
// this.viewmodel = this.format(this.datamodel);
var data = this.getViewModel();
_.each(this.observers, function (viewcontroller) {
if (_.isObject(viewcontroller))
_.callmethod(viewcontroller.update, viewcontroller, [data]);
});
}
});

他只负责更新数据,并在数据变化时候通知ViewController处理变化,接下来就是我们的viewController了

 Dalmatian.ViewController = _.inherit({

   // @description 构造函数入口
initialize: function (options) {
this._initialize();
this.handleOptions(options); //处理datamodel
this._handleDataModel();
this.create();
}, // @description 默认属性设置点,根据该函数,我可以知道该类具有哪些this属性
_initialize: function () { //用户设置的容器选择器,或者dom结构
this.containe;
//根元素
this.$el;
//默认容器
this.root = $('body'); //一定会出现
this.view;
//可能会出现
this.adapter;
//初始化的时候便需要设置view的状态,否则会渲染失败,这里给一个默认值
this.viewstatus = 'init'; }, setViewStatus: function (status) {
this.viewstatus = status;
}, // @description 操作构造函数传入操作
handleOptions: function (options) {
if (!options) return; this._verify(options); // @description 从形参中获取key和value绑定在this上
if (_.isObject(options)) _.extend(this, options);
}, //处理dataAdpter中的datamodel,为其注入view的默认容器数据
_handleDataModel: function () {
//不存在就不予理睬
if (!this.adapter) return;
this.adapter.datamodel.wrapperSet = this.view.wrapperSet;
this.adapter.registerObserver(this);
}, // @description 验证参数
_verify: function (options) {
//这个underscore方法新框架在报错
// if (!_.property('view')(options) && (!this.view)) throw Error('view必须在实例化的时候传入ViewController');
if (options.view && (!this.view)) throw Error('view必须在实例化的时候传入ViewController');
}, // @description 当数据发生变化时调用onViewUpdate,如果onViewUpdate方法不存在的话,直接调用render方法重绘
update: function (data) { //这样虽然减少回流,但会隐藏页面跳动
// _.callmethod(this.hide, this); if (!_.callmethod(this.onViewUpdate, this, [data])) {
this.render();
} // _.callmethod(this.show, this);
}, /**
* @override
*
*/
render: function () {
// @notation 这个方法需要被复写
// var data = this.adapter.format(this.origindata);
this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel());
}, // @description 返回基于当前view下的某节点
find: function (selector) {
if (!this.$el) return null;
return this.$el.find(selector);
}, _create: function () {
this.render(); //render 结束后构建好根元素dom结构
this.$el = $(this.view.html);
}, create: function () { //l_wang 这段代码没有看懂************
// var $element = this.find(this.view.viewid);
// if ($element) return _.callmethod(this.recreate, this);
//l_wang 这段代码没有看懂************ // @notation 在create方法调用前后设置onViewBeforeCreate和onViewAfterCreate两个回调
_.wrapmethod(this._create, 'onViewBeforeCreate', 'onViewAfterCreate', this); }, /**
* @description 如果进入create判断是否需要update一下页面,sync view和viewcontroller的数据
*/
_recreate: function () {
this.update();
}, recreate: function () {
_.wrapmethod(this._recreate, 'onViewBeforeRecreate', 'onViewAfterRecreate', this);
}, //事件注册点
bindEvents: function (events) {
if (!(events || (events = _.result(this, 'events')))) return this;
this.unBindEvents(); // @description 解析event参数的正则
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
var key, method, match, eventName, selector; //注意,此处做简单的字符串数据解析即可,不做实际业务
for (key in events) {
method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; match = key.match(delegateEventSplitter);
eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.view.viewid; if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
} return this;
}, //取消所有事件
unBindEvents: function () {
this.$el.off('.delegateEvents' + this.view.viewid);
return this;
}, _show: function () {
this.bindEvents();
this.root = $(this.container);
this.root.append(this.$el);
this.$el.show();
}, show: function () {
_.wrapmethod(this._show, 'onViewBeforeShow', 'onViewAfterShow', this);
}, _hide: function () {
this.forze();
this.$el.hide();
}, hide: function () {
_.wrapmethod(this._hide, 'onViewBeforeHide', 'onViewAfterHide', this);
}, _forze: function () {
this.unBindEvents();
}, forze: function () {
_.wrapmethod(this._forze, 'onViewBeforeForzen', 'onViewAfterForzen', this);
}, _destory: function () {
this.unBindEvents();
this.$el.remove();
// delete this;
}, destory: function () {
_.wrapmethod(this._destory, 'onViewBeforeDestory', 'onViewAfterDestory', this);
}
});

这个控制器是连接view以及Adapter的桥梁,三者合一便可以处理一些问题,接下来看一个简单的demo

Ajax例子

 <!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ToDoList</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/bootstrap/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/css/flat-ui.css">
<link href="../style/main.css" rel="stylesheet" type="text/css" />
<style type="text/css">
.cui-alert { width: auto; position: static; }
.txt { border: #cfcfcf 1px solid; margin: 10px 0; width: 80%; }
ul, li { padding: 0; margin: 0; }
.cui_calendar, .cui_week { list-style: none; }
.cui_calendar li, .cui_week li { float: left; width: 14%; overflow: hidden; padding: 4px 0; text-align: center; }
</style>
</head>
<body>
<article id="container">
</article>
<script type="text/underscore-template" id="template-ajax-init">
<div class="cui-alert" >
<div class="cui-pop-box">
<div class="cui-hd">
<%=title%>
</div>
<div class="cui-bd">
<div class="cui-error-tips">
</div>
<div class="cui-roller-btns" style="padding: 4px; "><input type="text" placeholder="请设置数据 [{ title: ''},{ title: ''}]" style="margin: 2px; width: 100%; " id="ajax_data" class="txt"></div>
<div class="cui-roller-btns">
<div class="cui-flexbd cui-btns-sure"><%=confirm%></div>
</div>
</div>
</div>
</div>
</script>
<script type="text/underscore-template" id="template-ajax-suc">
<ul>
<% console.log(ajaxData) %>
<%for(var i = 0; i < ajaxData.length; i++) { %>
<li><%=ajaxData[i].title %></li>
<% } %>
</ul>
</script> <script type="text/underscore-template" id="template-ajax-loading">
loading....
</script> <script type="text/javascript" src="../../vendor/underscore-min.js"></script>
<script type="text/javascript" src="../../vendor/zepto.min.js"></script>
<script src="../../src/underscore.extend.js" type="text/javascript"></script>
<script src="../../src/util.js" type="text/javascript"></script>
<script src="../../src/message-center-wl.js" type="text/javascript"></script>
<script src="../../src/mvc-wl.js" type="text/javascript"></script>
<script type="text/javascript"> //模拟Ajax请求
function getAjaxData(callback, data) {
setTimeout(function () {
if (!data) {
data = [];
for (var i = 0; i < 5; i++) {
data.push({ title: '我是标题_' + i });
}
}
callback(data);
}, 1000);
} var AjaxView = _.inherit(Dalmatian.View, {
_initialize: function ($super) {
//设置默认属性
$super(); this.templateSet = {
init: $('#template-ajax-init').html(),
loading: $('#template-ajax-loading').html(),
ajaxSuc: $('#template-ajax-suc').html()
}; this.wrapperSet = {
loading: '.cui-error-tips',
ajaxSuc: '.cui-error-tips'
};
}
}); var AjaxAdapter = _.inherit(Dalmatian.Adapter, {
_initialize: function ($super) {
$super();
this.datamodel = {
title: '标题',
confirm: '刷新数据'
};
this.datamodel.ajaxData = {};
}, format: function (datamodel) {
//处理datamodel生成viewModel的逻辑
return datamodel;
}, ajaxLoading: function () {
this.setStatus('loading');
this.notifyDataChanged();
}, ajaxSuc: function (data) {
this.datamodel.ajaxData = data;
this.setStatus('ajaxSuc');
this.notifyDataChanged();
}
}); var AjaxViewController = _.inherit(Dalmatian.ViewController, {
_initialize: function ($super) {
$super();
//设置基本的属性
this.view = new AjaxView();
this.adapter = new AjaxAdapter();
this.viewstatus = 'init';
this.container = '#container';
}, //显示后Ajax请求数据
onViewAfterShow: function () {
this._handleAjax();
}, _handleAjax: function (data) {
this.adapter.ajaxLoading();
getAjaxData($.proxy(function (data) {
this.adapter.ajaxSuc(data);
}, this), data);
}, events: {
'click .cui-btns-sure': function () {
var data = this.$el.find('#ajax_data').val();
data = eval('(' + data + ')');
this._handleAjax(data);
}
}
}); var a = new AjaxViewController();
a.show(); </script>
</body>
</html>

这段代码的核心在此

 //模拟Ajax请求
function getAjaxData(callback, data) {
setTimeout(function () {
if (!data) {
data = [];
for (var i = 0; i < 5; i++) {
data.push({ title: '我是标题_' + i });
}
}
callback(data);
}, 1000);
} var AjaxView = _.inherit(Dalmatian.View, {
_initialize: function ($super) {
//设置默认属性
$super(); this.templateSet = {
init: $('#template-ajax-init').html(),
loading: $('#template-ajax-loading').html(),
ajaxSuc: $('#template-ajax-suc').html()
}; this.wrapperSet = {
loading: '.cui-error-tips',
ajaxSuc: '.cui-error-tips'
};
}
}); var AjaxAdapter = _.inherit(Dalmatian.Adapter, {
_initialize: function ($super) {
$super();
this.datamodel = {
title: '标题',
confirm: '刷新数据'
};
this.datamodel.ajaxData = {};
}, format: function (datamodel) {
//处理datamodel生成viewModel的逻辑
return datamodel;
}, ajaxLoading: function () {
this.setStatus('loading');
this.notifyDataChanged();
}, ajaxSuc: function (data) {
this.datamodel.ajaxData = data;
this.setStatus('ajaxSuc');
this.notifyDataChanged();
}
}); var AjaxViewController = _.inherit(Dalmatian.ViewController, {
_initialize: function ($super) {
$super();
//设置基本的属性
this.view = new AjaxView();
this.adapter = new AjaxAdapter();
this.viewstatus = 'init';
this.container = '#container';
}, //显示后Ajax请求数据
onViewAfterShow: function () {
this._handleAjax();
}, _handleAjax: function (data) {
this.adapter.ajaxLoading();
getAjaxData($.proxy(function (data) {
this.adapter.ajaxSuc(data);
}, this), data);
}, events: {
'click .cui-btns-sure': function () {
var data = this.$el.find('#ajax_data').val();
data = eval('(' + data + ')');
this._handleAjax(data);
}
}
}); var a = new AjaxViewController();
a.show();

首先定义view

其次定义数据处理层

最后将两者合一

重点放到了数据处理中,实际上的逻辑由Controller处理,真正的html又view生成,整个代码如上......

【单页应用】view与model相关梳理(转载)

【单页应用】view与model相关梳理(转载)

【单页应用】view与model相关梳理(转载)

【单页应用】view与model相关梳理(转载)

结语

今天对之前的学习进行了一些整理,由于过程中多数时间在编码,所以描述少了一点,整个这块还是有一些问题,我们留待后期解决吧

本文由豆约翰博客备份专家远程一键发布
上一篇:shiro安全框架


下一篇:解决select2在bootstrap的modal中默认不显示的问题