Separation and inheritance: Implements Image Upload preview: ImageUploadView,

Source: Internet
Author: User
Tags eventbase

Separation and inheritance: Implements Image Upload preview: ImageUploadView,

This article describes how to generate a thumbnail preview directly after uploading images on a webpage. Considering the applicability of this function, therefore, the related logic is encapsulated into an ImageUploadView component. You can view the git in the next section. In the process of implementing this component, it is useful to the relevant content described in the previous blogs, such as inheriting the library class. js, any component Event Management Library eventBase. js also contains some thoughts on separation of duties, performance and behavior. You are welcome to read and exchange ideas.

Demo effect:


Note: Because the Demo code is static, the File Upload Component is simulated using setTimeout. However, it is called in the same way as uploading components in my actual work, therefore, the Code Implementation of the demo effect fully meets the actual functional requirements.

Based on my previous blog ideas, I will first introduce the requirements for this upload preview function.

1. Requirement Analysis

According to the previous demonstration, the analysis requirements are as follows:

1) At the beginning, only one click upload button is displayed in the upload area. When this button is clicked, the uploaded image is displayed in the preview area.

2) After the uploaded image is added to the preview area, you can delete it by clicking the delete button.

3) when the total number of uploaded images reaches a certain limit, for example, if the number of uploaded images in the demo is 4, the upload button is removed;

4) when the total number of uploaded images reaches a certain limit, if you delete an image, you have to display the upload button again.

The above requirements are as follows:

1) if the page is in the editing status, that is, the status queried from the database, as long as the image list is not empty, the image must be displayed at the beginning; in addition, you must control whether the upload button is displayed based on the length of the image list and the upload limit;

2) if the current page is in a status that can only be viewed but cannot be changed, you must remove the upload and delete buttons at the beginning.

After the requirement analysis is completed, Let's explain my Implementation ideas.

2. Implementation ideas

Because this is a form page, if you want to submit the image to the background after uploading, you must definitely need a text field, therefore, when I create a static page, I will take this text field into account. After uploading a new image and deleting the image, I have to modify the value of this text field. The structure of the static page is as follows:

<div class="holy-layout-am appForm-group appForm-group-img-upload clearfix">
< label class = "holy layout Al" > electronic version of legal person id card < / 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>
</li>
</ul>
<p class="img-upload-msg">
Please make sure the picture is clear and the text is legible
< a href = "ා" title = "view sample" > < I class = "Fa question circle" > < / I > view sample</a>
</p>
</div>
</div>

From this structure, we can also see that I put the entire upload area in a ul, and then use the first li of ul as the upload button. To complete this function, we mainly perform the following tasks: Upload and upload callback, adding or deleting image previews, and managing text domain values. From this point, combined with the separation of duties, this function requires at least three components, one for file upload, one for image preview management, and the other for text Domain value management. Do not encapsulate these three functions in pairs or in all. In this case, the function coupling is too strong, and the scalability of the written components is not highly reusable. If the three components need to interact with each other, you only need to define the interfaces they call to the outside by using the callback function or the publish-subscribe mode.

However, the management of text domain values is very simple. Writing or not writing components does not matter much, but at least function-level encapsulation is available. Although the File Upload Component is not the focus of this article, however, there are many open-source plug-ins available on the Internet, such as webuploader, which can be applied for both direct use and secondary encapsulation. The image preview function is the core content of this article, imageUploadView is the encapsulation of this component. From the perspective of requirements, this component has three semantic instance methods: render, append, delItem, here, render is used to display the initial preview list after initialization, append is used to add a new picture preview after the upload is successful, and delItem is used to delete the existing Picture preview. Follow this basic idea, we only need to design options and events for it based on the requirements and component development experience.

