图片上传组件 Yupload

本文介绍一款图片上传组件,基于原生 javascript 编写, 不依赖于其他框架

Yupload 特点

基于表单提交, 原生 javascript 实现,兼容性好
支持多个图片上传请求同时发出,将按照提交请求的先后顺序依次上传
API接口返回结果必须为 JSON 格式字符串
问题: 1、如果为 input[type=”file”] 元素绑定了事件, 在图片上传后需要重新为该元素注册事件
2、如果图片上传失败,即返回状态不是 200, 且返回结果为JSON格式的字符串, 该组件无法区分出错误类型,需要开发人员自己判断

Yupload 使用

1、图片上传元素编写
**注意 input[type=”file] 标签中的 name 标签不能漏, 就是上传图片时对应的字段

1
2
3
4
5
6
<div class="upload" id="upload">
<input type="file" class="upfile" name="file" id="upfile1">
<input type="button" id="to-upload1" value="上传1"> <br>
<input type="file" class="upfile" name="file" id="upfile2">
<input type="button" id="to-upload2" value="上传2">
</div>

2、初始化

  • 参数1: url, 图片提交地址, 类型 string, 不能为空
  • 参数2: csrftoken, 表单提交时需要提供的 csrftoken, 类型object, 包含属性 name: csrftoken 字段名称, value: csrftoken 值, 非必填,根据接口需要配置
  • 参数3: hideField, 上传图片时需要携带的其他字段, 类型 Array, 数组元素由对象构成, 对象属性包括: name 字段名, value 字段值。

例:

1
2
3
4
5
6
let options = {
url: '/common/api/upload/image/', //图片提交地址
csrftoken: { name: 'csrfmiddlewaretoken', value: csrftoken }, //django 框架的 csrftoken
hideField: [{name: 'tag', value: 123}] //添加的其他字段
}
let yUpload = new Yupload(options) // 初始化

初始化后会在 <body>中生成如下元素

1
2
3
4
5
6
7
8
9
10
<div id="yupload-wrap" style="display: none;">
<iframe id="yupload-iframe" name="yupload-iframe" style="display: none;"></iframe>
<form target="yupload-iframe" id="yupload-form" method="POST" enctype="multipart/form-data" action="/common/api/upload/material/">
<input type="hidden" name="csrfmiddlewaretoken" value="bbX3XLXGOPrF4EbGMtP2eG5VYgGUVHyPYgL7zw9pNUgW6ABCoMQLhWWFzX7DIZ4U">
<div id="yupload-fields">
<input type="hidden" name="tag" value="1233">
<input type="file" class="upfile" name="file" id="uploading-file">
</div>
</form>
</div>

3、触发图片上传
调用 Yupload 的方法 trigger 来触发图片上传, 在 trigger 方法中可以传递的参数如下:

  • url: 修改图片上传地址, 类型 string, 非必填
  • hideField: 修改随图片一起上传的参数, 数据格式和初始化时一样, 注意:使用此参数时会清空 初始化时 hideField 传入的所有参数
  • target: 需要上传的内容的 input[type=”file”] 标签, 必填, target 接受以下两类数据
    • input 标签的 id 属性, 类型 string,
    • input 标签的 dom 对象, 类型 HTMLInputElement,
  • callback 函数, 函数的第一参数为 error, 如果 error 不为空则说明图片上传出错, 第二个参数为接口返回的结果,类型 object

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$('#to-upload1').click(function() {
let options = {
target: $('#upfile1')[0],
hideField: [{name: 'tag', value: 1}],
}
yUpload.trigger(options, (err, resp) => {
console.log('img1', resp)
})
})
$('#to-upload2').click(function() {
let options = {
url: '/common/api/upload/pdf/',
target: $('#upfile2')[0],
hideField: [{name: 'tag', value: 2}],
}
yUpload.trigger(options, (err, resp) => {
console.log('img2', resp)
})
})

Yupload 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* author: feifeiyu
* verion: 1.5
* Yupload is a image upload component base on form submit
* @param url //image upload address
* @param csrftoken // csrftoken for POST Request
* @param hideField // params submit with image
* Yupload blog: https://feifeiyum.github.io/2016/12/24/front-yupload/
*/

