前言

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

PS:距离上次貌似很久了

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

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

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

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

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

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

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

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

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

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

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

MVC的学习

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

 (function () {

   // @description 全局可能用到的变量
var arr = [];
var slice = arr.slice; var method = method || {}; /**
* @description inherit方法,js的继承,默认为两个参数
* @param {function} supClass 可选,要继承的类
* @param {object} subProperty 被创建类的成员
* @return {function} 被创建的类
*/
method.inherit = function () { // @description 参数检测,该继承方法,只支持一个参数创建类,或者两个参数继承类
if (arguments.length === 0 || arguments.length > 2) throw '参数错误'; var parent = null; // @description 将参数转换为数组
var properties = slice.call(arguments); // @description 如果第一个参数为类(function),那么就将之取出
if (typeof properties[0] === 'function')
parent = properties.shift();
properties = properties[0]; // @description 创建新类用于返回
function klass() {
if (_.isFunction(this.initialize))
this.initialize.apply(this, arguments);
} klass.superclass = parent;
// parent.subclasses = []; if (parent) {
// @description 中间过渡类,防止parent的构造函数被执行
var subclass = function () { };
subclass.prototype = parent.prototype;
klass.prototype = new subclass();
// parent.subclasses.push(klass);
} var ancestor = klass.superclass && klass.superclass.prototype;
for (var k in properties) {
var value = properties[k]; //满足条件就重写
if (ancestor && typeof value == 'function') {
var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
//只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
if (argslist[0] === '$super' && ancestor[k]) {
value = (function (methodName, fn) {
return function () {
var scope = this;
var args = [function () {
return ancestor[methodName].apply(scope, arguments);
} ];
return fn.apply(this, args.concat(slice.call(arguments)));
};
})(k, value);
}
} //此处对对象进行扩展,当前原型链已经存在该对象,便进行扩展
if (_.isObject(klass.prototype[k]) && _.isObject(value) && (typeof klass.prototype[k] != 'function' && typeof value != 'fuction')) {
//原型链是共享的,这里不好办
var temp = {};
_.extend(temp, klass.prototype[k]);
_.extend(temp, value);
klass.prototype[k] = temp;
} else {
klass.prototype[k] = value;
} } if (!klass.prototype.initialize)
klass.prototype.initialize = function () { }; klass.prototype.constructor = klass; return klass;
}; _.extend(_, method); })(window);

对的,以上是我们前面实现的继承,我们将之扩展至underscore上,以后以此实现继承

其次,我们便需要思考如何分离我们的数据/模板/事件了

View/Adapter/ViewController

俗话说,大树底下好乘凉,事实上我一些想法来自于我的老大,我老大又借鉴了原来的ios开发,所以这里形成了一些东西,不知道是否合理,我们拿出来看看

View

首先,无论如何我们的应用都会有一个view的存在,我们认为view只做简单的页面渲染就好,与之有关的数据/事件什么的,我们不予关注

 // @description 正式的声明Dalmatian框架的命名空间
var Dalmatian = Dalmatian || {}; // @description 定义默认的template方法来自于underscore
Dalmatian.template = _.template;
Dalmatian.View = _.inherit({
// @description 构造函数入口
initialize: function(options) {
this._initialize();
this.handleOptions(options); }, // @description 设置默认属性
_initialize: function() { var DEFAULT_CONTAINER_TEMPLATE = '<section class="view" id="<%=viewid%>"><%=html%></section>'; // @description view状态机
// this.statusSet = {}; this.defaultContainerTemplate = DEFAULT_CONTAINER_TEMPLATE; // @override
// @description template集合,根据status做template的map
// @example
// { 0: '<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>' }
// this.templateSet = {}; this.viewid = _.uniqueId('dalmatian-view-'); }, // @description 操作构造函数传入操作
handleOptions: function(options) {
// @description 从形参中获取key和value绑定在this上
if (_.isObject(options)) _.extend(this, options); }, // @description 通过模板和数据渲染具体的View
// @param status {enum} View的状态参数
// @param data {object} 匹配View的数据格式的具体数据
// @param callback {functiion} 执行完成之后的回调
render: function(status, data, callback) { var templateSelected = this.templateSet[status];
if (templateSelected) { try {
// @description 渲染view
var templateFn = Dalmatian.template(templateSelected);
this.html = templateFn(data); // @description 在view外层加入外壳
templateFn = Dalmatian.template(this.defaultContainerTemplate);
this.html = templateFn({
viewid: this.viewid,
html: this.html
}); this.currentStatus = status; _.callmethod(callback, this); return true; } catch (e) { throw e; } finally { return false;
}
}
}, // @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

② View会根据当前状态、当前数据生成静态HTML

所以,我们的View事实上只会生成静态HTML,不同的是他会根据不同的状态生成不同的HTML,比如初始状态和加载结束状态

有了View便缺不了数据,也就是所谓的Model,我们这里给他取一个名字,Adapter

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);
}, // @description 设置
format: function(origindata){
this.datamodel = origindata;
this.viewmodel = this.parse(origindata);
return this.viewmodel;
}, // @override
// @description parse方法用来将datamodel转化为viewmodel,必须被重写
parse: function(origindata) {
throw Error('方法必须被重写');
}, registerObserver: function(viewcontroller) {
// @description 检查队列中如果没有viewcontroller,从队列尾部推入
if (!_.contains(this.observers, viewcontroller)) {
this.observers.push(viewcontroller);
}
}, unregisterObserver: function(viewcontroller) {
// @description 从observers的队列中剔除viewcontroller
this.observers = _.without(this.observers, viewcontroller);
}, notifyDataChanged: function() {
// @description 通知所有注册的观察者被观察者的数据发生变化
var data = this.format(this.datamodel);
_.each(this.observers, function(viewcontroller) {
if (_.isObject(viewcontroller))
_.callmethod(viewcontroller.update, viewcontroller, [data]);
});
}
});

