Heim > Web-Frontend > js-Tutorial > Die Idee der Trennung und Vererbung realisiert die Vorschaufunktion nach dem Hochladen des Bildes: ImageUploadView_javascript-Technik

Die Idee der Trennung und Vererbung realisiert die Vorschaufunktion nach dem Hochladen des Bildes: ImageUploadView_javascript-Technik

WBOY
Freigeben: 2016-05-16 15:06:13
Original
1447 Leute haben es durchsucht

Was in diesem Artikel vorgestellt wird, ist die Implementierungsidee, kleine Bildvorschauen direkt auf der Seite zu generieren, nachdem gängige Bilder auf Webseiten hochgeladen wurden. Da diese Funktion eine gewisse Anwendbarkeit hat, ist die relevante Logik tatsächlich in eine ImageUploadView-Komponente gekapselt Der Effekt kann im Git-Rendering im nächsten Absatz betrachtet werden. Bei der Implementierung dieser Komponente haben wir die in den vorherigen Blogs eingeführten relevanten Inhalte verwendet, z. B. die Vererbungsbibliothek class.js und die Ereignisverwaltungsbibliothek eventBase.js für jede Komponente, und auch unsere eigene Trennung von Verantwortlichkeiten, Leistung und integriert Einige Gedanken zu beiden Aspekten können gerne gelesen und ausgetauscht werden.

Demo-Effekt:

Hinweis: Da der Code für die Demonstration vollständig statisch ist, wird die Datei-Upload-Komponente mit setTimeout simuliert. Die Aufrufmethode ist jedoch genau die gleiche wie die Upload-Komponente, die ich in der tatsächlichen Arbeit verwende, also der Code für den Demonstrationseffekt wird den tatsächlichen Funktionsanforderungen vollständig gerecht.

In Anlehnung an die Ideen meines vorherigen Blogs möchte ich zunächst die Anforderungen für diese Upload-Vorschaufunktion vorstellen.

1. Bedarfsanalyse

Gemäß den vorherigen Demonstrationsdarstellungen lauten die Analyseanforderungen wie folgt:

1) Zunächst wird im Upload-Bereich nur ein anklickbarer Upload-Button angezeigt. Wenn auf den Button geklickt wird, wird das erfolgreich hochgeladene Bild im Vorschaubereich auf der Rückseite angezeigt

2) Nachdem das hochgeladene Bild zum Vorschaubereich hinzugefügt wurde, kann es durch Drücken der Löschtaste
entfernt werden

3) Wenn die Gesamtzahl der hochgeladenen Bilder ein bestimmtes Limit erreicht, beispielsweise beträgt das Upload-Limit in der Demo 4, wird die Schaltfläche zum Hochladen entfernt

4) Wenn die Gesamtzahl der hochgeladenen Bilder eine bestimmte Grenze erreicht und ein Bild durch einen Löschvorgang entfernt wird, muss die Schaltfläche zum Hochladen erneut angezeigt werden.

Die oben genannten Anforderungen sind erfahrungsgemäß wie folgt zu analysieren:

1) Wenn sich die Seite im Bearbeitungsstatus befindet, was der aus der Datenbank abgefragte Status ist, müssen die Bilder zunächst angezeigt werden und der Upload muss auf der Länge basieren Bildliste gefunden. Begrenzen, um zu steuern, ob der Upload-Button angezeigt wird;

2) Wenn sich die aktuelle Seite in einem Zustand befindet, der nur angezeigt und nicht geändert werden kann, müssen zunächst der Upload-Button und der Lösch-Button entfernt werden.

Da die Anforderungsanalyse nun abgeschlossen ist, möchte ich meine Umsetzungsideen erläutern.

2. Umsetzungsideen

Da es sich um eine Formularseite handelt, benötigen Sie auf jeden Fall ein Textfeld, wenn Sie das Bild nach dem Hochladen an das Backend senden möchten. Daher habe ich dieses Textfeld bei der Erstellung der statischen Seite beim Erstellen des neuen Bilds berücksichtigt hochgeladen wird. Auch nach dem Löschen des Bildes müssen Sie den Wert dieses Textfelds ändern. Beim Erstellen statischer Seiten war die Struktur dieses Teils wie folgt:

<div class="holy-layout-am appForm-group appForm-group-img-upload clearfix">
<label class="holy-layout-al">法人身份证电子版</label>
<div class="holy-layout-m">
<input id="legalPersonIDPic-input" name="legalPersonIDPic" class="form-control form-field"
type="hidden">
<ul id="legalPersonIDPic-view" class="image-upload-view clearfix">
<li class="view-item-add"><a class="view-act-add" href="javascript:;" title="点击上传">+</a>
</li>
</ul>
<p class="img-upload-msg">
请确保图片清晰,文字可辨
<a href="#" title="查看示例"><i class="fa fa-question-circle"></i> 查看示例</a>
</p>
</div>
</div>
Nach dem Login kopieren

从这个结构还可以看出,我把整个上传区域都放在一个ul里面,然后把ul的第一个li作为上传按钮来使用。为了完成这个功能,我们主要的任务是:上传及上传后的回调,新增或删除图片预览以及文本域值的管理。从这一点,结合职责分离的思想,这个功能至少需要三个组件,一个负责文件上传,一个负责图片预览的管理,一个负责文本域值的管理。千万不能把这三个功能,两两或者全部都封装在一起,那样的话功能耦合太强,写出来的组件可扩展性可重用性不高。如果这三个组件之间需要交互,我们只要借助回调函数或者发布-订阅模式定义它们给外部调用的接口即可。

不过文本域值的管理本身就很简单,写不写成组件都关系不大,但是至少函数级别的封装是得有的;文件上传组件虽然不是本文的重点,但是网上有很多现成的开源插件,比如webuploader,不管是直接用还是做二次封装都可以应用进来;图片预览的功能是本文的核心内容,ImageUploadView这个组件就是对它的封装,从需求来看,这个组件有语义的实例方法无非就是三个,分别是render, append, delItem,其中render用来在初始化完成之后显示初始的预览列表,append用来在上传成功后添加新的图片预览,delItem用来删除已有的图片预览,按照这个基本思路,我们只需要再结合需求和组件开发的经验为它设计好options和事件即可。

从前面的需求我们发现,这个ImageUploadView组件的render会受到页面状态的影响,当页面为查看模式时,这个组件不能做上传和删除的操作,所以可以考虑给它加一个readonly的option。同时它的上传和删除操作还会影响到上传按钮的UI逻辑,这个跟上传限制有关系,为了灵活性,也得把上传限制作为一个option。从前一段提到的三个实例方法来说,按照自己以前定义事件的经验,一般一个实例方法会定义一对事件,就像bootstrap的插件的做法一样,比如render方法,可以定义一个render.before,这个事件在render的主要逻辑执行前触发,如果外部监听器调用了这个事件的preventDefault()方法,那么render的主要逻辑都不会执行;还有一个render.after事件,这个事件在render的主要逻辑执行后触发。这种成对定义事件的好处是,既给外部提供扩展组件功能的方法,又能增加组件默认行为的管理。

最后从我之前的工作经验来说,除了有上传图片进行预览这样的功能,我曾经还做过上传视频,上传音频,上传普通文档等类似的,所以这一次碰到这个功能的时候我就觉得应该把这些功能里面相似的东西抽取出来,作为一个基类,图片上传,视频上传等分别继承这个基类去实现各自的逻辑。这个基类还有一个好处,就是能够让那些通用的逻辑完全与HTML结构分离,在这个基类里面只做一些通用的事情,比如options与组件行为(render, append, delItem)的定义,以及通用事件的监听和触发,它只要留有固定的接口留给子类来实现即可。在后面的实现中,我定义了一个FileUploadBaseView组件来完成这个基类的功能,这个基类不包含任何html或css处理的逻辑,它只是抽象了我们要完成的功能,不处理任何业务逻辑。根据业务逻辑实现的子类会受html结构的限制,所以子类的适用范围小;而基类因为做到了与html结构完全分离,所以有更大的适用范围。