var Yupload = (function() {
var YU = function(opt, cb) {
if(opt.url === undefined) {
throw 'upload url can not be null'
}
if(opt.hideField && opt.hideField.constructor.toString().indexOf('Array') === -1) {
throw 'type of hide field is array'
}
if(opt.csrftoken && typeof opt.csrftoken !== 'object') {
throw 'type of csrftoken is object'
}

var self = this
cb && (self.cb = cb)
self.queue = []
//生产 DOM
let fakeIframe = self.genDom(opt)
//注册事件
self.fetchData(fakeIframe)
}

YU.prototype.fetchData = function(fakeIframe) {
let self = this
var fetchResp = function() {
try {
var data = JSON.parse(this.contentDocument.activeElement.childNodes[0].innerText)
self.cb && self.cb(null, data)
} catch(err) {
self.cb && self.cb(err, null)
}
//clean queue
self.queue.splice(0, 1)
if(self.queue.length > 0) {
self.upload()
}
}

//get result
if(fakeIframe.addEventListener) {
fakeIframe.addEventListener('load', fetchResp)
} else if(fakeIframe.attachEvent) {
fakeIframe.attachEvent('load', fetchResp)
} else {
alert('current browser do not support Yupload')
}

}

YU.prototype.genDom = function(opt) {
let self = this

if(document.getElementById('yupload-wrap')) {
throw 'Yupload do not need to init multi-times'
}

var uploadWrap = document.createElement('div')
uploadWrap.id = 'yupload-wrap'
uploadWrap.style.display = 'none'
//fake iframe for form
var fakeIframe = document.createElement('iframe')
fakeIframe.id = 'yupload-iframe'
fakeIframe.name = 'yupload-iframe'
fakeIframe.style.display = 'none'
uploadWrap.appendChild(fakeIframe)

//form element
self.form = document.createElement('form')
self.form.target = 'yupload-iframe'
self.form.id = 'yupload-form'
self.form.method = 'POST'
self.form.enctype = 'multipart/form-data'
self.form.action = opt.url
//insert csrftoken
if(opt.csrftoken) {
var csrfElem = '<input type="hidden" name="' + opt.csrftoken.name + '" value="' + opt.csrftoken.value + '">'
self.form.innerHTML = csrfElem
}
// hidden fields to submit
self.fields = document.createElement('div')
self.fields.id = 'yupload-fields'
//render fields
if(opt.hideField) {
var hideElem = ''
for(let i = 0; i < opt.hideField.length; i++) {
hideElem += '<input type="hidden" name="' + opt.hideField[i].name + '" value="' + opt.hideField[i].value + '" id="">'
}
self.fields.innerHTML = hideElem
}
self.form.appendChild(self.fields)
uploadWrap.appendChild(self.form)
document.body.appendChild(uploadWrap)

return fakeIframe
}

//add request to queue
YU.prototype.trigger = function(opt, cb) {
let self = this
opt.cb = cb
//push queue

if(self.queue.length === 0) {
// if it is not busy
self.queue.push(opt)
self.upload()
} else {
self.queue.push(opt)
}
}
//operate upload
YU.prototype.upload = function() {
var self = this
var opt = self.queue[0]
if(opt.url) { //change post url
self.form.action = opt.url
}
if(opt.hideField && opt.hideField.constructor.toString().indexOf('Array') === -1) {
throw 'type of hide field is array'
}
opt.cb && (self.cb = opt.cb)
//rerender fields
if(opt.hideField) {
var hideElem = ''
for(let i = 0; i < opt.hideField.length; i++) {
hideElem += '<input type="hidden" name="' + opt.hideField[i].name + '" value="' + opt.hideField[i].value + '">'
}
self.fields.innerHTML = hideElem
}

if(opt.target) {
var inputFile = ''
var cloneNode = ''
//get input file element
if(typeof opt.target === 'string') {
inputFile = document.getElementById(opt.target)
} else if(opt.target.constructor.toString().indexOf('HTMLInputElement') > -1) {
inputFile = opt.target
} else {
throw 'type of file option is DOM(input) or input element\'s id and can not be null'
}
// clone and restore input element
cloneNode = inputFile.cloneNode(true)
inputFile.parentNode.replaceChild(cloneNode, inputFile)
//append input with selected file to form
inputFile.id = 'uploading-file'
self.fields.appendChild(inputFile)
//submit
self.form.submit()
}
}

return YU
}())

//采用 commonJs 模块导入时添加这句
//module.exports = Yupload

END