Adapter由以下几个关键组成:

① View观察者

② 数据模型,便是原始的数据

③ viewModel,便是view实际需要的数据

并且每一次数据的改变会通知观察的view,触发其update,所以Adapter的组成也比较干脆,并不复杂

但是,我们的view仍然没有事件,而且Adapter也没有与view联系起来,这个时候我们缺少一个要件,他的名字是Controller

ViewController

控制器是链接模型与视图的桥梁,我们这里也不例外,核心动作皆会在控制器处完成

 Dalmatian.ViewController = _.inherit({

   // @description 构造函数入口
initialize: function (options) {
this.handleOptions(options);
this.create();
}, // @description 操作构造函数传入操作
handleOptions: function (options) {
this._verify(options); // @description 从形参中获取key和value绑定在this上
if (_.isObject(options)) _.extend(this, options);
}, // @description 验证参数
_verify: function (options) {
if (!_.property('view')(options)) 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);
}, /**
* @description 传入事件对象,解析之,解析event,返回对象{events: [{target: '#btn', event:'click', callback: handler}]}
* @param events {obj} 事件对象,默认传入唯一id
* @param namespace 事件命名空间
* @return {obj}
*/
parseEvents: function (events) { //用于返回的事件对象
var eventArr = [];
//注意,此处做简单的字符串数据解析即可,不做实际业务
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; var match = key.match(delegateEventSplitter);
var eventName = match[1],
selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.view.viewid;
eventArr.push({
target: selector,
event: eventName,
callback: method
});
} return eventArr;
}, /**
* @override
*
*/
render: function() {
// @notation 这个方法需要被复写
// var data = this.adapter.format(this.origindata);
// this.view.render(this.viewstatus, data);
}, _create: function () {
this.render();
}, create: function () { var $element = selectDom(this.view.viewid);
if (domImplement($element, 'get', false, [0])) {
return _.callmethod(this.recreate, this);
} // @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);
}, _bind: function () {
this.viewcontent = createDom(this.view.html); var eventsList = this.parseEvents(this.events); var scope = this;
_.each(eventsList, function (item) { if (item.target === '') {
eventmethod(scope.viewcontent, 'on', item.event, item.callback, scope);
} else {
eventmethod(scope.viewcontent, 'on', item.event, item.callback, scope, item.target);
} });
}, bind: function () {
_.wrapmethod(this._bind, 'onViewBeforeBind', 'onViewAfterBind', this);
}, _show: function () {
var $element = selectDom('#' + this.view.viewid); // @notation 需要剔除码?
// if ((!$element || $element.length === 0) && this.viewcontent) {
var $container = selectDom(this.container);
domImplement($container, 'html', false, [this.viewcontent]);
// } domImplement($element, 'show');
}, show: function () {
this.bind(); _.wrapmethod(this._show, 'onViewBeforeShow', 'onViewAfterShow', this);
}, _hide: function () {
var $element = selectDom('#' + this.view.viewid);
domImplement($element, 'hide');
}, hide: function () {
_.wrapmethod(this._hide, 'onViewBeforeHide', 'onViewAfterHide', this); this.forze();
}, _forze: function () {
var $element = selectDom('#' + this.view.viewid);
domImplement($element, 'off');
}, forze: function () {
_.wrapmethod(this._forze, 'onViewBeforeForzen', 'onViewAfterForzen', this);
}, _destory: function () {
var $element = selectDom('#' + this.view.viewid).remove();
domImplement($element, 'remove');
}, destory: function () {
_.wrapmethod(this._destory, 'onViewBeforeDestory', 'onViewAfterDestory', this);
}
});

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学习完整代码

