├── .bowerrc ├── .gitignore ├── bower.json ├── server.js ├── package.json ├── umd.hbs ├── spec ├── index.html ├── uploader-spec.coffee └── uploader-spec.js ├── Gruntfile.coffee ├── README.md ├── demo.html ├── src └── uploader.coffee └── lib └── uploader.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor/bower" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | .DS_Store 18 | .sass-cache 19 | .grunt 20 | .bundle 21 | uploads 22 | vendor 23 | 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-uploader", 3 | "version": "2.0.4", 4 | "homepage": "https://github.com/mycolorway/simple-uploader", 5 | "authors": [ 6 | "farthinker " 7 | ], 8 | "description": "A HTML5 upload component without UI", 9 | "main": "lib/uploader.js", 10 | "keywords": [ 11 | "upload" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "spec", 17 | "node_modules", 18 | "vendor", 19 | "Gemfile", 20 | "Gemfile.lock", 21 | "Gruntfile.coffee", 22 | "package.json", 23 | "demo.html", 24 | "README.md", 25 | "umd.hbs" 26 | ], 27 | "devDependencies": { 28 | "jasmine-ajax": "2.x" 29 | }, 30 | "dependencies": { 31 | "jquery": "2.x", 32 | "simple-module": "2.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // nodejs server for upload testing 2 | 3 | var express = require('express'); 4 | var path = require('path') 5 | var fs = require('fs'); 6 | var app = express(); 7 | 8 | fs.mkdir('./uploads', 0777, function() { 9 | }); 10 | 11 | app.use(express.bodyParser({uploadDir:'./uploads'})); 12 | 13 | app.post('/upload', function(req, res) { 14 | var tmp_path = req.files.upload_file.path; 15 | var target_path = path.resolve('./uploads', req.files.upload_file.name); 16 | 17 | fs.rename(tmp_path, target_path, function(err) { 18 | if (err) throw err; 19 | fs.unlink(tmp_path, function() { 20 | if (err) throw err; 21 | res.send({ 22 | success: true, 23 | file_path: 'assets/images/' + req.files.upload_file.name 24 | }); 25 | }); 26 | }); 27 | }); 28 | 29 | module.exports = app; 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-uploader", 3 | "version": "2.0.4", 4 | "description": "A HTML5 upload component without UI", 5 | "keywords": "upload", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:mycolorway/simple-uploader.git" 9 | }, 10 | "author": "farthinker", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/mycolorway/simple-uploader/issues" 14 | }, 15 | "main": "lib/uploader.js", 16 | "homepage": "https://github.com/mycolorway/simple-uploader", 17 | "dependencies": { 18 | "jquery": "2.x", 19 | "simplemodule": "2.x" 20 | }, 21 | "devDependencies": { 22 | "grunt": "~0.4.5", 23 | "grunt-contrib-watch": "0.x", 24 | "grunt-contrib-coffee": "0.x", 25 | "grunt-contrib-jasmine": "0.x", 26 | "grunt-umd": "2.x", 27 | "grunt-express": "1.4.0", 28 | "express": "~3.3.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /umd.hbs: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define({{#if amdModuleId}}'{{amdModuleId}}', {{/if}}[{{{amdDependencies.wrapped}}}], function ({{{dependencies}}}) { 5 | return ({{#if objectToExport}}root['{{{objectToExport}}}'] = {{/if}}factory({{{dependencies}}})); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory({{{cjsDependencies.wrapped}}}); 12 | } else { 13 | root.simple = root.simple || {}; 14 | {{#if globalAlias}}root.simple['{{{globalAlias}}}'] = {{else}}{{#if objectToExport}}root['{{{objectToExport}}}'] = {{/if}}{{/if}}factory({{{globalDependencies.normal}}}); 15 | } 16 | }(this, function ({{dependencies}}) { 17 | 18 | {{{code}}} 19 | {{#if objectToExport}} 20 | return {{{objectToExport}}}; 21 | {{/if}} 22 | 23 | })); 24 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 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 | -------------------------------------------------------------------------------- /spec/uploader-spec.coffee: -------------------------------------------------------------------------------- 1 | 2 | describe 'simple uploader', -> 3 | 4 | beforeEach -> 5 | jasmine.Ajax.install() 6 | 7 | afterEach -> 8 | jasmine.Ajax.uninstall() 9 | 10 | it 'should trigger several events while uploading', -> 11 | 12 | uploader = simple.uploader 13 | url: '/upload' 14 | 15 | file = new Blob ['This is a test file'], 16 | type: 'text/plain' 17 | 18 | callback = 19 | beforeupload: jasmine.createSpy 'beforeupload' 20 | uploadprogress: jasmine.createSpy 'uploadprogress' 21 | uploadsuccess: jasmine.createSpy 'uploadsuccess' 22 | 23 | uploader.on 'beforeupload', (e, file) -> 24 | callback.beforeupload() 25 | 26 | uploader.on 'uploadprogress', (e, file, loaded, total) -> 27 | callback.uploadprogress() 28 | 29 | uploader.on 'uploadsuccess', (e, file, result) -> 30 | callback.uploadsuccess() 31 | expect(result?.success).toBe(true) 32 | 33 | uploader.upload file 34 | request = jasmine.Ajax.requests.mostRecent() 35 | 36 | expect(request.url).toBe('/upload') 37 | expect(callback.beforeupload).toHaveBeenCalled() 38 | expect(callback.uploadprogress).not.toHaveBeenCalled() 39 | expect(callback.uploadsuccess).not.toHaveBeenCalled() 40 | 41 | request.response 42 | status: 200 43 | contentType: 'application/json' 44 | responseText: '{"success": true}' 45 | 46 | expect(callback.uploadprogress).toHaveBeenCalled() 47 | expect(callback.uploadsuccess).toHaveBeenCalled() 48 | 49 | -------------------------------------------------------------------------------- /spec/uploader-spec.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | describe('simple uploader', function() { 3 | beforeEach(function() { 4 | return jasmine.Ajax.install(); 5 | }); 6 | afterEach(function() { 7 | return jasmine.Ajax.uninstall(); 8 | }); 9 | return it('should trigger several events while uploading', function() { 10 | var callback, file, request, uploader; 11 | uploader = simple.uploader({ 12 | url: '/upload' 13 | }); 14 | file = new Blob(['This is a test file'], { 15 | type: 'text/plain' 16 | }); 17 | callback = { 18 | beforeupload: jasmine.createSpy('beforeupload'), 19 | uploadprogress: jasmine.createSpy('uploadprogress'), 20 | uploadsuccess: jasmine.createSpy('uploadsuccess') 21 | }; 22 | uploader.on('beforeupload', function(e, file) { 23 | return callback.beforeupload(); 24 | }); 25 | uploader.on('uploadprogress', function(e, file, loaded, total) { 26 | return callback.uploadprogress(); 27 | }); 28 | uploader.on('uploadsuccess', function(e, file, result) { 29 | callback.uploadsuccess(); 30 | return expect(result != null ? result.success : void 0).toBe(true); 31 | }); 32 | uploader.upload(file); 33 | request = jasmine.Ajax.requests.mostRecent(); 34 | expect(request.url).toBe('/upload'); 35 | expect(callback.beforeupload).toHaveBeenCalled(); 36 | expect(callback.uploadprogress).not.toHaveBeenCalled(); 37 | expect(callback.uploadsuccess).not.toHaveBeenCalled(); 38 | request.response({ 39 | status: 200, 40 | contentType: 'application/json', 41 | responseText: '{"success": true}' 42 | }); 43 | expect(callback.uploadprogress).toHaveBeenCalled(); 44 | return expect(callback.uploadsuccess).toHaveBeenCalled(); 45 | }); 46 | }); 47 | 48 | }).call(this); 49 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | 3 | grunt.initConfig 4 | 5 | pkg: grunt.file.readJSON 'package.json' 6 | 7 | coffee: 8 | src: 9 | options: 10 | bare: true 11 | files: 'lib/uploader.js': 'src/uploader.coffee' 12 | spec: 13 | files: 14 | 'spec/uploader-spec.js': 'spec/uploader-spec.coffee' 15 | 16 | umd: 17 | all: 18 | src: 'lib/uploader.js' 19 | template: 'umd.hbs' 20 | amdModuleId: 'simple-uploader' 21 | objectToExport: 'uploader' 22 | globalAlias: 'uploader' 23 | deps: 24 | 'default': ['$', 'SimpleModule'] 25 | amd: ['jquery', 'simple-module'] 26 | cjs: ['jquery', 'simple-module'] 27 | global: 28 | items: ['jQuery', 'SimpleModule'] 29 | prefix: '' 30 | 31 | watch: 32 | src: 33 | files: ['src/**/*.coffee'] 34 | tasks: ['coffee', 'umd'] 35 | spec: 36 | files: ['spec/**/*.coffee'] 37 | tasks: ['coffee:spec'] 38 | jasmine: 39 | files: ['lib/**/*.js', 'specs/**/*.js'] 40 | tasks: 'jasmine:test:build' 41 | 42 | jasmine: 43 | test: 44 | src: ['lib/**/*.js'] 45 | options: 46 | outfile: 'spec/index.html' 47 | specs: 'spec/uploader-spec.js' 48 | vendor: [ 49 | 'vendor/bower/jquery/dist/jquery.min.js' 50 | 'vendor/bower/simple-module/lib/module.js' 51 | 'vendor/bower/jasmine-ajax/lib/mock-ajax.js' 52 | ] 53 | 54 | express: 55 | server: 56 | options: 57 | server: 'server.js' 58 | bases: __dirname 59 | 60 | grunt.loadNpmTasks 'grunt-contrib-coffee' 61 | grunt.loadNpmTasks 'grunt-contrib-watch' 62 | grunt.loadNpmTasks 'grunt-contrib-jasmine' 63 | grunt.loadNpmTasks 'grunt-express' 64 | grunt.loadNpmTasks 'grunt-umd' 65 | 66 | grunt.registerTask 'default', ['coffee', 'umd', 'jasmine:test:build', 'express', 'watch'] 67 | 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simple-uploader 2 | =============== 3 | 4 | 一个不带UI的HTML5上传组件,为实现各种上传交互提供最大的灵活度。 5 | 6 | 依赖项: 7 | * jQuery 2.0+ 8 | * [simple-module](https://github.com/mycolorway/simple-module)(组件抽象类) 9 | 10 | 浏览器支持:IE10+、Chrome、Safari、Firefox。 11 | 12 | ###使用方法 13 | 14 | 首先在页面里引用相关脚本: 15 | 16 | ```html 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | 初始化uploader实例: 24 | 25 | ```js 26 | var uploader = simple.uploader({ 27 | url: '/upload' 28 | }); 29 | 30 | ``` 31 | 32 | 获取文件对象,然后通过uploader上传: 33 | 34 | ``` 35 | $('#select-file').on('change', function(e) { 36 | uploader.upload(this.files); 37 | }); 38 | ``` 39 | 40 | ###API文档 41 | 42 | ####初始化选项 43 | 44 | __url__ 45 | 46 | 上传接口地址,必选。 47 | 48 | __params__ 49 | 50 | hash对象,上传请求附带的参数,可选 51 | 52 | __fileKey__ 53 | 54 | 服务器端获取上传文件的key,可选,默认是'upload_file' 55 | 56 | __connectionCount__ 57 | 58 | 允许同时上传的文件数量,可选,默认值是3 59 | 60 | ####方法 61 | 62 | uploader实例会暴露一些公共方法: 63 | 64 | __upload__ ([File Object]/[Input Element]/[File Array]) 65 | 66 | 开始上传的接口,可以接受的参数有:File对象(通过input:file选择或者通过拖拽接口获取)、input:file元素或者File对象的数组。 67 | 68 | __cancel__ (file/fileId) 69 | 70 | 取消上传某个文件,可以接受事件传出来的file对象或者file的id。 71 | 72 | __destroy__ 73 | 74 | 摧毁uploader实例 75 | 76 | __readImageFile__ ([File Object], callback) 77 | 78 | 通过图片的File对象获取图片的base64预览图,在上传图片之前需要预览的时候非常有用。 79 | 80 | ####事件 81 | 82 | uploader实例可以绑定一些自定义事件,例如: 83 | 84 | ```js 85 | uploader.on('beforeupload', function(e, file) { 86 | // do something before upload 87 | }); 88 | ``` 89 | 90 | __beforeupload__ (e, file) 91 | 92 | 上传开始之前触发,`return false`可以取消上传 93 | 94 | __uploadprogress__ (e, file, loaded, total) 95 | 96 | 上传的过程中会触发多次,`loaded`是已经上传的大小,`total`是文件的总大小,但是是byte。 97 | 98 | __uploadsuccess__ (e, file, result) 99 | 100 | 上传成功的时候触发,`result`是服务器端返回的json响应。 101 | 102 | __uploaderror__ (e, file, xhr, status) 103 | 104 | 上传失败的时候触发,`xhr`是上传接口的XMLHttpRequest对象,`status`是报错信息。 105 | 106 | __uploadcomplete__ (e, file, responseText) 107 | 108 | 无论上传成功还是失败都会触发这个事件,responseText是响应的文本字符串。 109 | 110 | __uploadcancel__ (e, file) 111 | 112 | 调用uploader.cancel()方法取消上传的时候会触发这个事件 113 | 114 | 115 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | html5 upload component 5 | 6 | 7 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | 79 |
80 |
81 |
82 | 83 | 90 | 91 | 92 | 93 | 94 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/uploader.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Uploader extends SimpleModule 3 | 4 | @count: 0 5 | 6 | opts: 7 | url: '' 8 | params: null 9 | fileKey: 'upload_file' 10 | connectionCount: 3 11 | 12 | _init: -> 13 | @files = [] #files being uploaded 14 | @queue = [] #files waiting to be uploaded 15 | @id = ++ Uploader.count 16 | 17 | # upload the files in the queue 18 | @on 'uploadcomplete', (e, file) => 19 | @files.splice($.inArray(file, @files), 1) 20 | if @queue.length > 0 and @files.length < @opts.connectionCount 21 | @upload @queue.shift() 22 | else 23 | @uploading = false 24 | 25 | # confirm to leave page while uploading 26 | $(window).on 'beforeunload.uploader-' + @id, (e) => 27 | return unless @uploading 28 | 29 | # for ie 30 | # TODO firefox can not set the string 31 | e.originalEvent.returnValue = @_t('leaveConfirm') 32 | # for webkit 33 | return @_t('leaveConfirm') 34 | 35 | generateId: (-> 36 | id = 0 37 | return -> 38 | id += 1 39 | )() 40 | 41 | upload: (file, opts = {}) -> 42 | return unless file? 43 | 44 | if $.isArray(file) or file instanceof FileList 45 | @upload(f, opts) for f in file 46 | else if $(file).is('input:file') 47 | key = $(file).attr('name') 48 | opts.fileKey = key if key 49 | @upload($.makeArray($(file)[0].files), opts) 50 | else if !file.id or !file.obj 51 | file = @getFile file 52 | 53 | return unless file and file.obj 54 | 55 | $.extend(file, opts) 56 | 57 | if @files.length >= @opts.connectionCount 58 | @queue.push file 59 | return 60 | 61 | return if @triggerHandler('beforeupload', [file]) == false 62 | 63 | @files.push file 64 | @_xhrUpload file 65 | @uploading = true 66 | 67 | getFile: (fileObj) -> 68 | if fileObj instanceof window.File or fileObj instanceof window.Blob 69 | name = fileObj.fileName ? fileObj.name 70 | else 71 | return null 72 | 73 | id: @generateId() 74 | url: @opts.url 75 | params: @opts.params 76 | fileKey: @opts.fileKey 77 | name: name 78 | size: fileObj.fileSize ? fileObj.size 79 | ext: if name then name.split('.').pop().toLowerCase() else '' 80 | obj: fileObj 81 | 82 | _xhrUpload: (file) -> 83 | formData = new FormData() 84 | formData.append(file.fileKey, file.obj) 85 | formData.append("original_filename", file.name) 86 | formData.append(k, v) for k, v of file.params if file.params 87 | 88 | file.xhr = $.ajax 89 | url: file.url 90 | data: formData 91 | processData: false 92 | contentType: false 93 | type: 'POST' 94 | headers: 95 | 'X-File-Name': encodeURIComponent(file.name) 96 | xhr: -> 97 | req = $.ajaxSettings.xhr() 98 | if req 99 | req.upload.onprogress = (e) => 100 | @progress(e) 101 | req 102 | progress: (e) => 103 | return unless e.lengthComputable 104 | @trigger 'uploadprogress', [file, e.loaded, e.total] 105 | error: (xhr, status, err) => 106 | @trigger 'uploaderror', [file, xhr, status] 107 | success: (result) => 108 | @trigger 'uploadprogress', [file, file.size, file.size] 109 | @trigger 'uploadsuccess', [file, result] 110 | $(document).trigger 'uploadsuccess', [file, result, @] 111 | complete: (xhr, status) => 112 | @trigger 'uploadcomplete', [file, xhr.responseText] 113 | 114 | cancel: (file) -> 115 | unless file.id 116 | for f in @files 117 | if f.id == file * 1 118 | file = f 119 | break 120 | 121 | @trigger 'uploadcancel', [file] 122 | 123 | # abort xhr will trigger complete event automatically 124 | file.xhr.abort() if file.xhr 125 | file.xhr = null 126 | 127 | readImageFile: (fileObj, callback) -> 128 | return unless $.isFunction callback 129 | 130 | img = new Image() 131 | img.onload = -> 132 | callback img 133 | img.onerror = -> 134 | callback() 135 | 136 | if window.FileReader && FileReader.prototype.readAsDataURL && /^image/.test(fileObj.type) 137 | fileReader = new FileReader() 138 | fileReader.onload = (e) -> 139 | img.src = e.target.result 140 | fileReader.readAsDataURL fileObj 141 | else 142 | callback() 143 | 144 | destroy: -> 145 | @queue.length = 0 146 | @cancel file for file in @files 147 | $(window).off '.uploader-' + @id 148 | $(document).off '.uploader-' + @id 149 | 150 | @i18n: 151 | 'zh-CN': 152 | leaveConfirm: '正在上传文件,如果离开上传会自动取消' 153 | 154 | @locale: 'zh-CN' 155 | 156 | 157 | uploader = (opts) -> 158 | new Uploader(opts) 159 | 160 | 161 | -------------------------------------------------------------------------------- /lib/uploader.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-uploader', ["jquery","simple-module"], function ($, SimpleModule) { 5 | return (root['uploader'] = factory($, SimpleModule)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery"),require("simple-module")); 12 | } else { 13 | root.simple = root.simple || {}; 14 | root.simple['uploader'] = factory(jQuery,SimpleModule); 15 | } 16 | }(this, function ($, SimpleModule) { 17 | 18 | var Uploader, uploader, 19 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 20 | hasProp = {}.hasOwnProperty; 21 | 22 | Uploader = (function(superClass) { 23 | extend(Uploader, superClass); 24 | 25 | function Uploader() { 26 | return Uploader.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | Uploader.count = 0; 30 | 31 | Uploader.prototype.opts = { 32 | url: '', 33 | params: null, 34 | fileKey: 'upload_file', 35 | connectionCount: 3 36 | }; 37 | 38 | Uploader.prototype._init = function() { 39 | this.files = []; 40 | this.queue = []; 41 | this.id = ++Uploader.count; 42 | this.on('uploadcomplete', (function(_this) { 43 | return function(e, file) { 44 | _this.files.splice($.inArray(file, _this.files), 1); 45 | if (_this.queue.length > 0 && _this.files.length < _this.opts.connectionCount) { 46 | return _this.upload(_this.queue.shift()); 47 | } else { 48 | return _this.uploading = false; 49 | } 50 | }; 51 | })(this)); 52 | return $(window).on('beforeunload.uploader-' + this.id, (function(_this) { 53 | return function(e) { 54 | if (!_this.uploading) { 55 | return; 56 | } 57 | e.originalEvent.returnValue = _this._t('leaveConfirm'); 58 | return _this._t('leaveConfirm'); 59 | }; 60 | })(this)); 61 | }; 62 | 63 | Uploader.prototype.generateId = (function() { 64 | var id; 65 | id = 0; 66 | return function() { 67 | return id += 1; 68 | }; 69 | })(); 70 | 71 | Uploader.prototype.upload = function(file, opts) { 72 | var f, i, key, len; 73 | if (opts == null) { 74 | opts = {}; 75 | } 76 | if (file == null) { 77 | return; 78 | } 79 | if ($.isArray(file) || file instanceof FileList) { 80 | for (i = 0, len = file.length; i < len; i++) { 81 | f = file[i]; 82 | this.upload(f, opts); 83 | } 84 | } else if ($(file).is('input:file')) { 85 | key = $(file).attr('name'); 86 | if (key) { 87 | opts.fileKey = key; 88 | } 89 | this.upload($.makeArray($(file)[0].files), opts); 90 | } else if (!file.id || !file.obj) { 91 | file = this.getFile(file); 92 | } 93 | if (!(file && file.obj)) { 94 | return; 95 | } 96 | $.extend(file, opts); 97 | if (this.files.length >= this.opts.connectionCount) { 98 | this.queue.push(file); 99 | return; 100 | } 101 | if (this.triggerHandler('beforeupload', [file]) === false) { 102 | return; 103 | } 104 | this.files.push(file); 105 | this._xhrUpload(file); 106 | return this.uploading = true; 107 | }; 108 | 109 | Uploader.prototype.getFile = function(fileObj) { 110 | var name, ref, ref1; 111 | if (fileObj instanceof window.File || fileObj instanceof window.Blob) { 112 | name = (ref = fileObj.fileName) != null ? ref : fileObj.name; 113 | } else { 114 | return null; 115 | } 116 | return { 117 | id: this.generateId(), 118 | url: this.opts.url, 119 | params: this.opts.params, 120 | fileKey: this.opts.fileKey, 121 | name: name, 122 | size: (ref1 = fileObj.fileSize) != null ? ref1 : fileObj.size, 123 | ext: name ? name.split('.').pop().toLowerCase() : '', 124 | obj: fileObj 125 | }; 126 | }; 127 | 128 | Uploader.prototype._xhrUpload = function(file) { 129 | var formData, k, ref, v; 130 | formData = new FormData(); 131 | formData.append(file.fileKey, file.obj); 132 | formData.append("original_filename", file.name); 133 | if (file.params) { 134 | ref = file.params; 135 | for (k in ref) { 136 | v = ref[k]; 137 | formData.append(k, v); 138 | } 139 | } 140 | return file.xhr = $.ajax({ 141 | url: file.url, 142 | data: formData, 143 | processData: false, 144 | contentType: false, 145 | type: 'POST', 146 | headers: { 147 | 'X-File-Name': encodeURIComponent(file.name) 148 | }, 149 | xhr: function() { 150 | var req; 151 | req = $.ajaxSettings.xhr(); 152 | if (req) { 153 | req.upload.onprogress = (function(_this) { 154 | return function(e) { 155 | return _this.progress(e); 156 | }; 157 | })(this); 158 | } 159 | return req; 160 | }, 161 | progress: (function(_this) { 162 | return function(e) { 163 | if (!e.lengthComputable) { 164 | return; 165 | } 166 | return _this.trigger('uploadprogress', [file, e.loaded, e.total]); 167 | }; 168 | })(this), 169 | error: (function(_this) { 170 | return function(xhr, status, err) { 171 | return _this.trigger('uploaderror', [file, xhr, status]); 172 | }; 173 | })(this), 174 | success: (function(_this) { 175 | return function(result) { 176 | _this.trigger('uploadprogress', [file, file.size, file.size]); 177 | _this.trigger('uploadsuccess', [file, result]); 178 | return $(document).trigger('uploadsuccess', [file, result, _this]); 179 | }; 180 | })(this), 181 | complete: (function(_this) { 182 | return function(xhr, status) { 183 | return _this.trigger('uploadcomplete', [file, xhr.responseText]); 184 | }; 185 | })(this) 186 | }); 187 | }; 188 | 189 | Uploader.prototype.cancel = function(file) { 190 | var f, i, len, ref; 191 | if (!file.id) { 192 | ref = this.files; 193 | for (i = 0, len = ref.length; i < len; i++) { 194 | f = ref[i]; 195 | if (f.id === file * 1) { 196 | file = f; 197 | break; 198 | } 199 | } 200 | } 201 | this.trigger('uploadcancel', [file]); 202 | if (file.xhr) { 203 | file.xhr.abort(); 204 | } 205 | return file.xhr = null; 206 | }; 207 | 208 | Uploader.prototype.readImageFile = function(fileObj, callback) { 209 | var fileReader, img; 210 | if (!$.isFunction(callback)) { 211 | return; 212 | } 213 | img = new Image(); 214 | img.onload = function() { 215 | return callback(img); 216 | }; 217 | img.onerror = function() { 218 | return callback(); 219 | }; 220 | if (window.FileReader && FileReader.prototype.readAsDataURL && /^image/.test(fileObj.type)) { 221 | fileReader = new FileReader(); 222 | fileReader.onload = function(e) { 223 | return img.src = e.target.result; 224 | }; 225 | return fileReader.readAsDataURL(fileObj); 226 | } else { 227 | return callback(); 228 | } 229 | }; 230 | 231 | Uploader.prototype.destroy = function() { 232 | var file, i, len, ref; 233 | this.queue.length = 0; 234 | ref = this.files; 235 | for (i = 0, len = ref.length; i < len; i++) { 236 | file = ref[i]; 237 | this.cancel(file); 238 | } 239 | $(window).off('.uploader-' + this.id); 240 | return $(document).off('.uploader-' + this.id); 241 | }; 242 | 243 | Uploader.i18n = { 244 | 'zh-CN': { 245 | leaveConfirm: '正在上传文件,如果离开上传会自动取消' 246 | } 247 | }; 248 | 249 | Uploader.locale = 'zh-CN'; 250 | 251 | return Uploader; 252 | 253 | })(SimpleModule); 254 | 255 | uploader = function(opts) { 256 | return new Uploader(opts); 257 | }; 258 | 259 | return uploader; 260 | 261 | })); 262 | --------------------------------------------------------------------------------