From the previous requirements, we found that the render of this ImageUploadView component will be affected by the Page Status. When the page is in view mode, this component cannot be uploaded or deleted, therefore, you can consider adding a readonly option to it. At the same time, its upload and deletion operations also affect the UI logic of the upload button, which is related to the upload restriction. for flexibility, you must also use the upload restriction as an option. The three instance methods mentioned in the previous section refer to their previous experience in defining events. Generally, an instance method defines a pair of events, just like the bootstrap plug-in method, such as the render method, you can define a render. before: this event is triggered before the main logic of render is executed. If the external listener calls the preventDefault () method of this event, the main logic of render is not executed. after event, which is triggered after the main logic of render is executed. The benefit of defining events in pairs is that it not only provides external extension component functions, but also increases the management of default behavior of components.

Finally, from my previous work experience, in addition to the function of uploading images for previewing, I have also done similar operations such as uploading videos, uploading audios, and uploading common documents, so this time when I met this function, I felt that we should extract similar things from these features as a base class and upload images, video upload and so on inherit this base class to implement their respective logic. This base class also has the advantage of completely separating general logic from the HTML structure. In this base class, it only does some common things, such as options and component behavior (render, append, delItem) definition, as well as monitoring and triggering of common events, it only needs to leave a fixed interface to the subclass for implementation. In the subsequent implementation, I defined a FileUploadBaseView component to complete the functions of this base class. This base class does not contain any html or css processing logic, it just abstracts the functions we need to complete and does not process any business logic. Subclass implemented based on business logic is limited by the html structure, so the application scope of the subclass is small. The base class has a larger applicability because it is completely isolated from the html structure.

3. Implementation Details

The implementation methods in part 1 include FileUploadBaseView and ImageUploadView, which are the base classes of the latter. At the same time, considering that the event management function should be provided to components, eventBase of the previous blog should be used. js and FileUploadBaseView must inherit the EventBase component of the library. Considering the definition and Inheritance of classes, the inherited Library class written earlier must also be used. js to define the inheritance relationship between components and components. The inheritance relationship of related components is: ImageUploadView extend FileUploadBaseView extend EventBase.

(Note: seajs is modular in the following code .)

FileUploadBaseView does the following:

1) define general options and general Event Management

In the DEFAULTS configuration of this component, you can see the definitions of all general options and General events:

var DEFAULTS = {
Data: [], / / the data list to be displayed. The list element must be of object type, such as [{URL: 'xxx. PNG'}, {URL: 'yyyy. PNG'}]
Sizelimit: 0, / / used to limit the number of elements displayed in baseview. A value of 0 means unlimited
Readonly: false, / / used to control whether elements in baseview can be added or deleted
Onbeforerender: $. NOOP, / / corresponds to the render.before event, which is triggered before the render method is called
Onrender: $. NOOP, / / corresponds to the render.after event, which is triggered after the render method is called
Onbeforeappend: $. NOOP, / / corresponds to the append.before event, which is triggered before the append method call
Onappend: $. NOOP, / / corresponds to the append.after event, which is triggered after the append method call
Onbeforedelitem: $. NOOP, / / corresponds to the delitem.before event, which is triggered before the delitem method call
Ondelitem: $. NOOP / / corresponds to the event delitem. After, which is triggered after the call of the delitem method
}

In the init method of this component, we can see the initialization logic for the general option and event management:

init: function (element, options) {
//Call the init method of the parent eventbase through this.base
this.base(element);
//Instance properties
var opts = this.options = this.getOptions(options);
this.data = resolveData(opts.data);
delete opts.data;
this.sizeLimit = opts.sizeLimit;
this.readOnly = opts.readOnly;
//Binding event
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) define the behavior of components and reserve interfaces that can be implemented by sub-classes:

render: function () {
* *
*Render is a template. Subclasses do not need to override the render method, but only the
*When the render method of a subclass is called, the render method of the parent class is called
*However, when executing the ﹐ render method, the ﹐ render method of the subclass is called
*In this way, the trigger operation of before and after events can be unified
* /
Var E;
this.trigger(e = $.Event('render.before'));
if (e.isDefaultPrevented()) return;
this._render();
this.trigger($.Event('render.after'));
}
//Subclass needs to implement the "render method"
_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);
}
//Subclass needs to implement the ﹐ append method
_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);
}
//Subclass needs to implement the \
_delItem: function (data) {
}