有不对的地方请提出

 "use strict";

 // @notation 本框架默认是以来于zepto。这里构建了基础的方法层,当用户使用其他框架时,可能需要复写这几个基础方法

 // @description 解析event参数的正则
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// Regular expression used to split event strings.
var eventSplitter = /\s+/; // ----------------------------------------------------
// @notation 从backbone中借鉴而来,用来多事件绑定的events // Implement fancy features of the Events API such as multiple event
// names `"change blur"` and jQuery-style event maps `{change: action}`
// in terms of the existing API.
var eventoperator = function(obj, action, name, rest) {
if (!name) return true; // Handle event maps.
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
} // Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, length = names.length; i < length; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
} return true;
};
// ---------------------------------------------------- // @notation 默认使用zepto的事件委托机制
function eventmethod(obj, action, name, callback, context, subobj) {
// _.bind(callback, context || this); var delegate = function(target, eventName, eventCallback, subtarget) {
if (subtarget) {
target.on(eventName, subtarget, eventCallback);
}else{
target.on(eventName, eventCallback);
}
}; var undelegate = function(target, eventName, eventCallback, subtarget) {
if (subtarget) {
target.off(eventName, subtarget, eventCallback);
}else{
target.off(eventName, eventCallback);
}
}; var trigger = function(target, eventName, subtarget) {
if (subtarget) {
target.find(subtarget).trigger(eventName);
}else{
target.trigger(eventName);
}
}; var map = {
'on': delegate,
'bind': delegate,
'off': undelegate,
'unbind': undelegate,
'trigger': trigger
}; if (_.isFunction(map[action])) {
map[action](obj, name, callback, subobj);
} } // @description 选择器
function selectDom(selector) {
return $(selector);
} function domImplement($element, action, context, param) {
if (_.isFunction($element[action]))
$element[action].apply(context || $element, param);
} function createDom (html) {
return $(html);
} // --------------------------------------------------- //
// ------------------华丽的分割线--------------------- // // @description 正式的声明Dalmatian框架的命名空间
var Dalmatian = Dalmatian || {}; // @description 定义默认的template方法来自于underscore
Dalmatian.template = _.template;
Dalmatian.View = _.inherit({
// @description 构造函数入口
initialize: function(options) {
this._initialize();
this.handleOptions(options); }, // @description 设置默认属性
_initialize: function() { var DEFAULT_CONTAINER_TEMPLATE = '<section class="view" id="<%=viewid%>"><%=html%></section>'; // @description view状态机
// this.statusSet = {}; this.defaultContainerTemplate = DEFAULT_CONTAINER_TEMPLATE; // @override
// @description template集合,根据status做template的map
// @example
// { 0: '<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>' }
// this.templateSet = {}; this.viewid = _.uniqueId('dalmatian-view-'); }, // @description 操作构造函数传入操作
handleOptions: function(options) {
// @description 从形参中获取key和value绑定在this上
if (_.isObject(options)) _.extend(this, options); }, // @description 通过模板和数据渲染具体的View
// @param status {enum} View的状态参数
// @param data {object} 匹配View的数据格式的具体数据
// @param callback {functiion} 执行完成之后的回调
render: function(status, data, callback) { var templateSelected = this.templateSet[status];
if (templateSelected) { try {
// @description 渲染view
var templateFn = Dalmatian.template(templateSelected);
this.html = templateFn(data); // @description 在view外层加入外壳
templateFn = Dalmatian.template(this.defaultContainerTemplate);
this.html = templateFn({
viewid: this.viewid,
html: this.html
}); this.currentStatus = status; _.callmethod(callback, this); return true; } catch (e) { throw e; } finally { return false;
}
}
}, // @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);
}
}); 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);
}, // @description 设置
format: function(origindata){
this.datamodel = origindata;
this.viewmodel = this.parse(origindata);
return this.viewmodel;
}, // @override
// @description parse方法用来将datamodel转化为viewmodel,必须被重写
parse: function(origindata) {
throw Error('方法必须被重写');
}, registerObserver: function(viewcontroller) {
// @description 检查队列中如果没有viewcontroller,从队列尾部推入
if (!_.contains(this.observers, viewcontroller)) {
this.observers.push(viewcontroller);
}
}, unregisterObserver: function(viewcontroller) {
// @description 从observers的队列中剔除viewcontroller
this.observers = _.without(this.observers, viewcontroller);
}, notifyDataChanged: function() {
// @description 通知所有注册的观察者被观察者的数据发生变化
var data = this.format(this.datamodel);
_.each(this.observers, function(viewcontroller) {
if (_.isObject(viewcontroller))
_.callmethod(viewcontroller.update, viewcontroller, [data]);
});
}
}); Dalmatian.ViewController = _.inherit({ // @description 构造函数入口
initialize: function (options) {
this.handleOptions(options);
this.create();
}, // @description 操作构造函数传入操作
handleOptions: function (options) {
this._verify(options); // @description 从形参中获取key和value绑定在this上
if (_.isObject(options)) _.extend(this, options);
}, // @description 验证参数
_verify: function (options) {
if (!_.property('view')(options)) 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);
}, /**
* @description 传入事件对象,解析之,解析event,返回对象{events: [{target: '#btn', event:'click', callback: handler}]}
* @param events {obj} 事件对象,默认传入唯一id
* @param namespace 事件命名空间
* @return {obj}
*/
parseEvents: function (events) { //用于返回的事件对象
var eventArr = [];
//注意,此处做简单的字符串数据解析即可,不做实际业务
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; var match = key.match(delegateEventSplitter);
var eventName = match[1],
selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.view.viewid;
eventArr.push({
target: selector,
event: eventName,
callback: method
});
} return eventArr;
}, /**
* @override
*
*/
render: function() {
// @notation 这个方法需要被复写
// var data = this.adapter.format(this.origindata);
// this.view.render(this.viewstatus, data);
}, _create: function () {
this.render();
}, create: function () { var $element = selectDom(this.view.viewid);
if (domImplement($element, 'get', false, [0])) {
return _.callmethod(this.recreate, this);
} // @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);
}, _bind: function () {
this.viewcontent = createDom(this.view.html); var eventsList = this.parseEvents(this.events); var scope = this;
_.each(eventsList, function (item) { if (item.target === '') {
eventmethod(scope.viewcontent, 'on', item.event, item.callback, scope);
} else {
eventmethod(scope.viewcontent, 'on', item.event, item.callback, scope, item.target);
} });
}, bind: function () {
_.wrapmethod(this._bind, 'onViewBeforeBind', 'onViewAfterBind', this);
}, _show: function () {
var $element = selectDom('#' + this.view.viewid); // @notation 需要剔除码?
// if ((!$element || $element.length === 0) && this.viewcontent) {
var $container = selectDom(this.container);
domImplement($container, 'html', false, [this.viewcontent]);
// } domImplement($element, 'show');
}, show: function () {
this.bind(); _.wrapmethod(this._show, 'onViewBeforeShow', 'onViewAfterShow', this);
}, _hide: function () {
var $element = selectDom('#' + this.view.viewid);
domImplement($element, 'hide');
}, hide: function () {
_.wrapmethod(this._hide, 'onViewBeforeHide', 'onViewAfterHide', this); this.forze();
}, _forze: function () {
var $element = selectDom('#' + this.view.viewid);
domImplement($element, 'off');
}, forze: function () {
_.wrapmethod(this._forze, 'onViewBeforeForzen', 'onViewAfterForzen', this);
}, _destory: function () {
var $element = selectDom('#' + this.view.viewid).remove();
domImplement($element, 'remove');
}, destory: function () {
_.wrapmethod(this._destory, 'onViewBeforeDestory', 'onViewAfterDestory', this);
}
});

underscore扩展

 (function () {

   // @description 全局可能用到的变量
var arr = [];
var slice = arr.slice; var method = method || {}; /**
* @description inherit方法,js的继承,默认为两个参数
* @param {function} supClass 可选,要继承的类
* @param {object} subProperty 被创建类的成员
* @return {function} 被创建的类
*/
method.inherit = function () { // @description 参数检测,该继承方法,只支持一个参数创建类,或者两个参数继承类
if (arguments.length === 0 || arguments.length > 2) throw '参数错误'; var parent = null; // @description 将参数转换为数组
var properties = slice.call(arguments); // @description 如果第一个参数为类(function),那么就将之取出
if (typeof properties[0] === 'function')
parent = properties.shift();
properties = properties[0]; // @description 创建新类用于返回
function klass() {
if (_.isFunction(this.initialize))
this.initialize.apply(this, arguments);
} klass.superclass = parent;
// parent.subclasses = []; if (parent) {
// @description 中间过渡类,防止parent的构造函数被执行
var subclass = function () { };
subclass.prototype = parent.prototype;
klass.prototype = new subclass();
// parent.subclasses.push(klass);
} var ancestor = klass.superclass && klass.superclass.prototype;
for (var k in properties) {
var value = properties[k]; //满足条件就重写
if (ancestor && typeof value == 'function') {
var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(',');
//只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
if (argslist[0] === '$super' && ancestor[k]) {
value = (function (methodName, fn) {
return function () {
var scope = this;
var args = [function () {
return ancestor[methodName].apply(scope, arguments);
} ];
return fn.apply(this, args.concat(slice.call(arguments)));
};
})(k, value);
}
} //此处对对象进行扩展,当前原型链已经存在该对象,便进行扩展
if (_.isObject(klass.prototype[k]) && _.isObject(value) && (typeof klass.prototype[k] != 'function' && typeof value != 'fuction')) {
//原型链是共享的,这里不好办
var temp = {};
_.extend(temp, klass.prototype[k]);
_.extend(temp, value);
klass.prototype[k] = temp;
} else {
klass.prototype[k] = value;
} } if (!klass.prototype.initialize)
klass.prototype.initialize = function () { }; klass.prototype.constructor = klass; return klass;
}; // @description 返回需要的函数
method.getNeedFn = function (key, scope) {
scope = scope || window;
if (_.isFunction(key)) return key;
if (_.isFunction(scope[key])) return scope[key];
return function () { };
}; method.callmethod = function (method, scope, params) {
scope = scope || this;
if (_.isFunction(method)) {
method.apply(scope, params);
return true;
} return false;
}; /**
* @description 在fn方法的前后通过键值设置两个传入的回调
* @param fn {function} 调用的方法
* @param beforeFnKey {string} 从context对象中获得的函数指针的键值,该函数在fn前执行
* @param afterFnKey {string} 从context对象中获得的函数指针的键值,该函数在fn后执行
* @param context {object} 执行环节的上下文
* @return {function}
*/
method.wrapmethod = method.insert = function (fn, beforeFnKey, afterFnKey, context) { var scope = context || this;
var action = _.wrap(fn, function (func) { _.callmethod(_.getNeedFn(beforeFnKey, scope), scope); func.call(scope); _.callmethod(_.getNeedFn(afterFnKey, scope), scope);
}); return _.callmethod(action, scope);
} _.extend(_, method); })(window);

简单alert框

首先我们来做一个简单的alert框,这个框在我们点击界面时候弹出一个提示,提示文字由文本框给出

第一步便是简单的HTML了

 <!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%; }
</style>
</head>
<body>
<article class="container">
</article>
<input type="text" id="addmsg" class="txt">
<button id="addbtn" class="btn">
show message</button>
<script type="text/underscore-template" id="template-alert">
<div class=" cui-alert" >
<div class="cui-pop-box">
<div class="cui-bd">
<p class="cui-error-tips"><%=content%></p>
<div class="cui-roller-btns">
<div class="cui-flexbd cui-btns-cancel"><%=cancel%></div>
<div class="cui-flexbd cui-btns-sure"><%=confirm%></div>
</div>
</div>
</div>
</div>
</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/mvc.js" type="text/javascript"></script>
<script type="text/javascript" src="ui.alert.js"></script>
</body>
</html>

因为该插件本身比较简单,不存在状态值便会,所以view定义如此即可

 var htmltemplate = $('#template-alert').html();

 var AlertView = _.inherit(Dalmatian.View, {
templateSet: {
0: htmltemplate
}, statusSet: {
STATUS_INIT: 0
}
});

Adapter也比较简单

var Adapter = _.inherit(Dalmatian.Adapter, {
parse: function (data) {
return data;
}
});

现在重点便是controller了

 var Controller = _.inherit(Dalmatian.ViewController, {
render: function () {
var data = this.adapter.viewmodel;
this.view.render(this.viewstatus, data);
}, set: function (options) {
this.adapter.datamodel.content = options.content;
this.adapter.notifyDataChanged();
}, events: {
"click .cui-btns-cancel": "cancelaction"
}, cancelaction: function () {
this.onCancelBtnClick();
}, attr: function (key, value) {
this[key] = value;
}
});

这里有个不一样的地方便是,这里有一个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')
}
});

然后我们写一段业务代码

 $('#addbtn').on('click', function (e) {
var content = $('#addmsg').val();
// adapter.datamodel.content = content;
// adapter.notifyDataChanged();
controller.set({ content: content });
controller.show();
});

基本完成我们的操作了

事实上,我对这段代码并不是十分满意,于是,我们这里做一次简单重构:

 var htmltemplate = $('#template-alert').html();

 var AlertView = _.inherit(Dalmatian.View, {
templateSet: {
0: htmltemplate
}, statusSet: {
STATUS_INIT: 0
}
}); var Adapter = _.inherit(Dalmatian.Adapter, {
parse: function (data) {
return data;
}
}); var Controller = _.inherit(Dalmatian.ViewController, {
//设置默认信息
_initialize: function () {
this.origindata = {
content: '',
confirm: '确定',
cancel: '取消'
}
}, initialize: function ($super, opts) {
this._initialize();
$super(opts);
this._init();
}, //基础数据处理
_init: function () {
this.adapter.format(this.origindata);
this.adapter.registerObserver(this);
this.viewstatus = this.view.statusSet.STATUS_INIT;
}, render: function () {
var data = this.adapter.viewmodel;
this.view.render(this.viewstatus, data);
}, set: function (options) {
_.extend(this.adapter.datamodel, options);
// this.adapter.datamodel.content = options.content;
this.adapter.notifyDataChanged();
}, events: {
"click .cui-btns-cancel": "cancelaction"
}, cancelaction: function () {
this.onCancelBtnClick();
}
}); var view = new AlertView()
var adapter = new Adapter(); var controller = new Controller({
view: view,
adapter: adapter,
container: '.container',
onCancelBtnClick: function () {
alert('cancel 2')
}
}); $('#addbtn').on('click', function (e) {
var content = $('#addmsg').val();
// adapter.datamodel.content = content;
// adapter.notifyDataChanged();
controller.set({ content: content, confirm: '确定1' });
controller.show();
});

这个例子结束后,我们来写另一个例子

todolist

Backbone有一个todoList,我们这里也来写一个阉割版的,因为若是今天全部时间来写这个,后面就没法继续了

这个例子事实上也比较简单了,首先看我们的HTML结构

 <!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">
</head>
<body>
<article class="container">
</article>
<script type="text/underscore-template" id="template-todolist">
<section class="row">
<div class="col-xs-9">
<form action="">
<legend>To Do List -- Input</legend>
<input type="text" placeholer="ToDoList" id="todoinput">
<button class="btn btn-primary" data-action="add">添加</button>
</form>
<ul id="todolist">
<%_.each(list, function(item){%>
<li><%=item.content %></li>
<%})%>
</ul>
</div>
</section>
</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/mvc.js" type="text/javascript"></script>
<script type="text/javascript" src="demo.js"></script>
</body>
</html>

其次是我们的js

 var htmltemplate = $('#template-todolist').html();

 var view = new Dalmatian.View({
templateSet: {
0:htmltemplate
},
statusSet: {
STATUS_INIT: 0
}
}); var Adapter = _.inherit(Dalmatian.Adapter, {
parse: function (origindata) {
return origindata;
}
}); var Controller = _.inherit(Dalmatian.ViewController, {
render: function() {
console.log('controller-render')
var data = this.adapter.viewmodel;
this.view.render(this.viewstatus, data);
}, events: {
'click button': 'action'
}, action: function(e) {
e.preventDefault(); var target = $(e.currentTarget).attr('data-action');
var strategy = {
'add': function(e) {
var value = $('#todoinput').val();
this.adapter.datamodel.list.push({ content: value });
// this.adapter.parse(this.adapter.datamodel);
this.adapter.notifyDataChanged();
}
} strategy[target].apply(this, [e]);
}
}) 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
}
}); controller.show();

阶段总结

MVC的学习暂时到这里,我们下面继续日历的的东西,虽然我与老大商量后形成了一些自己觉得不错的东西,但是真正使用过程中还是发现一些问题

① 第一个我认为比较大的问题是viewController中的代码,比如

var controller = new Controller({
view: view,
adapter: new Adapter(),
container: '.container',
onViewBeforeCreate: function () {
this.adapter.format({
list: []
});
this.adapter.registerObserver(this);
this.viewstatus = this.view.statusSet.STATUS_INIT
}
});

以及

var controller = new Controller({
view: view,
adapter: adapter,
container: '.container',
onCancelBtnClick: function () {
alert('cancel 2')
}
});

事实上这些代码不应该存在于此,真实情况下我所构想的viewController不会在实例化时候还有如此多的业务相关信息,viewController在实例化时候只应该包含系统级的东西

比如Controller释放出来的接口,比如全局消息监听什么的,显然我们上面代码中的做法是有问题的,这些东西事实上应该在定义ViewController类时,在继承处得到处理

不应该在实例化时候处理,我们viewController实例化时候应该有更重要的使命,这些留待下面解决

上面要表达的意思是,事实上我们ViewController是最后继承下来是需要干业务的事情,所以他应该在几个事件点将要干的事情做完,比如TodoList应该是这样的

 var htmltemplate = $('#template-todolist').html();

 var Adapter = _.inherit(Dalmatian.Adapter, {
parse: function (origindata) {
return origindata;
}
}); var Controller = _.inherit(Dalmatian.ViewController, { //设置默认信息
_initialize: function () {
this.view = new Dalmatian.View({
templateSet: {
0: htmltemplate
},
statusSet: {
STATUS_INIT: 0
}
});
this.adapter = new Adapter(); }, initialize: function ($super, opts) {
this._initialize();
$super(opts);
}, render: function () {
console.log('controller-render')
var data = this.adapter.viewmodel;
this.view.render(this.viewstatus, data);
}, container: '.container',
onViewBeforeCreate: function () {
this.adapter.format({
list: []
});
this.adapter.registerObserver(this);
this.viewstatus = this.view.statusSet.STATUS_INIT
}, events: {
'click button': 'action'
}, action: function (e) {
e.preventDefault(); var target = $(e.currentTarget).attr('data-action');
var strategy = {
'add': function (e) {
var value = $('#todoinput').val();
this.adapter.datamodel.list.push({ content: value });
// this.adapter.parse(this.adapter.datamodel);
this.adapter.notifyDataChanged();
}
} strategy[target].apply(this, [e]);
}
}) var controller = new Controller(); controller.show();

这样的话,业务应该的代码事实上写到了类的几个事件点中了,这些会在实例化时不同的状态被触发,所以根本不必在实例化时做任何操作

实例化时候应该有他的作用,因为继承到这一层的时候,该业务类便专注于处理这个业务了

简单日历

上次,我们的日历基本都成型了,今天我们便根据前面的想法为他做一次封装......

PS:想想有点很傻很天真的感觉......现在的问题是要将原来一个基本算总体的东西,分成三个部分,说实话这样封装的结构首先是让人阅读上稍微困难了

首先仍然是定义view的事情,首先一来就遇到个比较烦的地方,因为之前我们将模板分的很细:

① 星期显示模板

② 月模板

③ 日模板

所以,我们这里便不太好区分,而且还有一定嵌套关系,这里小钗费了一点功夫......

由这块的操作,我们甚至可以调整原来view的逻辑,优化由此一点一点慢慢就开始了......

 <!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 class="container">
</article>
<script type="text/template" id="template-calendar">
<ul class="cui_week">
<% var i = 0, day = 0; %>
<%for(day = 0; day < 7; day++) { %>
<li>
<%=weekDayItemTmpt[day] %></li>
<%} %>
</ul> <ul class="cui_calendar">
<% for(i = 0; i < beginWeek; i++) { %>
<li class="cui_invalid"></li>
<% } %>
<% for(i = 0; i < days; i++) { %>
<% day = i + 1; %>
<li class="cui_calendar_item" data-date="<%=year%>-<%=month + 1%>-<%=day%>"><%=day %></li>
<% } %>
</ul>
</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/mvc.js" type="text/javascript"></script>
<script type="text/javascript">
var tmpt = $('#template-calendar').html(); var CalendarView = _.inherit(Dalmatian.View, {
templateSet: {
0: tmpt
}, statusSet: {
STATUS_INIT: 0
}
}); var CalendarAdapter = _.inherit(Dalmatian.Adapter, {
_initialize: function ($super) {
$super(); //默认显示方案,可以根据参数修改
//任意一个model发生改变皆会引起update
this.weekDayItemTmpt = ['日', '一', '二', '三', '四', '五', '六'];
}, //该次重新,viewmodel的数据完全来源与parse中多定义
parse: function (data) {
return _.extend({
weekDayItemTmpt: this.weekDayItemTmpt
}, data);
}
}); var CalendarController = _.inherit(Dalmatian.ViewController, { _initialize: function () {
this.view = new CalendarView();
this.adapter = new CalendarAdapter(); //默认业务数据
this.dateObj = new Date();
this.container = '.container'; var s = '';
}, initialize: function ($super, opts) {
this._initialize();
$super(opts);
}, onViewBeforeCreate: function () { //使用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; var s = '';
}, render: function () {
//该操作可封装
var data = this.adapter.viewmodel;
this.view.render(this.viewstatus, data);
}, //根据传入年月,返回该月相关数据
_getMonthData: function (year, month) {
this.date = new Date(year, month);
var d = new Date(year, month);
//description 获取天数
var days = dateUtil.getDaysOfMonth(d);
//description 获取那个月第一天时星期几
var _beginWeek = dateUtil.getBeginDayOfMouth(d);
return {
year: d.getFullYear(),
month: d.getMonth(),
beginWeek: _beginWeek,
days: days
};
}
}); var calendar = new CalendarController();
calendar.show(); </script>
</body>
</html>

首次调整后,大概的东西出来了,这样一次操作后就会发现之前定义的MVC一些不合理的地方

① 操作Adapter有parse与format两个地方,我们用着用着就会分不清,应该只对外暴露一个借口

② Controller处操作Adapter以及view也会有多个地方事实上有一些必定会发生的流程我们应该封装起来,类似:

//使用adpter之前必须注册监听以及格式化viewModel,此操作应该封装起来
this.adapter.registerObserver(this);
this.adapter.format(this._getMonthData(this.dateObj.getFullYear(), this.dateObj.getMonth())); //view显示之前必定会给予状态,此应该封装
this.viewstatus = this.view.statusSet.STATUS_INIT;

③ 整个MVC的逻辑还是有一些不太清晰的地方,这个留待后续调整

这个时候我们将之前的一些借口加入进来,比如我们的handleDay

handleDay: function (dateStr, fn) {
if (dateUtil.isDate(dateStr)) dateStr = dateUtil.format(dateStr, 'Y-m-d');
var el = this.viewcontent.find('[data-date="' + dateStr + '"]'); if (typeof fn == 'function') fn(el, dateUtil.parse(dateStr, 'y-m-d'), this); }
var calendar = new CalendarController();
calendar.show(); calendar.handleDay(new Date(), function (el, date, calendar) {
el.html('今天');
});

现在如果有事件绑定的话,便注册至viewController即可,我这里便暂时结束了

结语

通过今天的学习,我将与我老大研究出来的MVC的东东搞了出来,事实证明还是需要有一些优化的......

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

