从第2部分的实现思路,要实现的类有:FileUploadBaseView和ImageUploadView,前者是后者的基类。同时考虑到要给组件提供事件管理的功能,所以要用到上一篇博客的eventBase.js,FileUploadBaseView得继承该库的EventBase组件;考虑到要有类的定义和继承,还要用到之前写的继承库class.js来定义组件以及组件的继承关系。相关组件的继承关系为:ImageUploadView extend FileUploadBaseView extend EventBase。
(注:以下相关代码中模块化用的是seajs。)
FileUploadBaseView所做的事情有:
1)定义通用的option以及通用的事件管理
在该组件的DEFAULTS配置中可以看到所有的通用option和通用事件的定义:
增加和
删除
onBeforeRender: $.noop,//对应render.before事件,在render
方法调用前触发
onRender: $.noop,//对应render.after事件,在render
方法调用后触发
onBeforeAppend: $.noop,//对应append.before事件,在append
方法调用前触发
onAppend: $.noop,//对应append.after事件,在append
方法调用后触发
onBeforeDelItem: $.noop,//对应delItem.before事件,在delItem
方法调用前触发
onDelItem: $.noop //对应delItem.after事件,在delItem
方法调用后触发
};
在该组件的init方法中可以看到对通用option和事件管理的初始化逻辑:
调用
父类EventBase的init
方法
this.base(element);
//实例
属性
var opts = this.options = this.getOptions(options);
this.data = resolveData(opts.data);
delete opts.data;
this.sizeLimit = opts.sizeLimit;
this.readOnly = opts.readOnly;
//绑定事件
this.on('render.before',$.proxy(opts.onBeforeRender,this));
this.on('render.after',$.proxy(opts.onRender,this));
this.on('append.before',$.proxy(opts.onBeforeAppend,this));
this.on('append.after',$.proxy(opts.onAppend,this));
this.on('delItem.before',$.proxy(opts.onBeforeDelItem,this));
this.on('delItem.after',$.proxy(opts.onDelItem,this));
},
2)定义组件的行为,预留可供子类实现的接口:
方法,只需要重写_render
方法
* 当
调用子类的render
方法时
调用的是
父类的render
方法
* 但是执行到_render
方法时,
调用的是子类的_render
方法
* 这样就能把before跟after事件的触发操作统一起来
*/
var e;
this.trigger(e = $.Event('render.before'));
if (e.isDefaultPrevented()) return;
this._render();
this.trigger($.Event('render.after'));
},//子类需实现_Render
方法
_render: function () {
},append: function (item) {
var e;
if (!item) return;
item = resolveDataItem(item);
this.trigger(e = $.Event('append.before'),item);
if (e.isDefaultPrevented()) return;
this.data.push(item);
this._append(item);
this.trigger($.Event('append.after'),item);
},//子类需实现_append
方法
_append: function (data) {
},delItem: function (uuid) {
var e,item = this.getDataItem(uuid);
if (!item) return;
this.trigger(e = $.Event('delItem.before'),item);
if (e.isDefaultPrevented()) return;
this.data.splice(this.getDataItemIndex(uuid),1);
this._delItem(item);
this.trigger($.Event('delItem.after'),//子类需实现_delItem
方法
_delItem: function (data) {
}
为了统一处理行为前后的事件派发逻辑,将render,delItem的主要逻辑抽出来成为需被子类实现的方法_render,_append和_delItem。当调用子类的render方法时,调用的实际上父类的方法,但是当父类执行到_render方法时,执行的就是子类的方法,另外两个方法也是类似的处理。需要注意的是子类不能去覆盖render,delItem三个方法,否则就得自己去处理相关事件的触发逻辑。
FileUploadBaseView整体实现如下:
方法
调用前触发
onDelItem: $.noop //对应delItem.after事件,在delItem
方法调用后触发
};
/**
* 数据处理,给data的每条记录都
添加一个_uuid的
属性,方便查找
*/
function resolveData(data) {
var time = new Date().getTime();
return $.map(data,function (d) {
return resolveDataItem(d,time);
});
}
function resolveDataItem(data,time) {
time = time || new Date().getTime();
data._uuid = '_uuid' + time + Math.floor(Math.random() * 100000);
return data;
}
var FileUploadBaseView = Class({
instanceMembers: {
init: function (element,getOptions: function (options) {
return $.extend({},this.getDefaults(),options);
},getDefaults: function () {
return DEFAULTS;
},getDataItem: function (uuid) {
//根据uuid
获取dateItem
return this.data.filter(function (item) {
return item._uuid === uuid;
})[0];
},getDataItemIndex: function (uuid) {
var ret;
this.data.forEach(function (item,i) {
item._uuid === uuid && (ret = i);
});
return ret;
},render: function () {
/**
* render是一个模板,子类不需要重写render
方法,只需要重写_render
方法
* 当
调用子类的render
方法时
调用的是
父类的render
方法
* 但是执行到_render
方法时,
调用的是子类的_render
方法
* 这样就能把before跟after事件的触发操作统一起来
*/
var e;
this.trigger(e = $.Event('render.before'));
if (e.isDefaultPrevented()) return;
this._render();
this.trigger($.Event('render.after'));
},//子类需实现_delItem
方法
_delItem: function (data) {
}
},extend: EventBase,staticMembers: {
DEFAULTS: DEFAULTS
}
});
return FileUploadBaseView;
});
ImageUploadView 的实现就比较简单了,跟填空差不多,有几个点需要说明一下:
1)这个类的DEFAULTS需要扩展父类的DEFAULTS,以便添加这个子类的默认options,同时还保留父类默认options的定义;根据静态页面结构,新增了一个onAppendClick事件,外部可在这个事件中调用文件上传组件的相关方法:
父类的默认DEFAULTS
var DEFAULTS = $.extend({},FileUploadBaseView.DEFAULTS,{
onAppendClick: $.noop //点击
上传按钮时候的回调
});
2)在init方法中,需要调用父类的init方法,才能完成那些通用的逻辑处理;同时在init的最后还得手动调用一下render方法,以便在组件实例化之后就能看到效果:
其它实现纯粹是业务逻辑实现,跟第2部分的需求密切相关。
ImageUploadView的整体实现如下:
父类的默认DEFAULTS
var DEFAULTS = $.extend({},{
onAppendClick: $.noop //点击
上传按钮时候的回调
});
var ImageUploadView = Class({
instanceMembers: {
init: function (element,options) {
var $element = this.$element = $(element);
var opts = this.getOptions(options);
//
调用父类的init
方法完成options
获取,data解析以及通用事件的监听处理
this.base(this.$element,options);
//
添加上传和
删除的监听器及触发处理
if (!this.readOnly) {
var that = this;
that.on('appendClick',$.proxy(opts.onAppendClick,this));
$element.on('click.append','.view-act-add',function (e) {
e.preventDefault();
that.trigger('appendClick');
});
$element.on('click.remove','.view-act-del',function (e) {
var $this = $(e.currentTarget);
that.delItem($this.data('uuid'));
e.preventDefault();
});
}
this.render();
},_setItemAddHtml: function () {
this.$element.prepend($('
'));
},_clearItemAddHtml: function ($itemAddLi) {
$itemAddLi.remove();
},_render: function () {
var html = [],that = this;
//如果不是只读的状态,并且还没有达到上传限制的话,就添加上传按钮
if (!(this.readOnly || (this.sizeLimit && this.sizeLimit <= this.data.length))) {
this._setItemAddHtml();
}
this.data.forEach(function (item) {
html.push(that._getItemRenderHtml(item))
});
this.$element.append($(html.join('')));
},_getItemRenderHtml: function (item) {
return [
'
',''
].join('');
},_dealWithSizeLimit: function () {
if (this.sizeLimit) {
var $itemAddLi = this.$element.find('li.view-item-add');
//如果已经达到
上传限制的话,就移除
上传按钮
if (this.sizeLimit && this.sizeLimit <= this.data.length && $itemAddLi.length) {
this._clearItemAddHtml($itemAddLi);
} else if (!$itemAddLi.length) {
this._setItemAddHtml();
}
}
},_append: function (data) {
this.$element.append($(this._getItemRenderHtml(data)));
this._dealWithSizeLimit();
},_delItem: function (data) {
$('#' + data._uuid).remove();
this._dealWithSizeLimit();
}
},extend: FileUploadBaseView
});
return ImageUploadView;
});
4. 演示说明