To unify the event distribution logic before and after processing behaviors, extract the main logic of render, append, and delItem into the method _ render, _ append, and _ delItem to be implemented by the quilt class. When the render method of the subclass is called, the actually parent class method is called. However, when the parent class executes the _ render method, the subclass method is executed, the other two methods are similar. It should be noted that the subclass cannot overwrite the render, append, and delItem methods. Otherwise, you must handle the trigger logic of related events on your own.

The overall implementation of FileUploadBaseView is as follows:

define(function (require, exports, module) {
var $ = require('jquery');
var Class = require('mod/class');
var EventBase = require('mod/eventBase');
var DEFAULTS = {
Data: [], / / the data list to be displayed. The list element must be of object type, such as [{URL: 'xxx. PNG'}, {URL: 'yyyy. PNG'}]
Sizelimit: 0, / / used to limit the number of elements displayed in baseview. A value of 0 means unlimited
Readonly: false, / / used to control whether elements in baseview can be added or deleted
Onbeforerender: $. NOOP, / / corresponds to the render.before event, which is triggered before the render method is called
Onrender: $. NOOP, / / corresponds to the render.after event, which is triggered after the render method is called
Onbeforeappend: $. NOOP, / / corresponds to the append.before event, which is triggered before the append method call
Onappend: $. NOOP, / / corresponds to the append.after event, which is triggered after the append method call
Onbeforedelitem: $. NOOP, / / corresponds to the delitem.before event, which is triggered before the delitem method call
Ondelitem: $. NOOP / / corresponds to the event delitem. After, which is triggered after the call of the delitem method
}
* *
*For data processing, add a "UUID" attribute to each record of data to facilitate searching
* /
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) {
//Call the init method of the parent eventbase through this.base
this.base(element);
//Instance properties
var opts = this.options = this.getOptions(options);
this.data = resolveData(opts.data);
delete opts.data;
this.sizeLimit = opts.sizeLimit;
this.readOnly = opts.readOnly;
//Binding event
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) {
//Get dateitem according to UUID
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 &amp;&amp; (ret = i);
};
Return ret;
}
render: function () {
* *
*Render is a template. Subclasses do not need to override the render method, but only the
*When the render method of a subclass is called, the render method of the parent class is called
*However, when executing the ﹐ render method, the ﹐ render method of the subclass is called
*In this way, the trigger operation of before and after events can be unified
* /
Var E;
this.trigger(e = $.Event('render.before'));
if (e.isDefaultPrevented()) return;
this._render();
this.trigger($.Event('render.after'));
}
//Subclass needs to implement the "render method"
_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);
}
//Subclass needs to implement the ﹐ append method
_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);
}
//Subclass needs to implement the \
_delItem: function (data) {
}
}
extend: EventBase,
staticMembers: {
DEFAULTS: DEFAULTS
}
};
return FileUploadBaseView;
};

The implementation of ImageUploadView is relatively simple. It is similar to filling in the blanks. There are several points to note:

1) DEFAULTS of this class needs to extend the DEFAULTS of the parent class to add the default options of this subclass, and retain the default options definition of the parent class; according to the static page structure, an onAppendClick event is added. You can call the related methods of the File Upload Component in this event:

// Inherit and extend the default defasvsvar DEFAULTS = $. extend ({}, FileUploadBaseView. DEFAULTS, {onAppendClick: $. noop // callback when the upload button is clicked}) of the parent class });

2) In the init method, you need to call the init method of the parent class to complete the general logic processing. At the same time, you have to manually call the render method at the end of the init, this allows you to see the effect after the component is instantiated:


Other implementations are purely business logic implementations, which are closely related to requirements in Part 1.

The overall implementation of ImageUploadView is as follows:

define(function (require, exports, module) {
var $ = require('jquery');
var Class = require('mod/class');
var FileUploadBaseView = require('mod/fileUploadBaseView');
//Inherit and extend the default defaults of the parent class
var DEFAULTS = $.extend({}, FileUploadBaseView.DEFAULTS, {
Onappendclick: $. NOOP / / callback when clicking upload
};
var ImageUploadView = Class({
instanceMembers: {
init: function (element, options) {
var $element = this.$element = $(element);
var opts = this.getOptions(options);
//Call the init method of the parent class to complete options acquisition, data analysis and general event listening
this.base(this.$element, options);
//Add upload and delete listeners and trigger processing
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. Reserved ($('< Li class = "view item add" > < a class = "view act add" href = "javascript:;" title = "click Upload" > + < / a > < li >')));
}
_clearItemAddHtml: function ($itemAddLi) {
$itemAddLi.remove();
}
_render: function () {
var html = [], that = this;
//If it is not read-only and the upload limit has not been reached, add the upload button
if (!(this.readOnly || (this.sizeLimit &amp;&amp; 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 ? '' : '<span class="view-act-del" data-uuid="',
Item._uuid,
'"> delete < / span >',
'</a></li>'
].join ('');
}
_dealWithSizeLimit: function () {
if (this.sizeLimit) {
var $itemAddLi = this.$element.find('li.view-item-add');
//Remove the upload button if the upload limit has been reached
if (this.sizeLimit &amp;&amp; this.sizeLimit <= this.data.length &amp;&amp; $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. Demo description

The demo project structure is:


It is the core code of the demo. FileUploadBaserView. js and imageUploadView. js are the two core components previously implemented. FileUploader. js is used to simulate the Upload Component. Its instance has an onSuccess callback, indicating that the upload is successful. Another openChooseFileWin is used to simulate the process of opening and uploading the selected file window:

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);
}
}
});

App/regist. js is the logic code of the demo page. The key parts are described using Annotations:

define(function (require, exports, module) {
var $ = require('jquery');
var ImageUploadView = require('mod/imageUploadView');
Var fileuploader = require ('mod / fileuploader '); / / this is a file upload component simulated by an asynchronous task
//$legalpersonidpic is used to store the uploaded file information. After the upload component is uploaded successfully and the imageuploadview component deletes an item, the value of $legalpersonidpic will be affected
var $legalPersonIDPic = $('#legalPersonIDPic-input'),
Data = json.parse ($legalpersonidpic. Val() | '[]'); / / data is the initial value. For example, the current page may be loaded from the database, and it needs to be rendered with the imageuploadview component
//After the file is uploaded successfully, save the newly uploaded file to the value of $legalpersonidpic
//$legalpersonidpic is stored as a JSON string
var appendImageInputValue = function ($input, item) {
var value = JSON.parse($input.val() || '[]');
value.push(item);
$input.val(JSON.stringify(value));
}
//When the imageuploadview component is called to delete an item, the stored information in $legalpersonidpic should be cleared synchronously
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 () {
//Open the window for selecting files
fileUploader.openChooseFileWin();
}
onDelItem: function (data) {
removeImageInputValue($legalPersonIDPic, data._uuid);
}
};
}; 

5. Summary

It is not difficult to implement the ImageUploadView component in the end, but I have spent a lot of time pondering its implementation methods and other parent classes, most of the time is spent in the abstract process of separation of duties and behaviors. The points I have expressed in this article on these two aspects are just my own practical experience. Because of the abstract aspects, the results of different ways of thinking will not be the same for everyone, therefore, I cannot directly say that I am right or wrong. The purpose of writing is to share and communicate with each other, let's see if there are any other experienced friends who are willing to share their ideas in this regard with you. I believe that after everyone sees others' ideas, it will also help your own programming thoughts.

Articles you may be interested in:
  • Check the size, size, format, and preview js Code before uploading the image.
  • Javascript Image Upload preview-compatible with standards
  • Jquery Image Upload proportional preview plug-in Set
  • Php Image Upload storage source code and can be previewed
  • JavaScript code preview before uploading images compatible with IE and FF
  • Simple code for preview Effect of Asp.net Image Upload
  • Asp.net FileUpload + Image sample code for creating an avatar
  • Upload and preview a simulated QQ mood Image
  • Jquery supports local preview of images uploaded by browsers
  • Ie8 sample code for Local Image Upload and preview
  • Local preview instance before Javascript Image Upload


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.