【UI插件】简单的日历插件(下)—— 学习MVC思想的更多相关文章

  1. 【UI插件】开发一个简单日历插件(上)

    前言 最近开始整理我们的单页应用框架了,虽然可能比不上MVVM模式的开发效率,也可能没有Backbone框架模块清晰,但是好歹也是自己开发出来 而且也用于了这么多频道的东西,如果没有总结,没有整理,没 ...

  2. 原生js日历选择器,学习js面向对象开发日历插件

    在web开发过程中经常会碰到需要选择日期的功能,一般的操作都是在文本框点击,然后弹出日历选择框,直接选择日期就可以在文本框显示选择的日期.开发好之后给用户使用是很方便,但如果每一个日历选择器都要临时开 ...

  3. 很全的vue插件汇总,赶紧收藏下(转)

    Vue是一个构建数据驱动的 web 界面的渐进式框架.Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件特别整理了常用的vue插件,来了个大汇总,方便查找使用,便于工作 ...

  4. 日历插件FullCalendar应用:(二)数据增删改

    接上一篇 日历插件FullCalendar应用:(一)数据展现. 这一篇主要讲使用fullcalendar插件如何做数据的增删改,用到了art.dialog web对话框组件,上一篇用到的webFor ...

  5. 给开发者准备的 10 款最好的 jQuery 日历插件[转]

    这篇文章介绍的是 10 款最棒而且又很有用的 jQuery 日历插件,允许开发者们把这些漂亮的日历插件结合到自己的网站中.这些日历插件易用性都很强,轻轻松松的就可以把漂亮的日历插件装饰到你的网站了.希 ...

  6. 完全原生javascript简约日历插件,js、html

    效果图: 效果如图所示,尽管看上去并不是很美观,但是,基本上的功能还是已经完成了,码了一天多的时间,权当做复习一下js吧. 整个做下来差不多码了500多行代码~其实只是很多的样式也包括了在其中了,虽然 ...

  7. 仿iphone日历插件(beta)

    前言 小伙伴们好,很久不见了.最近工作进入正常期了,所以慢慢的悠闲的时间久没有了,所以不能每天水一篇了. 最近也在听师傅(http://home.cnblogs.com/u/aaronjs/)的教导开 ...

  8. 日历插件My97DatePicker的使用

    在开发过程中,我们会经常遇到让用户输入日期的表单,这类表单处理起来也不是太繁琐,就是简单的字符串和日期之间的转换.但是,如果用户不按照已设定的日期格式进行输入,必定会造成不必要的麻烦.为了更好的处理这 ...

  9. 基于jQuery的日历插件

    上个星期看到同事做一个有关日历提醒功能的需求,为了找个插件也是费了不少心思,然后刚好有时间就试着写了一个简单demo 来看下最终效果图吧: 是长得丑了一点,不要吐槽我-.- 首先来说说这个日历主要的制 ...

随机推荐

  1. Angularjs1培训

    Angularjs1培训: angularjs解决什么问题? 从无穷无尽的DOM操作中解放出来,专注于业务逻辑,DOM操作不叫业务逻辑,那是试图呈现. 组件化,模块化为构建大型项目铺平道路,模块发开发 ...

  2. iOS中通讯录的开发

    通讯录开发主要是获取用户手机中的联系人,进而可以在应用中添加好友 一 .如何访问通讯录 (1)在iOS9之前,有两个框架可以访问用户的通讯录 AddressBookUI.framework: 提供了联 ...

  3. C语言static

    1. static 变量 静态变量的类型说明符是static. 静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量. 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 ...

  4. Disk IO Performance

    一,使用 Performance counter 监控Disk IO问题 1,Physical Disk vs. Logical Disk Windows可以在一个Physical Disk上划出若干 ...

  5. WPF 子窗体关闭时显示父窗体

    这个问题纠结了两天,今天在一个朋友的帮助下,解决了,其实很简单,但是可能作为新手,接触WPF时间还是短,因此作为一个问题困扰了我. 父窗体部分代码 private void EditInformati ...

  6. Android 设置对话框全屏

    1.在styles.xml中添加一个style: <style name="Dialog_Fullscreen"> <item name="androi ...

  7. ajax实现上传文件

      1.html部分 <input style="width: 280px" type="file" name="upLoadProjectPl ...

  8. 新作《ASP.NET Web API 2框架揭秘》正式出版

    我觉得大部分人都是“眼球动物“,他们关注的往往都是目光所及的东西.对于很多软件从业者来说,他们对看得见(具有UI界面)的应用抱有极大的热忱,但是对背后支撑整个应用的服务却显得较为冷漠.如果我们将整个“ ...

  9. android给View设置上下左右边框

    给View控件设置边框,可以动态设置上下左右.通过布局文件就能搞定 1.在drawable文件夹下新建一个shape_main_list_bg.xml文件 <layer-list xmlns:a ...

  10. 2、Redis入门介绍

    1.什么是Redis Redis:REmote DIctionary Server(远程字典服务器) 是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数 ...