3. 实现细节

从第2部分的实现思路,要实现的类有:FileUploadBaseView和ImageUploadView,前者是后者的基类。同时考虑到要给组件提供事件管理的功能,所以要用到上一篇博客的eventBase.js,FileUploadBaseView得继承该库的EventBase组件;考虑到要有类的定义和继承,还要用到之前写的继承库class.js来定义组件以及组件的继承关系。相关组件的继承关系为:ImageUploadView extend FileUploadBaseView extend EventBase。

(注:以下相关代码中模块化用的是seajs。)

FileUploadBaseView所做的事情有:

1)定义通用的option以及通用的事件管理

在该组件的DEFAULTS配置中可以看到所有的通用option和通用事件的定义:

var DEFAULTS = {
data: [], //要展示的数据列表,列表元素必须是object类型的,如[{url: 'xxx.png'},{url: 'yyyy.png'}]
sizeLimit: 0, //用来限制BaseView中的展示的元素个数,为0表示不限制
readonly: false, //用来控制BaseView中的元素是否允许增加和删除
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方法调用后触发
};
Nach dem Login kopieren

在该组件的init方法中可以看到对通用option和事件管理的初始化逻辑:

init: function (element, options) {
//通过this.base调用父类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));
},
Nach dem Login kopieren

2)定义组件的行为,预留可供子类实现的接口:

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'));
},
//子类需实现_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'), item);
},
//子类需实现_delItem方法
_delItem: function (data) {
}
Nach dem Login kopieren

为了统一处理行为前后的事件派发逻辑,将render, append ,delItem的主要逻辑抽出来成为需被子类实现的方法_render, _append和_delItem。当调用子类的render方法时,调用的实际上父类的方法,但是当父类执行到_render方法时,执行的就是子类的方法,另外两个方法也是类似的处理。需要注意的是子类不能去覆盖render, append ,delItem三个方法,否则就得自己去处理相关事件的触发逻辑。

FileUploadBaseView整体实现如下:

define(function (require, exports, module) {
var $ = require('jquery');
var Class = require('mod/class');
var EventBase = require('mod/eventBase');
var DEFAULTS = {
data: [], //要展示的数据列表,列表元素必须是object类型的,如[{url: 'xxx.png'},{url: 'yyyy.png'}]
sizeLimit: 0, //用来限制BaseView中的展示的元素个数,为0表示不限制
readonly: false, //用来控制BaseView中的元素是否允许增加和删除
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方法调用后触发
};
/**
* 数据处理,给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, options) {
//通过this.base调用父类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));
},
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'));
},
//子类需实现_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'), item);
},
//子类需实现_delItem方法
_delItem: function (data) {
}
},
extend: EventBase,
staticMembers: {
DEFAULTS: DEFAULTS
}
});
return FileUploadBaseView;
});
Nach dem Login kopieren

ImageUploadView 的实现就比较简单了,跟填空差不多,有几个点需要说明一下:

1)这个类的DEFAULTS需要扩展父类的DEFAULTS,以便添加这个子类的默认options,同时还保留父类默认options的定义;根据静态页面结构,新增了一个onAppendClick事件,外部可在这个事件中调用文件上传组件的相关方法:

//继承并扩展父类的默认DEFAULTS
var DEFAULTS = $.extend({}, FileUploadBaseView.DEFAULTS, {
onAppendClick: $.noop //点击上传按钮时候的回调
});
Nach dem Login kopieren

2)在init方法中,需要调用父类的init方法,才能完成那些通用的逻辑处理;同时在init的最后还得手动调用一下render方法,以便在组件实例化之后就能看到效果:

其它实现纯粹是业务逻辑实现,跟第2部分的需求密切相关。

ImageUploadView的整体实现如下:

define(function (require, exports, module) {
var $ = require('jquery');
var Class = require('mod/class');
var FileUploadBaseView = require('mod/fileUploadBaseView');
//继承并扩展父类的默认DEFAULTS
var DEFAULTS = $.extend({}, FileUploadBaseView.DEFAULTS, {
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();
},
getDefaults: function () {
return DEFAULTS;
},
_setItemAddHtml: function () {
this.$element.prepend($('<li class="view-item-add"><a class="view-act-add" href="javascript:;" title="点击上传">+</a></li>'));
},
_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 [
'<li id="',
item._uuid,
'"><a class="view-act-preview" href="javascript:;"><img alt="" src="',
item.url,
'">',
this.readOnly &#63; '' : '<span class="view-act-del" data-uuid="',
item._uuid,
'">删除</span>',
'</a></li>'
].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;
});
Nach dem Login kopieren

4. 演示说明

演示的项目结构为:

框起来的就是演示的核心代码。其中fileUploadBaserView.js和imageUploadView.js是前面实现的两个核心组件。fileUploader.js是用来模拟上传组件的,它的实例有一个onSuccess的回调,表示上传成功;还有一个openChooseFileWin用来模拟真实的打开选择文件窗口并上传的这个过程:

define(function(require, exports, module) {
return function() {
var imgList = ['../img/1.jpg','../img/2.jpg','../img/3.jpg','../img/4.jpg'], i = 0;
var that = this;
that.onSuccess = function(uploadValue){}
this.openChooseFileWin = function(){
setTimeout(function(){
that.onSuccess(imgList[i++]);
if(i == imgList.length) {
i = 0;
}
},1000);
}
}
});
Nach dem Login kopieren

app/regist.js是演示页面的逻辑代码,关键的部分已用注释进行说明:

define(function (require, exports, module) {
var $ = require('jquery');
var ImageUploadView = require('mod/imageUploadView');
var FileUploader = require('mod/fileUploader');//这是用异步任务模拟的文件上传组件
//$legalPersonIDPic,用来存储已上传的文件信息,上传组件上传成功之后以及ImageUploadView组件删除某个item之后会对$legalPersonIDPic的值产生影响
var $legalPersonIDPic = $('#legalPersonIDPic-input'),
data = JSON.parse($legalPersonIDPic.val() || '[]');//data是初始值,比如当前页面有可能是从数据库加载的,需要用ImageUploadView组件呈现出来
//在文件上传成功之后,将刚上传的文件保存到$legalPersonIDPic的value中
//$legalPersonIDPic以json字符串的形式存储
var appendImageInputValue = function ($input, item) {
var value = JSON.parse($input.val() || '[]');
value.push(item);
$input.val(JSON.stringify(value));
};
//当调用ImageUploadView组件删除某个item之后,要同步把$legalPersonIDPic中已存储的信息清掉
var removeImageInputValue = function ($input, uuid) {
var value = JSON.parse($input.val() || '[]'), index;
value.forEach(function (item, i) {
if (item._uuid === uuid) {
index = i;
}
});
value.splice(index, 1);
$input.val(JSON.stringify(value));
};
var fileUploader = new FileUploader();
fileUploader.onSuccess = function (uploadValue) {
var item = {url: uploadValue};
legalPersonIDPicView.append(item);
appendImageInputValue($legalPersonIDPic, item);
};
var legalPersonIDPicView = new ImageUploadView('#legalPersonIDPic-view', {
data: data,
sizeLimit: 4,
onAppendClick: function () {
//打开选择文件的窗口
fileUploader.openChooseFileWin();
},
onDelItem: function (data) {
removeImageInputValue($legalPersonIDPic, data._uuid);
}
});
});
Nach dem Login kopieren

5. 本文总结

ImageUploadView这个组件最终实现起来并不难,但是我也花了不少时间去琢磨它及其它父类的实现方法,大部分时间都花在对职责分离和行为分离的抽象过程中。我在本文表达的关于这两方面编程思想的观点也只是自己个人的实际体会,因为抽象层面的东西,每个人的思考方式不同最终理解的成果也就不会相同,所以我也不能直接说我的对还是不对,写出来的目的就是为了分享和交流,看看有没有其他有经验的朋友也愿意把自己在这方面的想法拿出来跟大家说一说,相信每个人看多了别人的思路之后,也会对自己的编程思想方面的锻炼带来帮助。

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage