├── server ├── node │ ├── tmp │ │ └── .gitignore │ ├── public │ │ └── files │ │ │ ├── thumbnail │ │ │ └── .gitignore │ │ │ └── .gitignore │ ├── .gitignore │ ├── package.json │ └── server.js ├── gae-go │ ├── static │ │ ├── robots.txt │ │ └── favicon.ico │ ├── app.yaml │ └── app │ │ └── main.go ├── gae-python │ ├── static │ │ ├── robots.txt │ │ └── favicon.ico │ ├── app.yaml │ └── main.py └── php │ ├── files │ ├── .gitignore │ └── .htaccess │ └── index.php ├── .gitignore ├── img ├── loading.gif └── progressbar.gif ├── css ├── style.css ├── jquery.fileupload-ui-noscript.css ├── demo-ie8.css ├── jquery.fileupload-noscript.css ├── jquery.fileupload.css ├── jquery.fileupload-ui.css └── demo.css ├── cors ├── result.html └── postmessage.html ├── blueimp-file-upload.jquery.json ├── Gruntfile.js ├── package.json ├── js ├── main.js ├── cors │ ├── jquery.xdr-transport.js │ └── jquery.postmessage-transport.js ├── jquery.fileupload-audio.js ├── jquery.fileupload-video.js ├── jquery.fileupload-validate.js ├── app.js ├── jquery.fileupload-jquery-ui.js ├── jquery.fileupload-process.js ├── jquery.iframe-transport.js ├── jquery.fileupload-image.js ├── jquery.fileupload-angular.js └── vendor │ └── jquery.ui.widget.js ├── CONTRIBUTING.md ├── bower.json ├── templates └── fileform.html ├── .jshintrc ├── angularjs.html ├── basic.html ├── test └── index.html ├── README.md ├── basic-plus.html ├── jquery-ui.html └── index.html /server/node/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/node/public/files/thumbnail/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/node/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /server/node/public/files/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /server/gae-go/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /server/gae-python/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /server/php/files/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !.htaccess 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | node_modules 4 | 5 | /bower_components/ 6 | 7 | .idea 8 | -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominicBoettger/jQuery-File-Upload/HEAD/img/loading.gif -------------------------------------------------------------------------------- /img/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominicBoettger/jQuery-File-Upload/HEAD/img/progressbar.gif -------------------------------------------------------------------------------- /server/gae-go/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominicBoettger/jQuery-File-Upload/HEAD/server/gae-go/static/favicon.ico -------------------------------------------------------------------------------- /server/gae-python/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DominicBoettger/jQuery-File-Upload/HEAD/server/gae-python/static/favicon.ico -------------------------------------------------------------------------------- /server/gae-go/app.yaml: -------------------------------------------------------------------------------- 1 | application: jquery-file-upload 2 | version: 2 3 | runtime: go 4 | api_version: go1 5 | 6 | handlers: 7 | - url: /(favicon\.ico|robots\.txt) 8 | static_files: static/\1 9 | upload: static/(.*) 10 | expiration: '1d' 11 | - url: /.* 12 | script: _go_app 13 | -------------------------------------------------------------------------------- /server/gae-python/app.yaml: -------------------------------------------------------------------------------- 1 | application: jquery-file-upload 2 | version: 1 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | builtins: 8 | - deferred: on 9 | 10 | handlers: 11 | - url: /(favicon\.ico|robots\.txt) 12 | static_files: static/\1 13 | upload: static/(.*) 14 | expiration: '1d' 15 | - url: /.* 16 | script: main.app 17 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS Example 8.8.2 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | body { 14 | padding-top: 60px; 15 | } 16 | -------------------------------------------------------------------------------- /server/php/index.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | jQuery Iframe Transport Plugin Redirect Page 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /server/php/files/.htaccess: -------------------------------------------------------------------------------- 1 | # The following directives force the content-type application/octet-stream 2 | # and force browsers to display a download dialog for non-image files. 3 | # This prevents the execution of script files in the context of the website: 4 | ForceType application/octet-stream 5 | Header set Content-Disposition attachment 6 | 7 | ForceType none 8 | Header unset Content-Disposition 9 | 10 | 11 | # The following directive prevents browsers from MIME-sniffing the content-type. 12 | # This is an important complement to the ForceType directive above: 13 | Header set X-Content-Type-Options nosniff 14 | 15 | # Uncomment the following lines to prevent unauthorized download of files: 16 | #AuthName "Authorization required" 17 | #AuthType Basic 18 | #require valid-user 19 | -------------------------------------------------------------------------------- /css/jquery.fileupload.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS 1.3.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button { 14 | position: relative; 15 | overflow: hidden; 16 | } 17 | .fileinput-button input { 18 | position: absolute; 19 | top: 0; 20 | right: 0; 21 | margin: 0; 22 | opacity: 0; 23 | -ms-filter: 'alpha(opacity=0)'; 24 | font-size: 200px; 25 | direction: ltr; 26 | cursor: pointer; 27 | } 28 | 29 | /* Fixes for IE < 8 */ 30 | @media screen\9 { 31 | .fileinput-button input { 32 | filter: alpha(opacity=0); 33 | font-size: 100%; 34 | height: 100%; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-file-upload-node", 3 | "version": "2.1.0", 4 | "title": "jQuery File Upload Node.js example", 5 | "description": "Node.js implementation example of a file upload handler for jQuery File Upload.", 6 | "keywords": [ 7 | "file", 8 | "upload", 9 | "cross-domain", 10 | "cross-site", 11 | "node" 12 | ], 13 | "homepage": "https://github.com/blueimp/jQuery-File-Upload", 14 | "author": { 15 | "name": "Sebastian Tschan", 16 | "url": "https://blueimp.net" 17 | }, 18 | "maintainers": [ 19 | { 20 | "name": "Sebastian Tschan", 21 | "url": "https://blueimp.net" 22 | } 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/blueimp/jQuery-File-Upload.git" 27 | }, 28 | "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues", 29 | "licenses": [ 30 | { 31 | "type": "MIT", 32 | "url": "http://www.opensource.org/licenses/MIT" 33 | } 34 | ], 35 | "dependencies": { 36 | "formidable": ">=1.0.11", 37 | "node-static": ">=0.6.5", 38 | "imagemagick": ">=0.1.3" 39 | }, 40 | "main": "server.js" 41 | } 42 | -------------------------------------------------------------------------------- /css/jquery.fileupload-ui.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 9.0.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2010, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileupload-buttonbar .btn, 14 | .fileupload-buttonbar .toggle { 15 | margin-bottom: 5px; 16 | } 17 | .progress-animated .progress-bar, 18 | .progress-animated .bar { 19 | background: url("../img/progressbar.gif") !important; 20 | filter: none; 21 | } 22 | .fileupload-process { 23 | float: right; 24 | display: none; 25 | } 26 | .fileupload-processing .fileupload-process, 27 | .files .processing .preview { 28 | display: block; 29 | width: 32px; 30 | height: 32px; 31 | background: url("../img/loading.gif") center no-repeat; 32 | background-size: contain; 33 | } 34 | .files audio, 35 | .files video { 36 | max-width: 300px; 37 | } 38 | 39 | @media (max-width: 767px) { 40 | .fileupload-buttonbar .toggle, 41 | .files .toggle, 42 | .files .btn span { 43 | display: none; 44 | } 45 | .files .name { 46 | width: 80px; 47 | word-wrap: break-word; 48 | } 49 | .files audio, 50 | .files video { 51 | max-width: 80px; 52 | } 53 | .files img, 54 | .files canvas { 55 | max-width: 100%; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /css/demo.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Demo CSS 1.1.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | body { 14 | max-width: 750px; 15 | margin: 0 auto; 16 | padding: 1em; 17 | font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif; 18 | font-size: 1em; 19 | line-height: 1.4em; 20 | background: #222; 21 | color: #fff; 22 | -webkit-text-size-adjust: 100%; 23 | -ms-text-size-adjust: 100%; 24 | } 25 | a { 26 | color: orange; 27 | text-decoration: none; 28 | } 29 | img { 30 | border: 0; 31 | vertical-align: middle; 32 | } 33 | h1 { 34 | line-height: 1em; 35 | } 36 | blockquote { 37 | padding: 0 0 0 15px; 38 | margin: 0 0 20px; 39 | border-left: 5px solid #eee; 40 | } 41 | table { 42 | width: 100%; 43 | margin: 10px 0; 44 | } 45 | 46 | .fileupload-progress { 47 | margin: 10px 0; 48 | } 49 | .fileupload-progress .progress-extended { 50 | margin-top: 5px; 51 | } 52 | .error { 53 | color: red; 54 | } 55 | 56 | @media (min-width: 481px) { 57 | .navigation { 58 | list-style: none; 59 | padding: 0; 60 | } 61 | .navigation li { 62 | display: inline-block; 63 | } 64 | .navigation li:not(:first-child):before { 65 | content: "| "; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /blueimp-file-upload.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-file-upload", 3 | "version": "9.5.7", 4 | "title": "jQuery File Upload", 5 | "author": { 6 | "name": "Sebastian Tschan", 7 | "url": "https://blueimp.net" 8 | }, 9 | "licenses": [ 10 | { 11 | "type": "MIT", 12 | "url": "http://www.opensource.org/licenses/MIT" 13 | } 14 | ], 15 | "dependencies": { 16 | "jquery": ">=1.6" 17 | }, 18 | "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.", 19 | "keywords": [ 20 | "jquery", 21 | "file", 22 | "upload", 23 | "widget", 24 | "multiple", 25 | "selection", 26 | "drag", 27 | "drop", 28 | "progress", 29 | "preview", 30 | "cross-domain", 31 | "cross-site", 32 | "chunk", 33 | "resume", 34 | "gae", 35 | "go", 36 | "python", 37 | "php", 38 | "bootstrap" 39 | ], 40 | "homepage": "https://github.com/blueimp/jQuery-File-Upload", 41 | "docs": "https://github.com/blueimp/jQuery-File-Upload/wiki", 42 | "demo": "http://blueimp.github.io/jQuery-File-Upload/", 43 | "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues", 44 | "maintainers": [ 45 | { 46 | "name": "Sebastian Tschan", 47 | "url": "https://blueimp.net" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Gruntfile 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*global module */ 13 | 14 | module.exports = function (grunt) { 15 | 'use strict'; 16 | 17 | grunt.initConfig({ 18 | jshint: { 19 | options: { 20 | jshintrc: '.jshintrc' 21 | }, 22 | all: [ 23 | 'Gruntfile.js', 24 | 'js/cors/*.js', 25 | 'js/*.js', 26 | 'server/node/server.js', 27 | 'test/test.js' 28 | ] 29 | }, 30 | concat: { 31 | dist: { 32 | src: [ 33 | 'js/vendor/jquery.ui.widget.js', 34 | 'js/jquery.iframe-transport.js', 35 | 'js/jquery.fileupload.js', 36 | 'js/jquery.fileupload-process.js', 37 | 'js/jquery.fileupload-image.js', 38 | 'js/jquery.fileupload-audio.js', 39 | 'js/jquery.fileupload-video.js', 40 | 'js/jquery.fileupload-validate.js', 41 | 'js/jquery.fileupload-angular.js', 42 | 'js/app.js' 43 | ], 44 | dest: 'dist/uploader.js' 45 | } 46 | }, 47 | uglify: { 48 | dist: { 49 | files: { 50 | 'dist/uploader.min.js': ['dist/uploader.js'] 51 | } 52 | } 53 | } 54 | }); 55 | 56 | grunt.loadNpmTasks('grunt-contrib-jshint'); 57 | grunt.loadNpmTasks('grunt-bump-build-git'); 58 | grunt.loadNpmTasks('grunt-contrib-concat'); 59 | grunt.loadNpmTasks('grunt-contrib-uglify'); 60 | grunt.registerTask('test', ['jshint']); 61 | grunt.registerTask('build', ['concat', 'uglify']); 62 | grunt.registerTask('default', ['test']); 63 | 64 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-jqfile-upload", 3 | "version": "1.0.2", 4 | "title": "angular jQuery File Upload", 5 | "description": "Fork of blueimp jquery file upload File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.", 6 | "keywords": [ 7 | "jquery", 8 | "file", 9 | "upload", 10 | "widget", 11 | "multiple", 12 | "selection", 13 | "drag", 14 | "drop", 15 | "progress", 16 | "preview", 17 | "cross-domain", 18 | "cross-site", 19 | "chunk", 20 | "resume", 21 | "gae", 22 | "go", 23 | "python", 24 | "php", 25 | "bootstrap" 26 | ], 27 | "homepage": "https://github.com/DominicBoettger/jQuery-File-Upload", 28 | "author": { 29 | "name": "Sebastian Tschan", 30 | "url": "https://blueimp.net" 31 | }, 32 | "maintainers": [ 33 | { 34 | "name": "Dominic Böttger", 35 | "url": "https://github.com/DominicBoettger" 36 | } 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/DominicBoettger/jQuery-File-Upload.git" 41 | }, 42 | "bugs": "https://github.com/DominicBoettger/jQuery-File-Upload/issues", 43 | "licenses": [ 44 | { 45 | "type": "MIT", 46 | "url": "http://www.opensource.org/licenses/MIT" 47 | } 48 | ], 49 | "devDependencies": { 50 | "grunt": "~0.4.2", 51 | "grunt-bump-build-git": "~1.1.1", 52 | "grunt-contrib-jshint": "~0.8.0", 53 | "grunt-contrib-concat": ">=0.4.0", 54 | "grunt-contrib-uglify": ">=0.0.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cors/postmessage.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | jQuery File Upload Plugin postMessage API 18 | 19 | 20 | 21 | 74 | 75 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin JS Example 8.9.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global $, window */ 13 | 14 | $(function () { 15 | 'use strict'; 16 | 17 | // Initialize the jQuery File Upload widget: 18 | $('#fileupload').fileupload({ 19 | // Uncomment the following to send cross-domain cookies: 20 | //xhrFields: {withCredentials: true}, 21 | url: 'server/php/' 22 | }); 23 | 24 | // Enable iframe cross-domain access via redirect option: 25 | $('#fileupload').fileupload( 26 | 'option', 27 | 'redirect', 28 | window.location.href.replace( 29 | /\/[^\/]*$/, 30 | '/cors/result.html?%s' 31 | ) 32 | ); 33 | 34 | if (window.location.hostname === 'blueimp.github.io') { 35 | // Demo settings: 36 | $('#fileupload').fileupload('option', { 37 | url: '//jquery-file-upload.appspot.com/', 38 | // Enable image resizing, except for Android and Opera, 39 | // which actually support image resizing, but fail to 40 | // send Blob objects via XHR requests: 41 | disableImageResize: /Android(?!.*Chrome)|Opera/ 42 | .test(window.navigator.userAgent), 43 | maxFileSize: 5000000, 44 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i 45 | }); 46 | // Upload server status check for browsers with CORS support: 47 | if ($.support.cors) { 48 | $.ajax({ 49 | url: '//jquery-file-upload.appspot.com/', 50 | type: 'HEAD' 51 | }).fail(function () { 52 | $('
') 53 | .text('Upload server currently unavailable - ' + 54 | new Date()) 55 | .appendTo('#fileupload'); 56 | }); 57 | } 58 | } else { 59 | // Load existing files: 60 | $('#fileupload').addClass('fileupload-processing'); 61 | $.ajax({ 62 | // Uncomment the following to send cross-domain cookies: 63 | //xhrFields: {withCredentials: true}, 64 | url: $('#fileupload').fileupload('option', 'url'), 65 | dataType: 'json', 66 | context: $('#fileupload')[0] 67 | }).always(function () { 68 | $(this).removeClass('fileupload-processing'); 69 | }).done(function (result) { 70 | $(this).fileupload('option', 'done') 71 | .call(this, $.Event('done'), {result: result}); 72 | }); 73 | } 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Issue Guidelines 2 | 3 | The issues tracker should only be used for **bugs** or **feature requests**. 4 | 5 | Please post **support requests** and **general discussions** about this project to the [support forum](https://groups.google.com/d/forum/jquery-fileupload). 6 | 7 | ## Bugs 8 | 9 | Please follow these guidelines before reporting a bug: 10 | 11 | 1. **Update to the latest version** — Check if you can reproduce the issue with the latest version from the `master` branch. 12 | 13 | 2. **Use the GitHub issue search** — check if the issue has already been reported. If it has been, please comment on the existing issue. 14 | 15 | 3. **Isolate the demonstrable problem** — Try to reproduce the problem with the [Demo](http://blueimp.github.io/jQuery-File-Upload/) or with a reduced test case that includes the least amount of code necessary to reproduce the problem. 16 | 17 | 4. **Provide a means to reproduce the problem** — Please provide as much details as possible, e.g. server information, browser and operating system versions, steps to reproduce the problem. If possible, provide a link to your reduced test case, e.g. via [JSFiddle](http://jsfiddle.net/). 18 | 19 | 20 | ## Feature requests 21 | 22 | Please follow the bug guidelines above for feature requests, i.e. update to the latest version and search for exising issues before posting a new request. 23 | 24 | Generally, feature requests might be accepted if the implementation would benefit a broader use case or the project could be considered incomplete without that feature. 25 | 26 | If you need help integrating this project into another framework, please post your request to the [support forum](https://groups.google.com/d/forum/jquery-fileupload). 27 | 28 | ## Pull requests 29 | 30 | [Pull requests](https://help.github.com/articles/using-pull-requests) are welcome and the preferred way of accepting code contributions. 31 | 32 | However, if you add a server-side upload handler implementation for another framework, please continue to maintain this version in your own fork without sending a pull request. You are welcome to add a link and possibly documentation about your implementation to the [Wiki](https://github.com/blueimp/jQuery-File-Upload/wiki). 33 | 34 | Please follow these guidelines before sending a pull request: 35 | 36 | 1. Update your fork to the latest upstream version. 37 | 38 | 2. Follow the coding conventions of the original repository. Changes to one of the JavaScript source files are required to pass the [JSLint](http://jslint.com/) validation tool. 39 | 40 | 3. Keep your commits as atomic as possible, i.e. create a new commit for every single bug fix or feature added. 41 | 42 | 4. Always add meaningful commit messages. 43 | -------------------------------------------------------------------------------- /js/cors/jquery.xdr-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery XDomainRequest Transport Plugin 1.1.3 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Based on Julian Aubourg's ajaxHooks xdr.js: 12 | * https://github.com/jaubourg/ajaxHooks/ 13 | */ 14 | 15 | /* global define, window, XDomainRequest */ 16 | 17 | (function (factory) { 18 | 'use strict'; 19 | if (typeof define === 'function' && define.amd) { 20 | // Register as an anonymous AMD module: 21 | define(['jquery'], factory); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | if (window.XDomainRequest && !$.support.cors) { 29 | $.ajaxTransport(function (s) { 30 | if (s.crossDomain && s.async) { 31 | if (s.timeout) { 32 | s.xdrTimeout = s.timeout; 33 | delete s.timeout; 34 | } 35 | var xdr; 36 | return { 37 | send: function (headers, completeCallback) { 38 | var addParamChar = /\?/.test(s.url) ? '&' : '?'; 39 | 40 | function callback(status, statusText, responses, responseHeaders) { 41 | xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; 42 | xdr = null; 43 | completeCallback(status, statusText, responses, responseHeaders); 44 | } 45 | 46 | xdr = new XDomainRequest(); 47 | // XDomainRequest only supports GET and POST: 48 | if (s.type === 'DELETE') { 49 | s.url = s.url + addParamChar + '_method=DELETE'; 50 | s.type = 'POST'; 51 | } else if (s.type === 'PUT') { 52 | s.url = s.url + addParamChar + '_method=PUT'; 53 | s.type = 'POST'; 54 | } else if (s.type === 'PATCH') { 55 | s.url = s.url + addParamChar + '_method=PATCH'; 56 | s.type = 'POST'; 57 | } 58 | xdr.open(s.type, s.url); 59 | xdr.onload = function () { 60 | callback( 61 | 200, 62 | 'OK', 63 | {text: xdr.responseText}, 64 | 'Content-Type: ' + xdr.contentType 65 | ); 66 | }; 67 | xdr.onerror = function () { 68 | callback(404, 'Not Found'); 69 | }; 70 | if (s.xdrTimeout) { 71 | xdr.ontimeout = function () { 72 | callback(0, 'timeout'); 73 | }; 74 | xdr.timeout = s.xdrTimeout; 75 | } 76 | xdr.send((s.hasContent && s.data) || null); 77 | }, 78 | abort: function () { 79 | if (xdr) { 80 | xdr.onerror = $.noop(); 81 | xdr.abort(); 82 | } 83 | } 84 | }; 85 | } 86 | }); 87 | } 88 | })); 89 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-jqfile-upload", 3 | "version": "1.0.4", 4 | "title": "angular jQuery File Upload", 5 | "description": "Fork of blueimp jquery file upload File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.", 6 | "keywords": [ 7 | "jquery", 8 | "file", 9 | "upload", 10 | "widget", 11 | "multiple", 12 | "selection", 13 | "drag", 14 | "drop", 15 | "progress", 16 | "preview", 17 | "cross-domain", 18 | "cross-site", 19 | "chunk", 20 | "resume", 21 | "gae", 22 | "go", 23 | "python", 24 | "php", 25 | "bootstrap" 26 | ], 27 | "homepage": "https://github.com/DominicBoettger/jQuery-File-Upload", 28 | "authors": [ 29 | { 30 | "name": "Sebastian Tschan", 31 | "url": "https://blueimp.net" 32 | }, 33 | { 34 | "name": "Dominic Böttger", 35 | "url": "http://inspirationlabs.com" 36 | } 37 | ], 38 | "maintainers": [ 39 | { 40 | "name": "Dominic Böttger", 41 | "url": "https://github.com/DominicBoettger" 42 | } 43 | ], 44 | "repository": { 45 | "type": "git", 46 | "url": "git://github.com/DominicBoettger/jQuery-File-Upload.git" 47 | }, 48 | "bugs": "https://github.com/DominicBoettger/jQuery-File-Upload/issues", 49 | "licenses": [ 50 | { 51 | "type": "MIT", 52 | "url": "http://www.opensource.org/licenses/MIT" 53 | } 54 | ], 55 | "dependencies": { 56 | "blueimp-tmpl": ">=2.5.3", 57 | "blueimp-load-image": ">=1.11.0", 58 | "blueimp-canvas-to-blob": ">=2.1.0", 59 | "blueimp-gallery": ">=2.14.0", 60 | "bootstrap": ">=3.1.1", 61 | "jquery": ">=2.0", 62 | "angular": ">=1.2.12", 63 | "blueimp-load-image": "latest", 64 | "angular-bootstrap": "latest" 65 | }, 66 | "main": [ 67 | "css/jquery.fileupload.css", 68 | "css/jquery.fileupload-ui.css", 69 | "css/jquery.fileupload-noscript.css", 70 | "css/jquery.fileupload-ui-noscript.css", 71 | "js/cors/jquery.postmessage-transport.js", 72 | "js/cors/jquery.xdr-transport.js", 73 | "js/vendor/jquery.ui.widget.js", 74 | "js/jquery.fileupload.js", 75 | "js/jquery.fileupload-process.js", 76 | "js/jquery.fileupload-validate.js", 77 | "js/jquery.fileupload-image.js", 78 | "js/jquery.fileupload-audio.js", 79 | "js/jquery.fileupload-video.js", 80 | "js/jquery.fileupload-ui.js", 81 | "js/jquery.fileupload-jquery-ui.js", 82 | "js/jquery.fileupload-angular.js", 83 | "js/jquery.iframe-transport.js", 84 | "js/app.js" 85 | ], 86 | "ignore": [ 87 | "/*.*", 88 | "/cors", 89 | "css/demo-ie8.css", 90 | "css/demo.css", 91 | "css/style.css", 92 | "js/main.js", 93 | "server", 94 | "test" 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /js/jquery.fileupload-audio.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Audio Preview Plugin 1.0.3 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | './jquery.fileupload-process' 23 | ], factory); 24 | } else { 25 | // Browser globals: 26 | factory( 27 | window.jQuery, 28 | window.loadImage 29 | ); 30 | } 31 | }(function ($, loadImage) { 32 | 'use strict'; 33 | 34 | // Prepend to the default processQueue: 35 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 36 | { 37 | action: 'loadAudio', 38 | // Use the action as prefix for the "@" options: 39 | prefix: true, 40 | fileTypes: '@', 41 | maxFileSize: '@', 42 | disabled: '@disableAudioPreview' 43 | }, 44 | { 45 | action: 'setAudio', 46 | name: '@audioPreviewName', 47 | disabled: '@disableAudioPreview' 48 | } 49 | ); 50 | 51 | // The File Upload Audio Preview plugin extends the fileupload widget 52 | // with audio preview functionality: 53 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 54 | 55 | options: { 56 | // The regular expression for the types of audio files to load, 57 | // matched against the file type: 58 | loadAudioFileTypes: /^audio\/.*$/ 59 | }, 60 | 61 | _audioElement: document.createElement('audio'), 62 | 63 | processActions: { 64 | 65 | // Loads the audio file given via data.files and data.index 66 | // as audio element if the browser supports playing it. 67 | // Accepts the options fileTypes (regular expression) 68 | // and maxFileSize (integer) to limit the files to load: 69 | loadAudio: function (data, options) { 70 | if (options.disabled) { 71 | return data; 72 | } 73 | var file = data.files[data.index], 74 | url, 75 | audio; 76 | if (this._audioElement.canPlayType && 77 | this._audioElement.canPlayType(file.type) && 78 | ($.type(options.maxFileSize) !== 'number' || 79 | file.size <= options.maxFileSize) && 80 | (!options.fileTypes || 81 | options.fileTypes.test(file.type))) { 82 | url = loadImage.createObjectURL(file); 83 | if (url) { 84 | audio = this._audioElement.cloneNode(false); 85 | audio.src = url; 86 | audio.controls = true; 87 | data.audio = audio; 88 | return data; 89 | } 90 | } 91 | return data; 92 | }, 93 | 94 | // Sets the audio element as a property of the file object: 95 | setAudio: function (data, options) { 96 | if (data.audio && !options.disabled) { 97 | data.files[data.index][options.name || 'preview'] = data.audio; 98 | } 99 | return data; 100 | } 101 | 102 | } 103 | 104 | }); 105 | 106 | })); 107 | -------------------------------------------------------------------------------- /js/jquery.fileupload-video.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Video Preview Plugin 1.0.3 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | './jquery.fileupload-process' 23 | ], factory); 24 | } else { 25 | // Browser globals: 26 | factory( 27 | window.jQuery, 28 | window.loadImage 29 | ); 30 | } 31 | }(function ($, loadImage) { 32 | 'use strict'; 33 | 34 | // Prepend to the default processQueue: 35 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 36 | { 37 | action: 'loadVideo', 38 | // Use the action as prefix for the "@" options: 39 | prefix: true, 40 | fileTypes: '@', 41 | maxFileSize: '@', 42 | disabled: '@disableVideoPreview' 43 | }, 44 | { 45 | action: 'setVideo', 46 | name: '@videoPreviewName', 47 | disabled: '@disableVideoPreview' 48 | } 49 | ); 50 | 51 | // The File Upload Video Preview plugin extends the fileupload widget 52 | // with video preview functionality: 53 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 54 | 55 | options: { 56 | // The regular expression for the types of video files to load, 57 | // matched against the file type: 58 | loadVideoFileTypes: /^video\/.*$/ 59 | }, 60 | 61 | _videoElement: document.createElement('video'), 62 | 63 | processActions: { 64 | 65 | // Loads the video file given via data.files and data.index 66 | // as video element if the browser supports playing it. 67 | // Accepts the options fileTypes (regular expression) 68 | // and maxFileSize (integer) to limit the files to load: 69 | loadVideo: function (data, options) { 70 | if (options.disabled) { 71 | return data; 72 | } 73 | var file = data.files[data.index], 74 | url, 75 | video; 76 | if (this._videoElement.canPlayType && 77 | this._videoElement.canPlayType(file.type) && 78 | ($.type(options.maxFileSize) !== 'number' || 79 | file.size <= options.maxFileSize) && 80 | (!options.fileTypes || 81 | options.fileTypes.test(file.type))) { 82 | url = loadImage.createObjectURL(file); 83 | if (url) { 84 | video = this._videoElement.cloneNode(false); 85 | video.src = url; 86 | video.controls = true; 87 | data.video = video; 88 | return data; 89 | } 90 | } 91 | return data; 92 | }, 93 | 94 | // Sets the video element as a property of the file object: 95 | setVideo: function (data, options) { 96 | if (data.video && !options.disabled) { 97 | data.files[data.index][options.name || 'preview'] = data.video; 98 | } 99 | return data; 100 | } 101 | 102 | } 103 | 104 | }); 105 | 106 | })); 107 | -------------------------------------------------------------------------------- /js/cors/jquery.postmessage-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage Transport Plugin 1.1.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, window, document */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else { 20 | // Browser globals: 21 | factory(window.jQuery); 22 | } 23 | }(function ($) { 24 | 'use strict'; 25 | 26 | var counter = 0, 27 | names = [ 28 | 'accepts', 29 | 'cache', 30 | 'contents', 31 | 'contentType', 32 | 'crossDomain', 33 | 'data', 34 | 'dataType', 35 | 'headers', 36 | 'ifModified', 37 | 'mimeType', 38 | 'password', 39 | 'processData', 40 | 'timeout', 41 | 'traditional', 42 | 'type', 43 | 'url', 44 | 'username' 45 | ], 46 | convert = function (p) { 47 | return p; 48 | }; 49 | 50 | $.ajaxSetup({ 51 | converters: { 52 | 'postmessage text': convert, 53 | 'postmessage json': convert, 54 | 'postmessage html': convert 55 | } 56 | }); 57 | 58 | $.ajaxTransport('postmessage', function (options) { 59 | if (options.postMessage && window.postMessage) { 60 | var iframe, 61 | loc = $('').prop('href', options.postMessage)[0], 62 | target = loc.protocol + '//' + loc.host, 63 | xhrUpload = options.xhr().upload; 64 | return { 65 | send: function (_, completeCallback) { 66 | counter += 1; 67 | var message = { 68 | id: 'postmessage-transport-' + counter 69 | }, 70 | eventName = 'message.' + message.id; 71 | iframe = $( 72 | '' 75 | ).bind('load', function () { 76 | $.each(names, function (i, name) { 77 | message[name] = options[name]; 78 | }); 79 | message.dataType = message.dataType.replace('postmessage ', ''); 80 | $(window).bind(eventName, function (e) { 81 | e = e.originalEvent; 82 | var data = e.data, 83 | ev; 84 | if (e.origin === target && data.id === message.id) { 85 | if (data.type === 'progress') { 86 | ev = document.createEvent('Event'); 87 | ev.initEvent(data.type, false, true); 88 | $.extend(ev, data); 89 | xhrUpload.dispatchEvent(ev); 90 | } else { 91 | completeCallback( 92 | data.status, 93 | data.statusText, 94 | {postmessage: data.result}, 95 | data.headers 96 | ); 97 | iframe.remove(); 98 | $(window).unbind(eventName); 99 | } 100 | } 101 | }); 102 | iframe[0].contentWindow.postMessage( 103 | message, 104 | target 105 | ); 106 | }).appendTo(document.body); 107 | }, 108 | abort: function () { 109 | if (iframe) { 110 | iframe.remove(); 111 | } 112 | } 113 | }; 114 | } 115 | }); 116 | 117 | })); 118 | -------------------------------------------------------------------------------- /js/jquery.fileupload-validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Validation Plugin 1.1.2 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, window */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define([ 19 | 'jquery', 20 | './jquery.fileupload-process' 21 | ], factory); 22 | } else { 23 | // Browser globals: 24 | factory( 25 | window.jQuery 26 | ); 27 | } 28 | }(function ($) { 29 | 'use strict'; 30 | 31 | // Append to the default processQueue: 32 | $.blueimp.fileupload.prototype.options.processQueue.push( 33 | { 34 | action: 'validate', 35 | // Always trigger this action, 36 | // even if the previous action was rejected: 37 | always: true, 38 | // Options taken from the global options map: 39 | acceptFileTypes: '@', 40 | maxFileSize: '@', 41 | minFileSize: '@', 42 | maxNumberOfFiles: '@', 43 | disabled: '@disableValidation' 44 | } 45 | ); 46 | 47 | // The File Upload Validation plugin extends the fileupload widget 48 | // with file validation functionality: 49 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 50 | 51 | options: { 52 | /* 53 | // The regular expression for allowed file types, matches 54 | // against either file type or file name: 55 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 56 | // The maximum allowed file size in bytes: 57 | maxFileSize: 10000000, // 10 MB 58 | // The minimum allowed file size in bytes: 59 | minFileSize: undefined, // No minimal file size 60 | // The limit of files to be uploaded: 61 | maxNumberOfFiles: 10, 62 | */ 63 | 64 | // Function returning the current number of files, 65 | // has to be overriden for maxNumberOfFiles validation: 66 | getNumberOfFiles: $.noop, 67 | 68 | // Error and info messages: 69 | messages: { 70 | maxNumberOfFiles: 'Maximum number of files exceeded', 71 | acceptFileTypes: 'File type not allowed', 72 | maxFileSize: 'File is too large', 73 | minFileSize: 'File is too small' 74 | } 75 | }, 76 | 77 | processActions: { 78 | 79 | validate: function (data, options) { 80 | if (options.disabled) { 81 | return data; 82 | } 83 | var dfd = $.Deferred(), 84 | settings = this.options, 85 | file = data.files[data.index], 86 | fileSize; 87 | if (options.minFileSize || options.maxFileSize) { 88 | fileSize = file.size; 89 | } 90 | if ($.type(options.maxNumberOfFiles) === 'number' && 91 | (settings.getNumberOfFiles() || 0) + data.files.length > 92 | options.maxNumberOfFiles) { 93 | file.error = settings.i18n('maxNumberOfFiles'); 94 | } else if (options.acceptFileTypes && !(options.acceptFileTypes.test(file.type) || 95 | options.acceptFileTypes.test(file.name))) { 96 | file.error = settings.i18n('acceptFileTypes'); 97 | } else if (fileSize > options.maxFileSize) { 98 | file.error = settings.i18n('maxFileSize'); 99 | } else if ($.type(fileSize) === 'number' && 100 | fileSize < options.minFileSize) { 101 | file.error = settings.i18n('minFileSize'); 102 | } else { 103 | delete file.error; 104 | } 105 | if (file.error || data.files.error) { 106 | data.files.error = true; 107 | dfd.rejectWith(this, [data]); 108 | } else { 109 | dfd.resolveWith(this, [data]); 110 | } 111 | return dfd.promise(); 112 | } 113 | 114 | } 115 | 116 | }); 117 | 118 | })); 119 | -------------------------------------------------------------------------------- /templates/fileform.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | 8 | 9 | Add files... 10 | 11 | 12 | 16 | 20 | 21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
 
29 |
30 |
31 | 32 | 33 | 34 | 40 | 50 | 54 | 68 | 69 |
35 |
36 | 37 |
38 |
39 |
41 |

42 | 43 | {{file.name}} 44 | {{file.name}} 45 | 46 | {{file.name}} 47 |

48 | {{file.error}} 49 |
51 |

{{file.size | formatFileSize}}

52 |
53 |
55 | 59 | 63 | 67 |
70 |
-------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin Angular JS Example 1.2.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global angular */ 14 | 15 | (function () { 16 | 'use strict'; 17 | var app = angular.module('uploadModule', [ 18 | 'blueimp.fileupload' 19 | ]); 20 | 21 | app.directive('ngUploadForm', [function () { 22 | return { 23 | restrict: 'E', 24 | templateUrl: './templates/fileform.html', 25 | scope: { 26 | allowed: '@', 27 | url: '@', 28 | autoUpload: '@', 29 | sizeLimit: '@', 30 | ngModel: '=', 31 | name: '@' 32 | }, 33 | controller: ['$scope', '$element', 'fileUpload', function ( 34 | $scope, $element, fileUpload) { 35 | $scope.$on('fileuploaddone', function (e, data) { 36 | fileUpload.addFieldData($scope.name, data._response.result.files[0].result); 37 | }); 38 | 39 | $scope.options = { 40 | url: $scope.url, 41 | dropZone: $element, 42 | maxFileSize: $scope.sizeLimit, 43 | autoUpload: $scope.autoUpload 44 | }; 45 | $scope.loadingFiles = false; 46 | 47 | if (!$scope.queue) { 48 | $scope.queue = []; 49 | } 50 | 51 | var generateFileObject = function generateFileObjects(objects) { 52 | angular.forEach(objects, function (value, key) { 53 | var fileObject = { 54 | name: value.filename, 55 | size: value.length, 56 | url: value.url, 57 | thumbnailUrl: value.url, 58 | deleteUrl: value.url, 59 | deleteType: 'DELETE', 60 | result: value 61 | }; 62 | 63 | if (fileObject.url && fileObject.url.charAt(0) !== '/') { 64 | fileObject.url = '/' + fileObject.url; 65 | } 66 | 67 | if (fileObject.deleteUrl && fileObject.deleteUrl.charAt(0) !== '/') { 68 | fileObject.deleteUrl = '/' + fileObject.deleteUrl; 69 | } 70 | 71 | if (fileObject.thumbnailUrl && fileObject.thumbnailUrl.charAt(0) !== '/') { 72 | fileObject.thumbnailUrl = '/' + fileObject.thumbnailUrl; 73 | } 74 | 75 | $scope.queue[key] = fileObject; 76 | }); 77 | }; 78 | fileUpload.registerField($scope.name); 79 | $scope.filequeue = fileUpload.fieldData[$scope.name]; 80 | 81 | $scope.$watchCollection('filequeue', function (newval) { 82 | generateFileObject(newval); 83 | }); 84 | }] 85 | }; 86 | }]) 87 | .controller('FileDestroyController', ['$scope', '$http', 'fileUpload', function ( 88 | $scope, $http, fileUpload) { 89 | var file = $scope.file, 90 | state; 91 | 92 | if ($scope.$parent && $scope.$parent.$parent && $scope.$parent.$parent.$parent.name) { 93 | $scope.fieldname = $scope.$parent.$parent.$parent.name; 94 | } 95 | 96 | if (!fileUpload.fieldData[$scope.name]) { 97 | fileUpload.fieldData[$scope.name] = []; 98 | } 99 | 100 | $scope.filequeue = fileUpload.fieldData; 101 | 102 | if (file.url) { 103 | file.$state = function () { 104 | return state; 105 | }; 106 | file.$destroy = function () { 107 | state = 'pending'; 108 | return $http({ 109 | url: file.deleteUrl, 110 | method: file.deleteType 111 | }).then( 112 | function () { 113 | state = 'resolved'; 114 | fileUpload.removeFieldData($scope.fieldname, file.result._id); 115 | $scope.clear(file); 116 | }, 117 | function () { 118 | state = 'rejected'; 119 | fileUpload.removeFieldData($scope.fieldname, file.result._id); 120 | $scope.clear(file); 121 | } 122 | ); 123 | 124 | 125 | }; 126 | } else if (!file.$cancel && !file._index) { 127 | file.$cancel = function () { 128 | $scope.clear(file); 129 | }; 130 | } 131 | } 132 | ]); 133 | })(); -------------------------------------------------------------------------------- /js/jquery.fileupload-jquery-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload jQuery UI Plugin 8.7.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, window */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define(['jquery', './jquery.fileupload-ui'], factory); 20 | } else { 21 | // Browser globals: 22 | factory(window.jQuery); 23 | } 24 | }(function ($) { 25 | 'use strict'; 26 | 27 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 28 | 29 | options: { 30 | processdone: function (e, data) { 31 | data.context.find('.start').button('enable'); 32 | }, 33 | progress: function (e, data) { 34 | if (data.context) { 35 | data.context.find('.progress').progressbar( 36 | 'option', 37 | 'value', 38 | parseInt(data.loaded / data.total * 100, 10) 39 | ); 40 | } 41 | }, 42 | progressall: function (e, data) { 43 | var $this = $(this); 44 | $this.find('.fileupload-progress') 45 | .find('.progress').progressbar( 46 | 'option', 47 | 'value', 48 | parseInt(data.loaded / data.total * 100, 10) 49 | ).end() 50 | .find('.progress-extended').each(function () { 51 | $(this).html( 52 | ($this.data('blueimp-fileupload') || 53 | $this.data('fileupload')) 54 | ._renderExtendedProgress(data) 55 | ); 56 | }); 57 | } 58 | }, 59 | 60 | _renderUpload: function (func, files) { 61 | var node = this._super(func, files), 62 | showIconText = $(window).width() > 480; 63 | node.find('.progress').empty().progressbar(); 64 | node.find('.start').button({ 65 | icons: {primary: 'ui-icon-circle-arrow-e'}, 66 | text: showIconText 67 | }); 68 | node.find('.cancel').button({ 69 | icons: {primary: 'ui-icon-cancel'}, 70 | text: showIconText 71 | }); 72 | if (node.hasClass('fade')) { 73 | node.hide(); 74 | } 75 | return node; 76 | }, 77 | 78 | _renderDownload: function (func, files) { 79 | var node = this._super(func, files), 80 | showIconText = $(window).width() > 480; 81 | node.find('.delete').button({ 82 | icons: {primary: 'ui-icon-trash'}, 83 | text: showIconText 84 | }); 85 | if (node.hasClass('fade')) { 86 | node.hide(); 87 | } 88 | return node; 89 | }, 90 | 91 | _startHandler: function (e) { 92 | $(e.currentTarget).button('disable'); 93 | this._super(e); 94 | }, 95 | 96 | _transition: function (node) { 97 | var deferred = $.Deferred(); 98 | if (node.hasClass('fade')) { 99 | node.fadeToggle( 100 | this.options.transitionDuration, 101 | this.options.transitionEasing, 102 | function () { 103 | deferred.resolveWith(node); 104 | } 105 | ); 106 | } else { 107 | deferred.resolveWith(node); 108 | } 109 | return deferred; 110 | }, 111 | 112 | _create: function () { 113 | this._super(); 114 | this.element 115 | .find('.fileupload-buttonbar') 116 | .find('.fileinput-button').each(function () { 117 | var input = $(this).find('input:file').detach(); 118 | $(this) 119 | .button({icons: {primary: 'ui-icon-plusthick'}}) 120 | .append(input); 121 | }) 122 | .end().find('.start') 123 | .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) 124 | .end().find('.cancel') 125 | .button({icons: {primary: 'ui-icon-cancel'}}) 126 | .end().find('.delete') 127 | .button({icons: {primary: 'ui-icon-trash'}}) 128 | .end().find('.progress').progressbar(); 129 | }, 130 | 131 | _destroy: function () { 132 | this.element 133 | .find('.fileupload-buttonbar') 134 | .find('.fileinput-button').each(function () { 135 | var input = $(this).find('input:file').detach(); 136 | $(this) 137 | .button('destroy') 138 | .append(input); 139 | }) 140 | .end().find('.start') 141 | .button('destroy') 142 | .end().find('.cancel') 143 | .button('destroy') 144 | .end().find('.delete') 145 | .button('destroy') 146 | .end().find('.progress').progressbar('destroy'); 147 | this._super(); 148 | } 149 | 150 | }); 151 | 152 | })); 153 | -------------------------------------------------------------------------------- /js/jquery.fileupload-process.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Processing Plugin 1.3.0 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, window */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | './jquery.fileupload' 22 | ], factory); 23 | } else { 24 | // Browser globals: 25 | factory( 26 | window.jQuery 27 | ); 28 | } 29 | }(function ($) { 30 | 'use strict'; 31 | 32 | var originalAdd = $.blueimp.fileupload.prototype.options.add; 33 | 34 | // The File Upload Processing plugin extends the fileupload widget 35 | // with file processing functionality: 36 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 37 | 38 | options: { 39 | // The list of processing actions: 40 | processQueue: [ 41 | /* 42 | { 43 | action: 'log', 44 | type: 'debug' 45 | } 46 | */ 47 | ], 48 | add: function (e, data) { 49 | var $this = $(this); 50 | data.process(function () { 51 | return $this.fileupload('process', data); 52 | }); 53 | originalAdd.call(this, e, data); 54 | } 55 | }, 56 | 57 | processActions: { 58 | /* 59 | log: function (data, options) { 60 | console[options.type]( 61 | 'Processing "' + data.files[data.index].name + '"' 62 | ); 63 | } 64 | */ 65 | }, 66 | 67 | _processFile: function (data, originalData) { 68 | var that = this, 69 | dfd = $.Deferred().resolveWith(that, [data]), 70 | chain = dfd.promise(); 71 | this._trigger('process', null, data); 72 | $.each(data.processQueue, function (i, settings) { 73 | var func = function (data) { 74 | if (originalData.errorThrown) { 75 | return $.Deferred() 76 | .rejectWith(that, [originalData]).promise(); 77 | } 78 | return that.processActions[settings.action].call( 79 | that, 80 | data, 81 | settings 82 | ); 83 | }; 84 | chain = chain.pipe(func, settings.always && func); 85 | }); 86 | chain 87 | .done(function () { 88 | that._trigger('processdone', null, data); 89 | that._trigger('processalways', null, data); 90 | }) 91 | .fail(function () { 92 | that._trigger('processfail', null, data); 93 | that._trigger('processalways', null, data); 94 | }); 95 | return chain; 96 | }, 97 | 98 | // Replaces the settings of each processQueue item that 99 | // are strings starting with an "@", using the remaining 100 | // substring as key for the option map, 101 | // e.g. "@autoUpload" is replaced with options.autoUpload: 102 | _transformProcessQueue: function (options) { 103 | var processQueue = []; 104 | $.each(options.processQueue, function () { 105 | var settings = {}, 106 | action = this.action, 107 | prefix = this.prefix === true ? action : this.prefix; 108 | $.each(this, function (key, value) { 109 | if ($.type(value) === 'string' && 110 | value.charAt(0) === '@') { 111 | settings[key] = options[ 112 | value.slice(1) || (prefix ? prefix + 113 | key.charAt(0).toUpperCase() + key.slice(1) : key) 114 | ]; 115 | } else { 116 | settings[key] = value; 117 | } 118 | 119 | }); 120 | processQueue.push(settings); 121 | }); 122 | options.processQueue = processQueue; 123 | }, 124 | 125 | // Returns the number of files currently in the processsing queue: 126 | processing: function () { 127 | return this._processing; 128 | }, 129 | 130 | // Processes the files given as files property of the data parameter, 131 | // returns a Promise object that allows to bind callbacks: 132 | process: function (data) { 133 | var that = this, 134 | options = $.extend({}, this.options, data); 135 | if (options.processQueue && options.processQueue.length) { 136 | this._transformProcessQueue(options); 137 | if (this._processing === 0) { 138 | this._trigger('processstart'); 139 | } 140 | $.each(data.files, function (index) { 141 | var opts = index ? $.extend({}, options) : options, 142 | func = function () { 143 | if (data.errorThrown) { 144 | return $.Deferred() 145 | .rejectWith(that, [data]).promise(); 146 | } 147 | return that._processFile(opts, data); 148 | }; 149 | opts.index = index; 150 | that._processing += 1; 151 | that._processingQueue = that._processingQueue.pipe(func, func) 152 | .always(function () { 153 | that._processing -= 1; 154 | if (that._processing === 0) { 155 | that._trigger('processstop'); 156 | } 157 | }); 158 | }); 159 | } 160 | return this._processingQueue; 161 | }, 162 | 163 | _create: function () { 164 | this._super(); 165 | this._processing = 0; 166 | this._processingQueue = $.Deferred().resolveWith(this) 167 | .promise(); 168 | } 169 | 170 | }); 171 | 172 | })); 173 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 3 | "camelcase" : true, // true: Identifiers must be in camelCase 4 | "curly" : true, // true: Require {} for every new block or scope 5 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 6 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 7 | "immed" : true, // true: Require immediate invocations to be wrapped in parens 8 | // e.g. `(function () { } ());` 9 | "indent" : 2, // {int} Number of spaces to use for indentation 10 | "latedef" : true, // true: Require variables/functions to be defined before being used 11 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 12 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 13 | "noempty" : true, // true: Prohibit use of empty blocks 14 | "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) 15 | "plusplus" : false, // true: Prohibit use of `++` & `--` 16 | "quotmark" : "single", // Quotation mark consistency: 17 | // false : do nothing (default) 18 | // true : ensure whatever is used is consistent 19 | // "single" : require single quotes 20 | // "double" : require double quotes 21 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 22 | "unused" : true, // true: Require all defined variables be used 23 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 24 | "trailing" : true, // true: Prohibit trailing whitespaces 25 | "maxparams" : false, // {int} Max number of formal params allowed per function 26 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 27 | "maxstatements" : false, // {int} Max number statements per function 28 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 29 | "maxlen" : false, // {int} Max number of characters per line 30 | 31 | // Relaxing 32 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 33 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 34 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 35 | "eqnull" : false, // true: Tolerate use of `== null` 36 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 37 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 38 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 39 | // (ex: `for each`, multiple try/catch, function expression…) 40 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 41 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 42 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 43 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 44 | "iterator" : false, // true: Tolerate using the `__iterator__` property 45 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 46 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 47 | "laxcomma" : false, // true: Tolerate comma-first style coding 48 | "loopfunc" : false, // true: Tolerate functions being defined in loops 49 | "multistr" : false, // true: Tolerate multi-line strings 50 | "proto" : false, // true: Tolerate using the `__proto__` property 51 | "scripturl" : false, // true: Tolerate script-targeted URLs 52 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 53 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 54 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 55 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 56 | "validthis" : false, // true: Tolerate using this in a non-constructor function 57 | 58 | // Environments 59 | "browser" : false, // Web Browser (window, document, etc) 60 | "couch" : false, // CouchDB 61 | "devel" : false, // Development/debugging (alert, confirm, etc) 62 | "dojo" : false, // Dojo Toolkit 63 | "jquery" : false, // jQuery 64 | "mootools" : false, // MooTools 65 | "node" : false, // Node.js 66 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 67 | "prototypejs" : false, // Prototype and Scriptaculous 68 | "rhino" : false, // Rhino 69 | "worker" : false, // Web Workers 70 | "wsh" : false, // Windows Scripting Host 71 | "yui" : false, // Yahoo User Interface 72 | 73 | // Legacy 74 | "nomen" : true, // true: Prohibit dangling `_` in variables 75 | "onevar" : false, // true: Allow only one `var` statement per function 76 | "passfail" : false, // true: Stop on first error 77 | "white" : true, // true: Check against strict whitespace and indentation rules 78 | 79 | // Custom Globals 80 | "globals" : {} // additional predefined global variables 81 | } 82 | -------------------------------------------------------------------------------- /server/gae-python/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # jQuery File Upload Plugin GAE Python Example 2.1.1 4 | # https://github.com/blueimp/jQuery-File-Upload 5 | # 6 | # Copyright 2011, Sebastian Tschan 7 | # https://blueimp.net 8 | # 9 | # Licensed under the MIT license: 10 | # http://www.opensource.org/licenses/MIT 11 | # 12 | 13 | from __future__ import with_statement 14 | from google.appengine.api import files, images 15 | from google.appengine.ext import blobstore, deferred 16 | from google.appengine.ext.webapp import blobstore_handlers 17 | import json 18 | import re 19 | import urllib 20 | import webapp2 21 | 22 | WEBSITE = 'http://blueimp.github.io/jQuery-File-Upload/' 23 | MIN_FILE_SIZE = 1 # bytes 24 | MAX_FILE_SIZE = 5000000 # bytes 25 | IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)') 26 | ACCEPT_FILE_TYPES = IMAGE_TYPES 27 | THUMBNAIL_MODIFICATOR = '=s80' # max width / height 28 | EXPIRATION_TIME = 300 # seconds 29 | 30 | 31 | def cleanup(blob_keys): 32 | blobstore.delete(blob_keys) 33 | 34 | 35 | class UploadHandler(webapp2.RequestHandler): 36 | 37 | def initialize(self, request, response): 38 | super(UploadHandler, self).initialize(request, response) 39 | self.response.headers['Access-Control-Allow-Origin'] = '*' 40 | self.response.headers[ 41 | 'Access-Control-Allow-Methods' 42 | ] = 'OPTIONS, HEAD, GET, POST, PUT, DELETE' 43 | self.response.headers[ 44 | 'Access-Control-Allow-Headers' 45 | ] = 'Content-Type, Content-Range, Content-Disposition' 46 | 47 | def validate(self, file): 48 | if file['size'] < MIN_FILE_SIZE: 49 | file['error'] = 'File is too small' 50 | elif file['size'] > MAX_FILE_SIZE: 51 | file['error'] = 'File is too big' 52 | elif not ACCEPT_FILE_TYPES.match(file['type']): 53 | file['error'] = 'Filetype not allowed' 54 | else: 55 | return True 56 | return False 57 | 58 | def get_file_size(self, file): 59 | file.seek(0, 2) # Seek to the end of the file 60 | size = file.tell() # Get the position of EOF 61 | file.seek(0) # Reset the file position to the beginning 62 | return size 63 | 64 | def write_blob(self, data, info): 65 | blob = files.blobstore.create( 66 | mime_type=info['type'], 67 | _blobinfo_uploaded_filename=info['name'] 68 | ) 69 | with files.open(blob, 'a') as f: 70 | f.write(data) 71 | files.finalize(blob) 72 | return files.blobstore.get_blob_key(blob) 73 | 74 | def handle_upload(self): 75 | results = [] 76 | blob_keys = [] 77 | for name, fieldStorage in self.request.POST.items(): 78 | if type(fieldStorage) is unicode: 79 | continue 80 | result = {} 81 | result['name'] = re.sub( 82 | r'^.*\\', 83 | '', 84 | fieldStorage.filename 85 | ) 86 | result['type'] = fieldStorage.type 87 | result['size'] = self.get_file_size(fieldStorage.file) 88 | if self.validate(result): 89 | blob_key = str( 90 | self.write_blob(fieldStorage.value, result) 91 | ) 92 | blob_keys.append(blob_key) 93 | result['deleteType'] = 'DELETE' 94 | result['deleteUrl'] = self.request.host_url +\ 95 | '/?key=' + urllib.quote(blob_key, '') 96 | if (IMAGE_TYPES.match(result['type'])): 97 | try: 98 | result['url'] = images.get_serving_url( 99 | blob_key, 100 | secure_url=self.request.host_url.startswith( 101 | 'https' 102 | ) 103 | ) 104 | result['thumbnailUrl'] = result['url'] +\ 105 | THUMBNAIL_MODIFICATOR 106 | except: # Could not get an image serving url 107 | pass 108 | if not 'url' in result: 109 | result['url'] = self.request.host_url +\ 110 | '/' + blob_key + '/' + urllib.quote( 111 | result['name'].encode('utf-8'), '') 112 | results.append(result) 113 | deferred.defer( 114 | cleanup, 115 | blob_keys, 116 | _countdown=EXPIRATION_TIME 117 | ) 118 | return results 119 | 120 | def options(self): 121 | pass 122 | 123 | def head(self): 124 | pass 125 | 126 | def get(self): 127 | self.redirect(WEBSITE) 128 | 129 | def post(self): 130 | if (self.request.get('_method') == 'DELETE'): 131 | return self.delete() 132 | result = {'files': self.handle_upload()} 133 | s = json.dumps(result, separators=(',', ':')) 134 | redirect = self.request.get('redirect') 135 | if redirect: 136 | return self.redirect(str( 137 | redirect.replace('%s', urllib.quote(s, ''), 1) 138 | )) 139 | if 'application/json' in self.request.headers.get('Accept'): 140 | self.response.headers['Content-Type'] = 'application/json' 141 | self.response.write(s) 142 | 143 | def delete(self): 144 | key = self.request.get('key') or '' 145 | blobstore.delete(key) 146 | s = json.dumps({key: True}, separators=(',', ':')) 147 | if 'application/json' in self.request.headers.get('Accept'): 148 | self.response.headers['Content-Type'] = 'application/json' 149 | self.response.write(s) 150 | 151 | 152 | class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler): 153 | def get(self, key, filename): 154 | if not blobstore.get(key): 155 | self.error(404) 156 | else: 157 | # Prevent browsers from MIME-sniffing the content-type: 158 | self.response.headers['X-Content-Type-Options'] = 'nosniff' 159 | # Cache for the expiration time: 160 | self.response.headers['Cache-Control'] = 'public,max-age=%d' % EXPIRATION_TIME 161 | # Send the file forcing a download dialog: 162 | self.send_blob(key, save_as=filename, content_type='application/octet-stream') 163 | 164 | app = webapp2.WSGIApplication( 165 | [ 166 | ('/', UploadHandler), 167 | ('/([^/]+)/([^/]+)', DownloadHandler) 168 | ], 169 | debug=True 170 | ) 171 | -------------------------------------------------------------------------------- /angularjs.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 20 | 21 | jQuery File Upload Demo - AngularJS version 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 42 | 43 | 44 |
64 |
65 |

jQuery File Upload Demo

66 |

AngularJS version

67 | 74 |
75 |
76 |

File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for AngularJS.
77 | Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
78 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.

79 |
80 |
81 | 82 |
83 | 84 | 85 |
86 |
87 |
88 |

Demo Notes

89 |
90 |
91 |
    92 |
  • The maximum file size for uploads in this demo is 5 MB (default file size is unlimited).
  • 93 |
  • Only image files (JPG, GIF, PNG) are allowed in this demo (by default there is no file type restriction).
  • 94 |
  • Uploaded files will be deleted automatically after 5 minutes (demo setting).
  • 95 |
  • You can drag & drop files from your desktop on this webpage (see Browser support).
  • 96 |
  • Please refer to the project website and documentation for more information.
  • 97 |
  • Built with Twitter's Bootstrap CSS framework and Icons from Glyphicons.
  • 98 |
99 |
100 |
101 |
102 | 103 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /basic.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | jQuery File Upload Demo - Basic version 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 50 |
51 |

jQuery File Upload Demo

52 |

Basic version

53 | 60 |
61 |
62 |

File Upload widget with multiple file selection, drag&drop support and progress bar for jQuery.
63 | Supports cross-domain, chunked and resumable file uploads.
64 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.

65 |
66 |
67 | 68 | 69 | 70 | Select files... 71 | 72 | 73 | 74 |
75 |
76 | 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |

Demo Notes

86 |
87 |
88 |
    89 |
  • The maximum file size for uploads in this demo is 5 MB (default file size is unlimited).
  • 90 |
  • Only image files (JPG, GIF, PNG) are allowed in this demo (by default there is no file type restriction).
  • 91 |
  • Uploaded files will be deleted automatically after 5 minutes (demo setting).
  • 92 |
  • You can drag & drop files from your desktop on this webpage (see Browser support).
  • 93 |
  • Please refer to the project website and documentation for more information.
  • 94 |
  • Built with Twitter's Bootstrap CSS framework and Icons from Glyphicons.
  • 95 |
96 |
97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 20 | 21 | jQuery File Upload Plugin Test 22 | 23 | 24 | 25 | 26 |

jQuery File Upload Plugin Test

27 | 28 |

29 | 30 |
31 |

32 |
    33 |
    34 | 35 |
    36 | 37 |
    38 |
    39 | 40 | 41 | 42 | Add files... 43 | 44 | 45 | 49 | 53 | 57 | 58 | 59 | 60 |
    61 | 62 |
    63 | 64 |
    65 |
    66 |
    67 | 68 |
     
    69 |
    70 |
    71 | 72 | 73 | 74 | 75 |
    76 |
    77 | 78 | 110 | 111 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /server/gae-go/app/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin GAE Go Example 3.1.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | package app 13 | 14 | import ( 15 | "appengine" 16 | "appengine/blobstore" 17 | "appengine/image" 18 | "appengine/taskqueue" 19 | "bytes" 20 | "encoding/json" 21 | "fmt" 22 | "io" 23 | "log" 24 | "mime/multipart" 25 | "net/http" 26 | "net/url" 27 | "regexp" 28 | "strings" 29 | "time" 30 | ) 31 | 32 | const ( 33 | WEBSITE = "http://blueimp.github.io/jQuery-File-Upload/" 34 | MIN_FILE_SIZE = 1 // bytes 35 | MAX_FILE_SIZE = 5000000 // bytes 36 | IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)" 37 | ACCEPT_FILE_TYPES = IMAGE_TYPES 38 | EXPIRATION_TIME = 300 // seconds 39 | THUMBNAIL_PARAM = "=s80" 40 | ) 41 | 42 | var ( 43 | imageTypes = regexp.MustCompile(IMAGE_TYPES) 44 | acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES) 45 | ) 46 | 47 | type FileInfo struct { 48 | Key appengine.BlobKey `json:"-"` 49 | Url string `json:"url,omitempty"` 50 | ThumbnailUrl string `json:"thumbnailUrl,omitempty"` 51 | Name string `json:"name"` 52 | Type string `json:"type"` 53 | Size int64 `json:"size"` 54 | Error string `json:"error,omitempty"` 55 | DeleteUrl string `json:"deleteUrl,omitempty"` 56 | DeleteType string `json:"deleteType,omitempty"` 57 | } 58 | 59 | func (fi *FileInfo) ValidateType() (valid bool) { 60 | if acceptFileTypes.MatchString(fi.Type) { 61 | return true 62 | } 63 | fi.Error = "Filetype not allowed" 64 | return false 65 | } 66 | 67 | func (fi *FileInfo) ValidateSize() (valid bool) { 68 | if fi.Size < MIN_FILE_SIZE { 69 | fi.Error = "File is too small" 70 | } else if fi.Size > MAX_FILE_SIZE { 71 | fi.Error = "File is too big" 72 | } else { 73 | return true 74 | } 75 | return false 76 | } 77 | 78 | func (fi *FileInfo) CreateUrls(r *http.Request, c appengine.Context) { 79 | u := &url.URL{ 80 | Scheme: r.URL.Scheme, 81 | Host: appengine.DefaultVersionHostname(c), 82 | Path: "/", 83 | } 84 | uString := u.String() 85 | fi.Url = uString + escape(string(fi.Key)) + "/" + 86 | escape(string(fi.Name)) 87 | fi.DeleteUrl = fi.Url + "?delete=true" 88 | fi.DeleteType = "DELETE" 89 | if imageTypes.MatchString(fi.Type) { 90 | servingUrl, err := image.ServingURL( 91 | c, 92 | fi.Key, 93 | &image.ServingURLOptions{ 94 | Secure: strings.HasSuffix(u.Scheme, "s"), 95 | Size: 0, 96 | Crop: false, 97 | }, 98 | ) 99 | check(err) 100 | fi.ThumbnailUrl = servingUrl.String() + THUMBNAIL_PARAM 101 | } 102 | } 103 | 104 | func check(err error) { 105 | if err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | func escape(s string) string { 111 | return strings.Replace(url.QueryEscape(s), "+", "%20", -1) 112 | } 113 | 114 | func delayedDelete(c appengine.Context, fi *FileInfo) { 115 | if key := string(fi.Key); key != "" { 116 | task := &taskqueue.Task{ 117 | Path: "/" + escape(key) + "/-", 118 | Method: "DELETE", 119 | Delay: time.Duration(EXPIRATION_TIME) * time.Second, 120 | } 121 | taskqueue.Add(c, task, "") 122 | } 123 | } 124 | 125 | func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) { 126 | fi = &FileInfo{ 127 | Name: p.FileName(), 128 | Type: p.Header.Get("Content-Type"), 129 | } 130 | if !fi.ValidateType() { 131 | return 132 | } 133 | defer func() { 134 | if rec := recover(); rec != nil { 135 | log.Println(rec) 136 | fi.Error = rec.(error).Error() 137 | } 138 | }() 139 | lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1} 140 | context := appengine.NewContext(r) 141 | w, err := blobstore.Create(context, fi.Type) 142 | defer func() { 143 | w.Close() 144 | fi.Size = MAX_FILE_SIZE + 1 - lr.N 145 | fi.Key, err = w.Key() 146 | check(err) 147 | if !fi.ValidateSize() { 148 | err := blobstore.Delete(context, fi.Key) 149 | check(err) 150 | return 151 | } 152 | delayedDelete(context, fi) 153 | fi.CreateUrls(r, context) 154 | }() 155 | check(err) 156 | _, err = io.Copy(w, lr) 157 | return 158 | } 159 | 160 | func getFormValue(p *multipart.Part) string { 161 | var b bytes.Buffer 162 | io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB 163 | return b.String() 164 | } 165 | 166 | func handleUploads(r *http.Request) (fileInfos []*FileInfo) { 167 | fileInfos = make([]*FileInfo, 0) 168 | mr, err := r.MultipartReader() 169 | check(err) 170 | r.Form, err = url.ParseQuery(r.URL.RawQuery) 171 | check(err) 172 | part, err := mr.NextPart() 173 | for err == nil { 174 | if name := part.FormName(); name != "" { 175 | if part.FileName() != "" { 176 | fileInfos = append(fileInfos, handleUpload(r, part)) 177 | } else { 178 | r.Form[name] = append(r.Form[name], getFormValue(part)) 179 | } 180 | } 181 | part, err = mr.NextPart() 182 | } 183 | return 184 | } 185 | 186 | func get(w http.ResponseWriter, r *http.Request) { 187 | if r.URL.Path == "/" { 188 | http.Redirect(w, r, WEBSITE, http.StatusFound) 189 | return 190 | } 191 | parts := strings.Split(r.URL.Path, "/") 192 | if len(parts) == 3 { 193 | if key := parts[1]; key != "" { 194 | blobKey := appengine.BlobKey(key) 195 | bi, err := blobstore.Stat(appengine.NewContext(r), blobKey) 196 | if err == nil { 197 | w.Header().Add("X-Content-Type-Options", "nosniff") 198 | if !imageTypes.MatchString(bi.ContentType) { 199 | w.Header().Add("Content-Type", "application/octet-stream") 200 | w.Header().Add( 201 | "Content-Disposition", 202 | fmt.Sprintf("attachment; filename=\"%s\"", parts[2]), 203 | ) 204 | } 205 | w.Header().Add( 206 | "Cache-Control", 207 | fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME), 208 | ) 209 | blobstore.Send(w, blobKey) 210 | return 211 | } 212 | } 213 | } 214 | http.Error(w, "404 Not Found", http.StatusNotFound) 215 | } 216 | 217 | func post(w http.ResponseWriter, r *http.Request) { 218 | result := make(map[string][]*FileInfo, 1) 219 | result["files"] = handleUploads(r) 220 | b, err := json.Marshal(result) 221 | check(err) 222 | if redirect := r.FormValue("redirect"); redirect != "" { 223 | if strings.Contains(redirect, "%s") { 224 | redirect = fmt.Sprintf( 225 | redirect, 226 | escape(string(b)), 227 | ) 228 | } 229 | http.Redirect(w, r, redirect, http.StatusFound) 230 | return 231 | } 232 | w.Header().Set("Cache-Control", "no-cache") 233 | jsonType := "application/json" 234 | if strings.Index(r.Header.Get("Accept"), jsonType) != -1 { 235 | w.Header().Set("Content-Type", jsonType) 236 | } 237 | fmt.Fprintln(w, string(b)) 238 | } 239 | 240 | func delete(w http.ResponseWriter, r *http.Request) { 241 | parts := strings.Split(r.URL.Path, "/") 242 | if len(parts) != 3 { 243 | return 244 | } 245 | result := make(map[string]bool, 1) 246 | if key := parts[1]; key != "" { 247 | c := appengine.NewContext(r) 248 | blobKey := appengine.BlobKey(key) 249 | err := blobstore.Delete(c, blobKey) 250 | check(err) 251 | err = image.DeleteServingURL(c, blobKey) 252 | check(err) 253 | result[key] = true 254 | } 255 | jsonType := "application/json" 256 | if strings.Index(r.Header.Get("Accept"), jsonType) != -1 { 257 | w.Header().Set("Content-Type", jsonType) 258 | } 259 | b, err := json.Marshal(result) 260 | check(err) 261 | fmt.Fprintln(w, string(b)) 262 | } 263 | 264 | func handle(w http.ResponseWriter, r *http.Request) { 265 | params, err := url.ParseQuery(r.URL.RawQuery) 266 | check(err) 267 | w.Header().Add("Access-Control-Allow-Origin", "*") 268 | w.Header().Add( 269 | "Access-Control-Allow-Methods", 270 | "OPTIONS, HEAD, GET, POST, PUT, DELETE", 271 | ) 272 | w.Header().Add( 273 | "Access-Control-Allow-Headers", 274 | "Content-Type, Content-Range, Content-Disposition", 275 | ) 276 | switch r.Method { 277 | case "OPTIONS": 278 | case "HEAD": 279 | case "GET": 280 | get(w, r) 281 | case "POST": 282 | if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" { 283 | delete(w, r) 284 | } else { 285 | post(w, r) 286 | } 287 | case "DELETE": 288 | delete(w, r) 289 | default: 290 | http.Error(w, "501 Not Implemented", http.StatusNotImplemented) 291 | } 292 | } 293 | 294 | func init() { 295 | http.HandleFunc("/", handle) 296 | } 297 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery File Upload Plugin - AngularJS Fork 2 | 3 | This is a fork of [Blueimp File Upload](https://github.com/blueimp/jQuery-File-Upload) with improved angular integration. 4 | Additionally all js files are bundled into dist/. 5 | 6 | The whole plugin has been bundled into a angular directive which can be changed via html attributes. 7 | 8 | ## Usage 9 | 10 | ``` 11 | 12 | ``` 13 | 14 | ## Demo 15 | [Demo File Upload](http://blueimp.github.io/jQuery-File-Upload/) 16 | 17 | ## Description 18 | File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery. 19 | Supports cross-domain, chunked and resumable file uploads and client-side image resizing. Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads. 20 | 21 | ## Setup 22 | * [How to setup the plugin on your website](https://github.com/blueimp/jQuery-File-Upload/wiki/Setup) 23 | * [How to use only the basic plugin (minimal setup guide).](https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin) 24 | 25 | ## Support 26 | 27 | * **[Support Forum](https://groups.google.com/d/forum/jquery-fileupload)** 28 | **Support requests** and **general discussions** about the File Upload plugin can be posted to the official 29 | [Support Forum](https://groups.google.com/d/forum/jquery-fileupload). 30 | If your question is not directly related to the File Upload plugin, you might have a better chance to get a reply by posting to [Stack Overflow](http://stackoverflow.com/questions/tagged/blueimp+jquery+file-upload). 31 | 32 | * Bugs and Feature requests 33 | **Bugs** and **Feature requests** can be reported using the [issues tracker](https://github.com/blueimp/jQuery-File-Upload/issues). 34 | Please read the [issue guidelines](https://github.com/blueimp/jQuery-File-Upload/blob/master/CONTRIBUTING.md) before posting. 35 | 36 | ## Features 37 | * **Multiple file upload:** 38 | Allows to select multiple files at once and upload them simultaneously. 39 | * **Drag & Drop support:** 40 | Allows to upload files by dragging them from your desktop or filemanager and dropping them on your browser window. 41 | * **Upload progress bar:** 42 | Shows a progress bar indicating the upload progress for individual files and for all uploads combined. 43 | * **Cancelable uploads:** 44 | Individual file uploads can be canceled to stop the upload progress. 45 | * **Resumable uploads:** 46 | Aborted uploads can be resumed with browsers supporting the Blob API. 47 | * **Chunked uploads:** 48 | Large files can be uploaded in smaller chunks with browsers supporting the Blob API. 49 | * **Client-side image resizing:** 50 | Images can be automatically resized on client-side with browsers supporting the required JS APIs. 51 | * **Preview images, audio and video:** 52 | A preview of image, audio and video files can be displayed before uploading with browsers supporting the required APIs. 53 | * **No browser plugins (e.g. Adobe Flash) required:** 54 | The implementation is based on open standards like HTML5 and JavaScript and requires no additional browser plugins. 55 | * **Graceful fallback for legacy browsers:** 56 | Uploads files via XMLHttpRequests if supported and uses iframes as fallback for legacy browsers. 57 | * **HTML file upload form fallback:** 58 | Allows progressive enhancement by using a standard HTML file upload form as widget element. 59 | * **Cross-site file uploads:** 60 | Supports uploading files to a different domain with cross-site XMLHttpRequests or iframe redirects. 61 | * **Multiple plugin instances:** 62 | Allows to use multiple plugin instances on the same webpage. 63 | * **Customizable and extensible:** 64 | Provides an API to set individual options and define callBack methods for various upload events. 65 | * **Multipart and file contents stream uploads:** 66 | Files can be uploaded as standard "multipart/form-data" or file contents stream (HTTP PUT file upload). 67 | * **Compatible with any server-side application platform:** 68 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads. 69 | 70 | ## Requirements 71 | 72 | ### Mandatory requirements 73 | * [jQuery](http://jquery.com/) v. 1.6+ 74 | * [jQuery UI widget factory](http://api.jqueryui.com/jQuery.widget/) v. 1.9+ (included) 75 | * [jQuery Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js) (included) 76 | 77 | The jQuery UI widget factory is a requirement for the basic File Upload plugin, but very lightweight without any other dependencies from the jQuery UI suite. 78 | 79 | The jQuery Iframe Transport is required for [browsers without XHR file upload support](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support). 80 | 81 | ### Optional requirements 82 | * [JavaScript Templates engine](https://github.com/blueimp/JavaScript-Templates) v. 2.5.3+ 83 | * [JavaScript Load Image library](https://github.com/blueimp/JavaScript-Load-Image) v. 1.11.0+ 84 | * [JavaScript Canvas to Blob polyfill](https://github.com/blueimp/JavaScript-Canvas-to-Blob) v. 2.1.0+ 85 | * [blueimp Gallery](https://github.com/blueimp/Gallery) v. 2.12.0+ 86 | * [Bootstrap CSS framework](http://getbootstrap.com/) v. 3.0.0+ 87 | * [Glyphicons](http://glyphicons.com/) 88 | 89 | The JavaScript Templates engine is used to render the selected and uploaded files for the Basic Plus UI and jQuery UI versions. 90 | 91 | The JavaScript Load Image library and JavaScript Canvas to Blob polyfill are required for the image previews and resizing functionality. 92 | 93 | The blueimp Gallery is used to display the uploaded images in a lightbox. 94 | 95 | The user interface of all versions except the jQuery UI version is built with Twitter's [Bootstrap](http://getbootstrap.com/) framework and icons from [Glyphicons](http://glyphicons.com/). 96 | 97 | ### Cross-domain requirements 98 | [Cross-domain File Uploads](https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads) using the [Iframe Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/jquery.iframe-transport.js) require a redirect back to the origin server to retrieve the upload results. The [example implementation](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/main.js) makes use of [result.html](https://github.com/blueimp/jQuery-File-Upload/blob/master/cors/result.html) as a static redirect page for the origin server. 99 | 100 | The repository also includes the [jQuery XDomainRequest Transport plugin](https://github.com/blueimp/jQuery-File-Upload/blob/master/js/cors/jquery.xdr-transport.js), which enables limited cross-domain AJAX requests in Microsoft Internet Explorer 8 and 9 (IE 10 supports cross-domain XHR requests). 101 | The XDomainRequest object allows GET and POST requests only and doesn't support file uploads. It is used on the [Demo](http://blueimp.github.io/jQuery-File-Upload/) to delete uploaded files from the cross-domain demo file upload service. 102 | 103 | ## Browsers 104 | 105 | ### Desktop browsers 106 | The File Upload plugin is regularly tested with the latest browser versions and supports the following minimal versions: 107 | 108 | * Google Chrome 109 | * Apple Safari 4.0+ 110 | * Mozilla Firefox 3.0+ 111 | * Opera 11.0+ 112 | * Microsoft Internet Explorer 6.0+ 113 | 114 | ### Mobile browsers 115 | The File Upload plugin has been tested with and supports the following mobile browsers: 116 | 117 | * Apple Safari on iOS 6.0+ 118 | * Google Chrome on iOS 6.0+ 119 | * Google Chrome on Android 4.0+ 120 | * Default Browser on Android 2.3+ 121 | * Opera Mobile 12.0+ 122 | 123 | ### Supported features 124 | For a detailed overview of the features supported by each browser version please have a look at the [Extended browser support information](https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support). 125 | 126 | ## License 127 | Released under the [MIT license](http://www.opensource.org/licenses/MIT). 128 | 129 | ## Donations 130 | jQuery File Upload is free software, but you can donate to support the developer, Sebastian Tschan: 131 | 132 | Flattr: [![Flattr](https://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/286433/jQuery-File-Upload-Plugin) 133 | 134 | PayPal: [![PayPal](https://www.paypalobjects.com/WEBSCR-640-20110429-1/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=PYWYSYP77KL54) 135 | -------------------------------------------------------------------------------- /js/jquery.iframe-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Iframe Transport Plugin 1.8.2 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, window, document */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else { 20 | // Browser globals: 21 | factory(window.jQuery); 22 | } 23 | }(function ($) { 24 | 'use strict'; 25 | 26 | // Helper variable to create unique names for the transport iframes: 27 | var counter = 0; 28 | 29 | // The iframe transport accepts four additional options: 30 | // options.fileInput: a jQuery collection of file input fields 31 | // options.paramName: the parameter name for the file form data, 32 | // overrides the name property of the file input field(s), 33 | // can be a string or an array of strings. 34 | // options.formData: an array of objects with name and value properties, 35 | // equivalent to the return data of .serializeArray(), e.g.: 36 | // [{name: 'a', value: 1}, {name: 'b', value: 2}] 37 | // options.initialIframeSrc: the URL of the initial iframe src, 38 | // by default set to "javascript:false;" 39 | $.ajaxTransport('iframe', function (options) { 40 | if (options.async) { 41 | // javascript:false as initial iframe src 42 | // prevents warning popups on HTTPS in IE6: 43 | /*jshint scripturl: true */ 44 | var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', 45 | /*jshint scripturl: false */ 46 | form, 47 | iframe, 48 | addParamChar; 49 | return { 50 | send: function (_, completeCallback) { 51 | form = $('
    '); 52 | form.attr('accept-charset', options.formAcceptCharset); 53 | addParamChar = /\?/.test(options.url) ? '&' : '?'; 54 | // XDomainRequest only supports GET and POST: 55 | if (options.type === 'DELETE') { 56 | options.url = options.url + addParamChar + '_method=DELETE'; 57 | options.type = 'POST'; 58 | } else if (options.type === 'PUT') { 59 | options.url = options.url + addParamChar + '_method=PUT'; 60 | options.type = 'POST'; 61 | } else if (options.type === 'PATCH') { 62 | options.url = options.url + addParamChar + '_method=PATCH'; 63 | options.type = 'POST'; 64 | } 65 | // IE versions below IE8 cannot set the name property of 66 | // elements that have already been added to the DOM, 67 | // so we set the name along with the iframe HTML markup: 68 | counter += 1; 69 | iframe = $( 70 | '' 72 | ).bind('load', function () { 73 | var fileInputClones, 74 | paramNames = $.isArray(options.paramName) ? 75 | options.paramName : [options.paramName]; 76 | iframe 77 | .unbind('load') 78 | .bind('load', function () { 79 | var response; 80 | // Wrap in a try/catch block to catch exceptions thrown 81 | // when trying to access cross-domain iframe contents: 82 | try { 83 | response = iframe.contents(); 84 | // Google Chrome and Firefox do not throw an 85 | // exception when calling iframe.contents() on 86 | // cross-domain requests, so we unify the response: 87 | if (!response.length || !response[0].firstChild) { 88 | throw new Error(); 89 | } 90 | } catch (e) { 91 | response = undefined; 92 | } 93 | // The complete callback returns the 94 | // iframe content document as response object: 95 | completeCallback( 96 | 200, 97 | 'success', 98 | {'iframe': response} 99 | ); 100 | // Fix for IE endless progress bar activity bug 101 | // (happens on form submits to iframe targets): 102 | $('') 103 | .appendTo(form); 104 | window.setTimeout(function () { 105 | // Removing the form in a setTimeout call 106 | // allows Chrome's developer tools to display 107 | // the response result 108 | form.remove(); 109 | }, 0); 110 | }); 111 | form 112 | .prop('target', iframe.prop('name')) 113 | .prop('action', options.url) 114 | .prop('method', options.type); 115 | if (options.formData) { 116 | $.each(options.formData, function (index, field) { 117 | $('') 118 | .prop('name', field.name) 119 | .val(field.value) 120 | .appendTo(form); 121 | }); 122 | } 123 | if (options.fileInput && options.fileInput.length && 124 | options.type === 'POST') { 125 | fileInputClones = options.fileInput.clone(); 126 | // Insert a clone for each file input field: 127 | options.fileInput.after(function (index) { 128 | return fileInputClones[index]; 129 | }); 130 | if (options.paramName) { 131 | options.fileInput.each(function (index) { 132 | $(this).prop( 133 | 'name', 134 | paramNames[index] || options.paramName 135 | ); 136 | }); 137 | } 138 | // Appending the file input fields to the hidden form 139 | // removes them from their original location: 140 | form 141 | .append(options.fileInput) 142 | .prop('enctype', 'multipart/form-data') 143 | // enctype must be set as encoding for IE: 144 | .prop('encoding', 'multipart/form-data'); 145 | // Remove the HTML5 form attribute from the input(s): 146 | options.fileInput.removeAttr('form'); 147 | } 148 | form.submit(); 149 | // Insert the file input fields at their original location 150 | // by replacing the clones with the originals: 151 | if (fileInputClones && fileInputClones.length) { 152 | options.fileInput.each(function (index, input) { 153 | var clone = $(fileInputClones[index]); 154 | // Restore the original name and form properties: 155 | $(input) 156 | .prop('name', clone.prop('name')) 157 | .attr('form', clone.attr('form')); 158 | clone.replaceWith(input); 159 | }); 160 | } 161 | }); 162 | form.append(iframe).appendTo(document.body); 163 | }, 164 | abort: function () { 165 | if (iframe) { 166 | // javascript:false as iframe src aborts the request 167 | // and prevents warning popups on HTTPS in IE6. 168 | // concat is used to avoid the "Script URL" JSLint error: 169 | iframe 170 | .unbind('load') 171 | .prop('src', initialIframeSrc); 172 | } 173 | if (form) { 174 | form.remove(); 175 | } 176 | } 177 | }; 178 | } 179 | }); 180 | 181 | // The iframe transport returns the iframe content document as response. 182 | // The following adds converters from iframe to text, json, html, xml 183 | // and script. 184 | // Please note that the Content-Type for JSON responses has to be text/plain 185 | // or text/html, if the browser doesn't include application/json in the 186 | // Accept header, else IE will show a download dialog. 187 | // The Content-Type for XML responses on the other hand has to be always 188 | // application/xml or text/xml, so IE properly parses the XML response. 189 | // See also 190 | // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation 191 | $.ajaxSetup({ 192 | converters: { 193 | 'iframe text': function (iframe) { 194 | return iframe && $(iframe[0].body).text(); 195 | }, 196 | 'iframe json': function (iframe) { 197 | return iframe && $.parseJSON($(iframe[0].body).text()); 198 | }, 199 | 'iframe html': function (iframe) { 200 | return iframe && $(iframe[0].body).html(); 201 | }, 202 | 'iframe xml': function (iframe) { 203 | var xmlDoc = iframe && iframe[0]; 204 | return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : 205 | $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || 206 | $(xmlDoc.body).html()); 207 | }, 208 | 'iframe script': function (iframe) { 209 | return iframe && $.globalEval($(iframe[0].body).text()); 210 | } 211 | } 212 | }); 213 | 214 | })); 215 | -------------------------------------------------------------------------------- /js/jquery.fileupload-image.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Image Preview & Resize Plugin 1.7.2 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, window, Blob */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | 'load-image-meta', 23 | 'load-image-exif', 24 | 'load-image-ios', 25 | 'canvas-to-blob', 26 | './jquery.fileupload-process' 27 | ], factory); 28 | } else { 29 | // Browser globals: 30 | factory( 31 | window.jQuery, 32 | window.loadImage 33 | ); 34 | } 35 | }(function ($, loadImage) { 36 | 'use strict'; 37 | 38 | // Prepend to the default processQueue: 39 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 40 | { 41 | action: 'loadImageMetaData', 42 | disableImageHead: '@', 43 | disableExif: '@', 44 | disableExifThumbnail: '@', 45 | disableExifSub: '@', 46 | disableExifGps: '@', 47 | disabled: '@disableImageMetaDataLoad' 48 | }, 49 | { 50 | action: 'loadImage', 51 | // Use the action as prefix for the "@" options: 52 | prefix: true, 53 | fileTypes: '@', 54 | maxFileSize: '@', 55 | noRevoke: '@', 56 | disabled: '@disableImageLoad' 57 | }, 58 | { 59 | action: 'resizeImage', 60 | // Use "image" as prefix for the "@" options: 61 | prefix: 'image', 62 | maxWidth: '@', 63 | maxHeight: '@', 64 | minWidth: '@', 65 | minHeight: '@', 66 | crop: '@', 67 | orientation: '@', 68 | forceResize: '@', 69 | disabled: '@disableImageResize' 70 | }, 71 | { 72 | action: 'saveImage', 73 | quality: '@imageQuality', 74 | type: '@imageType', 75 | disabled: '@disableImageResize' 76 | }, 77 | { 78 | action: 'saveImageMetaData', 79 | disabled: '@disableImageMetaDataSave' 80 | }, 81 | { 82 | action: 'resizeImage', 83 | // Use "preview" as prefix for the "@" options: 84 | prefix: 'preview', 85 | maxWidth: '@', 86 | maxHeight: '@', 87 | minWidth: '@', 88 | minHeight: '@', 89 | crop: '@', 90 | orientation: '@', 91 | thumbnail: '@', 92 | canvas: '@', 93 | disabled: '@disableImagePreview' 94 | }, 95 | { 96 | action: 'setImage', 97 | name: '@imagePreviewName', 98 | disabled: '@disableImagePreview' 99 | }, 100 | { 101 | action: 'deleteImageReferences', 102 | disabled: '@disableImageReferencesDeletion' 103 | } 104 | ); 105 | 106 | // The File Upload Resize plugin extends the fileupload widget 107 | // with image resize functionality: 108 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 109 | 110 | options: { 111 | // The regular expression for the types of images to load: 112 | // matched against the file type: 113 | loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/, 114 | // The maximum file size of images to load: 115 | loadImageMaxFileSize: 10000000, // 10MB 116 | // The maximum width of resized images: 117 | imageMaxWidth: 1920, 118 | // The maximum height of resized images: 119 | imageMaxHeight: 1080, 120 | // Defines the image orientation (1-8) or takes the orientation 121 | // value from Exif data if set to true: 122 | imageOrientation: false, 123 | // Define if resized images should be cropped or only scaled: 124 | imageCrop: false, 125 | // Disable the resize image functionality by default: 126 | disableImageResize: true, 127 | // The maximum width of the preview images: 128 | previewMaxWidth: 80, 129 | // The maximum height of the preview images: 130 | previewMaxHeight: 80, 131 | // Defines the preview orientation (1-8) or takes the orientation 132 | // value from Exif data if set to true: 133 | previewOrientation: true, 134 | // Create the preview using the Exif data thumbnail: 135 | previewThumbnail: true, 136 | // Define if preview images should be cropped or only scaled: 137 | previewCrop: false, 138 | // Define if preview images should be resized as canvas elements: 139 | previewCanvas: true 140 | }, 141 | 142 | processActions: { 143 | 144 | // Loads the image given via data.files and data.index 145 | // as img element, if the browser supports the File API. 146 | // Accepts the options fileTypes (regular expression) 147 | // and maxFileSize (integer) to limit the files to load: 148 | loadImage: function (data, options) { 149 | if (options.disabled) { 150 | return data; 151 | } 152 | var that = this, 153 | file = data.files[data.index], 154 | dfd = $.Deferred(); 155 | if (($.type(options.maxFileSize) === 'number' && 156 | file.size > options.maxFileSize) || 157 | (options.fileTypes && !options.fileTypes.test(file.type)) || !loadImage( 158 | file, 159 | function (img) { 160 | if (img.src) { 161 | data.img = img; 162 | } 163 | dfd.resolveWith(that, [data]); 164 | }, 165 | options 166 | )) { 167 | return data; 168 | } 169 | return dfd.promise(); 170 | }, 171 | 172 | // Resizes the image given as data.canvas or data.img 173 | // and updates data.canvas or data.img with the resized image. 174 | // Also stores the resized image as preview property. 175 | // Accepts the options maxWidth, maxHeight, minWidth, 176 | // minHeight, canvas and crop: 177 | resizeImage: function (data, options) { 178 | if (options.disabled || !(data.canvas || data.img)) { 179 | return data; 180 | } 181 | options = $.extend({canvas: true}, options); 182 | var that = this, 183 | dfd = $.Deferred(), 184 | img = (options.canvas && data.canvas) || data.img, 185 | resolve = function (newImg) { 186 | if (newImg && (newImg.width !== img.width || 187 | newImg.height !== img.height || 188 | options.forceResize)) { 189 | data[newImg.getContext ? 'canvas' : 'img'] = newImg; 190 | } 191 | data.preview = newImg; 192 | dfd.resolveWith(that, [data]); 193 | }, 194 | thumbnail; 195 | if (data.exif) { 196 | if (options.orientation === true) { 197 | options.orientation = data.exif.get('Orientation'); 198 | } 199 | if (options.thumbnail) { 200 | thumbnail = data.exif.get('Thumbnail'); 201 | if (thumbnail) { 202 | loadImage(thumbnail, resolve, options); 203 | return dfd.promise(); 204 | } 205 | } 206 | // Prevent orienting the same image twice: 207 | if (data.orientation) { 208 | delete options.orientation; 209 | } else { 210 | data.orientation = options.orientation; 211 | } 212 | } 213 | if (img) { 214 | resolve(loadImage.scale(img, options)); 215 | return dfd.promise(); 216 | } 217 | return data; 218 | }, 219 | 220 | // Saves the processed image given as data.canvas 221 | // inplace at data.index of data.files: 222 | saveImage: function (data, options) { 223 | if (!data.canvas || options.disabled) { 224 | return data; 225 | } 226 | var that = this, 227 | file = data.files[data.index], 228 | dfd = $.Deferred(); 229 | if (data.canvas.toBlob) { 230 | data.canvas.toBlob( 231 | function (blob) { 232 | if (!blob.name) { 233 | if (file.type === blob.type) { 234 | blob.name = file.name; 235 | } else if (file.name) { 236 | blob.name = file.name.replace( 237 | /\..+$/, 238 | '.' + blob.type.substr(6) 239 | ); 240 | } 241 | } 242 | // Don't restore invalid meta data: 243 | if (file.type !== blob.type) { 244 | delete data.imageHead; 245 | } 246 | // Store the created blob at the position 247 | // of the original file in the files list: 248 | data.files[data.index] = blob; 249 | dfd.resolveWith(that, [data]); 250 | }, 251 | options.type || file.type, 252 | options.quality 253 | ); 254 | } else { 255 | return data; 256 | } 257 | return dfd.promise(); 258 | }, 259 | 260 | loadImageMetaData: function (data, options) { 261 | if (options.disabled) { 262 | return data; 263 | } 264 | var that = this, 265 | dfd = $.Deferred(); 266 | loadImage.parseMetaData(data.files[data.index], function (result) { 267 | $.extend(data, result); 268 | dfd.resolveWith(that, [data]); 269 | }, options); 270 | return dfd.promise(); 271 | }, 272 | 273 | saveImageMetaData: function (data, options) { 274 | if (!(data.imageHead && data.canvas && 275 | data.canvas.toBlob && !options.disabled)) { 276 | return data; 277 | } 278 | var file = data.files[data.index], 279 | blob = new Blob([ 280 | data.imageHead, 281 | // Resized images always have a head size of 20 bytes, 282 | // including the JPEG marker and a minimal JFIF header: 283 | this._blobSlice.call(file, 20) 284 | ], {type: file.type}); 285 | blob.name = file.name; 286 | data.files[data.index] = blob; 287 | return data; 288 | }, 289 | 290 | // Sets the resized version of the image as a property of the 291 | // file object, must be called after "saveImage": 292 | setImage: function (data, options) { 293 | if (data.preview && !options.disabled) { 294 | data.files[data.index][options.name || 'preview'] = data.preview; 295 | } 296 | return data; 297 | }, 298 | 299 | deleteImageReferences: function (data, options) { 300 | if (!options.disabled) { 301 | delete data.img; 302 | delete data.canvas; 303 | delete data.preview; 304 | delete data.imageHead; 305 | } 306 | return data; 307 | } 308 | 309 | } 310 | 311 | }); 312 | 313 | })); 314 | -------------------------------------------------------------------------------- /basic-plus.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | jQuery File Upload Demo - Basic Plus version 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 50 |
    51 |

    jQuery File Upload Demo

    52 |

    Basic Plus version

    53 | 60 |
    61 |
    62 |

    File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery.
    63 | Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
    64 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.

    65 |
    66 |
    67 | 68 | 69 | 70 | Add files... 71 | 72 | 73 | 74 |
    75 |
    76 | 77 |
    78 |
    79 |
    80 | 81 |
    82 |
    83 |
    84 |
    85 |

    Demo Notes

    86 |
    87 |
    88 |
      89 |
    • The maximum file size for uploads in this demo is 5 MB (default file size is unlimited).
    • 90 |
    • Only image files (JPG, GIF, PNG) are allowed in this demo (by default there is no file type restriction).
    • 91 |
    • Uploaded files will be deleted automatically after 5 minutes (demo setting).
    • 92 |
    • You can drag & drop files from your desktop on this webpage (see Browser support).
    • 93 |
    • Please refer to the project website and documentation for more information.
    • 94 |
    • Built with Twitter's Bootstrap CSS framework and Icons from Glyphicons.
    • 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 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /server/node/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * jQuery File Upload Plugin Node.js Example 2.1.1 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2012, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | /* jshint nomen:false */ 14 | /* global require, __dirname, unescape, console */ 15 | 16 | (function (port) { 17 | 'use strict'; 18 | if (typeof String.prototype.startsWith !== 'function') { 19 | // see below for better implementation! 20 | String.prototype.startsWith = function (str) { 21 | return this.indexOf(str) === 0; 22 | }; 23 | } 24 | if (typeof String.prototype.endsWith !== 'function') { 25 | String.prototype.endsWith = function (str) { 26 | return this.substring(this.length - str.length, this.length) === str; 27 | }; 28 | } 29 | var path = require('path'), 30 | fs = require('fs'), 31 | // Since Node 0.8, .existsSync() moved from path to fs: 32 | _existsSync = fs.existsSync || path.existsSync, 33 | formidable = require('formidable'), 34 | nodeStatic = require('node-static'), 35 | imageMagick = require('imagemagick'), 36 | options = { 37 | tmpDir: __dirname + '/tmp', 38 | publicDir: __dirname + '/public', 39 | uploadDir: __dirname + '/public/files', 40 | uploadUrl: '/files/', 41 | frontendDir: __dirname + '/../..', 42 | maxPostSize: 11000000000, // 11 GB 43 | minFileSize: 1, 44 | maxFileSize: 10000000000, // 10 GB 45 | acceptFileTypes: /.+/i, 46 | // Files not matched by this regular expression force a download dialog, 47 | // to prevent executing any scripts in the context of the service domain: 48 | inlineFileTypes: /\.(gif|jpe?g|png)$/i, 49 | imageTypes: /\.(gif|jpe?g|png)$/i, 50 | imageVersions: { 51 | 'thumbnail': { 52 | width: 80, 53 | height: 80 54 | } 55 | }, 56 | accessControl: { 57 | allowOrigin: '*', 58 | allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE', 59 | allowHeaders: 'Content-Type, Content-Range, Content-Disposition' 60 | }, 61 | /* Uncomment and edit this section to provide the service via HTTPS: 62 | ssl: { 63 | key: fs.readFileSync('/Applications/XAMPP/etc/ssl.key/server.key'), 64 | cert: fs.readFileSync('/Applications/XAMPP/etc/ssl.crt/server.crt') 65 | }, 66 | */ 67 | nodeStatic: { 68 | cache: 3600 // seconds to cache served files 69 | } 70 | }, 71 | utf8encode = function (str) { 72 | return unescape(encodeURIComponent(str)); 73 | }, 74 | fileServer = new nodeStatic.Server(options.publicDir, options.nodeStatic), 75 | frontendServer = new nodeStatic.Server(options.frontendDir, options.nodeStatic), 76 | nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/, 77 | nameCountFunc = function (s, index, ext) { 78 | return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || ''); 79 | }, 80 | FileInfo = function (file) { 81 | this.name = file.name; 82 | this.size = file.size; 83 | this.type = file.type; 84 | this.deleteType = 'DELETE'; 85 | }, 86 | UploadHandler = function (req, res, callback) { 87 | this.req = req; 88 | this.res = res; 89 | this.callback = callback; 90 | }, 91 | serve = function (req, res) { 92 | res.setHeader( 93 | 'Access-Control-Allow-Origin', 94 | options.accessControl.allowOrigin 95 | ); 96 | res.setHeader( 97 | 'Access-Control-Allow-Methods', 98 | options.accessControl.allowMethods 99 | ); 100 | res.setHeader( 101 | 'Access-Control-Allow-Headers', 102 | options.accessControl.allowHeaders 103 | ); 104 | var handleResult = function (result, redirect) { 105 | if (redirect) { 106 | res.writeHead(302, { 107 | 'Location': redirect.replace( 108 | /%s/, 109 | encodeURIComponent(JSON.stringify(result)) 110 | ) 111 | }); 112 | res.end(); 113 | } else { 114 | res.writeHead(200, { 115 | 'Content-Type': req.headers.accept 116 | .indexOf('application/json') !== -1 ? 117 | 'application/json' : 'text/plain' 118 | }); 119 | res.end(JSON.stringify(result)); 120 | } 121 | }, 122 | setNoCacheHeaders = function () { 123 | res.setHeader('Pragma', 'no-cache'); 124 | res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); 125 | res.setHeader('Content-Disposition', 'inline; filename="files.json"'); 126 | }, 127 | handler = new UploadHandler(req, res, handleResult); 128 | switch (req.method) { 129 | case 'OPTIONS': 130 | res.end(); 131 | break; 132 | case 'HEAD': 133 | case 'GET': 134 | console.log(req.url); 135 | if (req.url === '/') { 136 | setNoCacheHeaders(); 137 | if (req.method === 'GET') { 138 | handler.get(); 139 | } else { 140 | res.end(); 141 | } 142 | } else if (req.url.endsWith('.html') || req.url.startsWith('/css/') || req.url.startsWith('/js/') || req.url.startsWith('/dist/') || req.url.startsWith('/bower_components/') || req.url.startsWith('/img/') || req.url.startsWith('/templates/')) { 143 | frontendServer.serve(req, res); 144 | } else { 145 | fileServer.serve(req, res); 146 | } 147 | break; 148 | case 'POST': 149 | setNoCacheHeaders(); 150 | handler.post(); 151 | break; 152 | case 'DELETE': 153 | handler.destroy(); 154 | break; 155 | default: 156 | res.statusCode = 405; 157 | res.end(); 158 | } 159 | }; 160 | fileServer.respond = function (pathname, status, _headers, files, stat, req, res, finish) { 161 | // Prevent browsers from MIME-sniffing the content-type: 162 | _headers['X-Content-Type-Options'] = 'nosniff'; 163 | if (!options.inlineFileTypes.test(files[0])) { 164 | // Force a download dialog for unsafe file extensions: 165 | _headers['Content-Type'] = 'application/octet-stream'; 166 | _headers['Content-Disposition'] = 'attachment; filename="' + 167 | utf8encode(path.basename(files[0])) + '"'; 168 | } 169 | nodeStatic.Server.prototype.respond 170 | .call(this, pathname, status, _headers, files, stat, req, res, finish); 171 | }; 172 | FileInfo.prototype.validate = function () { 173 | if (options.minFileSize && options.minFileSize > this.size) { 174 | this.error = 'File is too small'; 175 | } else if (options.maxFileSize && options.maxFileSize < this.size) { 176 | this.error = 'File is too big'; 177 | } else if (!options.acceptFileTypes.test(this.name)) { 178 | this.error = 'Filetype not allowed'; 179 | } 180 | return !this.error; 181 | }; 182 | FileInfo.prototype.safeName = function () { 183 | // Prevent directory traversal and creating hidden system files: 184 | this.name = path.basename(this.name).replace(/^\.+/, ''); 185 | // Prevent overwriting existing files: 186 | while (_existsSync(options.uploadDir + '/' + this.name)) { 187 | this.name = this.name.replace(nameCountRegexp, nameCountFunc); 188 | } 189 | }; 190 | FileInfo.prototype.initUrls = function (req) { 191 | if (!this.error) { 192 | var that = this, 193 | baseUrl = (options.ssl ? 'https:' : 'http:') + 194 | '//' + req.headers.host + options.uploadUrl; 195 | this.url = this.deleteUrl = baseUrl + encodeURIComponent(this.name); 196 | Object.keys(options.imageVersions).forEach(function (version) { 197 | if (_existsSync( 198 | options.uploadDir + '/' + version + '/' + that.name 199 | )) { 200 | that[version + 'Url'] = baseUrl + version + '/' + 201 | encodeURIComponent(that.name); 202 | } 203 | }); 204 | } 205 | }; 206 | UploadHandler.prototype.get = function () { 207 | var handler = this, 208 | files = []; 209 | fs.readdir(options.uploadDir, function (err, list) { 210 | list.forEach(function (name) { 211 | var stats = fs.statSync(options.uploadDir + '/' + name), 212 | fileInfo; 213 | if (stats.isFile() && name[0] !== '.') { 214 | fileInfo = new FileInfo({ 215 | name: name, 216 | size: stats.size 217 | }); 218 | fileInfo.initUrls(handler.req); 219 | files.push(fileInfo); 220 | } 221 | }); 222 | handler.callback({files: files}); 223 | }); 224 | }; 225 | UploadHandler.prototype.post = function () { 226 | var handler = this, 227 | form = new formidable.IncomingForm(), 228 | tmpFiles = [], 229 | files = [], 230 | map = {}, 231 | counter = 1, 232 | redirect, 233 | finish = function () { 234 | counter -= 1; 235 | if (!counter) { 236 | files.forEach(function (fileInfo) { 237 | fileInfo.initUrls(handler.req); 238 | }); 239 | handler.callback({files: files}, redirect); 240 | } 241 | }; 242 | form.uploadDir = options.tmpDir; 243 | form.on('fileBegin', function (name, file) { 244 | tmpFiles.push(file.path); 245 | var fileInfo = new FileInfo(file, handler.req, true); 246 | fileInfo.safeName(); 247 | map[path.basename(file.path)] = fileInfo; 248 | files.push(fileInfo); 249 | }).on('field', function (name, value) { 250 | if (name === 'redirect') { 251 | redirect = value; 252 | } 253 | }).on('file', function (name, file) { 254 | var fileInfo = map[path.basename(file.path)]; 255 | fileInfo.size = file.size; 256 | if (!fileInfo.validate()) { 257 | fs.unlink(file.path); 258 | return; 259 | } 260 | fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name); 261 | if (options.imageTypes.test(fileInfo.name)) { 262 | Object.keys(options.imageVersions).forEach(function (version) { 263 | counter += 1; 264 | var opts = options.imageVersions[version]; 265 | imageMagick.resize({ 266 | width: opts.width, 267 | height: opts.height, 268 | srcPath: options.uploadDir + '/' + fileInfo.name, 269 | dstPath: options.uploadDir + '/' + version + '/' + 270 | fileInfo.name 271 | }, finish); 272 | }); 273 | } 274 | }).on('aborted', function () { 275 | tmpFiles.forEach(function (file) { 276 | fs.unlink(file); 277 | }); 278 | }).on('error', function (e) { 279 | console.log(e); 280 | }).on('progress', function (bytesReceived) { 281 | if (bytesReceived > options.maxPostSize) { 282 | handler.req.connection.destroy(); 283 | } 284 | }).on('end', finish).parse(handler.req); 285 | }; 286 | UploadHandler.prototype.destroy = function () { 287 | var handler = this, 288 | fileName; 289 | if (handler.req.url.slice(0, options.uploadUrl.length) === options.uploadUrl) { 290 | fileName = path.basename(decodeURIComponent(handler.req.url)); 291 | if (fileName[0] !== '.') { 292 | fs.unlink(options.uploadDir + '/' + fileName, function (ex) { 293 | Object.keys(options.imageVersions).forEach(function (version) { 294 | fs.unlink(options.uploadDir + '/' + version + '/' + fileName); 295 | }); 296 | handler.callback({success: !ex}); 297 | }); 298 | return; 299 | } 300 | } 301 | handler.callback({success: false}); 302 | }; 303 | if (options.ssl) { 304 | require('https').createServer(options.ssl, serve).listen(port); 305 | } else { 306 | require('http').createServer(serve).listen(port); 307 | } 308 | }(8888)); 309 | -------------------------------------------------------------------------------- /jquery-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 20 | 21 | jQuery File Upload Demo - jQuery UI version 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 |

    jQuery File Upload Demo

    55 |

    jQuery UI version

    56 |
    57 | 58 | 84 |
    85 | 92 |
    93 |

    File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery UI.
    94 | Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
    95 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.

    96 |
    97 | 98 |
    99 | 100 | 101 | 102 |
    103 |
    104 | 105 | 106 | Add files... 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
    116 | 117 | 123 |
    124 | 125 |
    126 |
    127 |
    128 |

    Demo Notes

    129 | 137 | 138 | 147 | 148 | 173 | 174 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 245 | 246 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 20 | 21 | jQuery File Upload Demo 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 58 |
    59 |

    jQuery File Upload Demo

    60 |

    Basic Plus UI version

    61 | 68 |
    69 |
    70 |

    File Upload widget with multiple file selection, drag&drop support, progress bars, validation and preview images, audio and video for jQuery.
    71 | Supports cross-domain, chunked and resumable file uploads and client-side image resizing.
    72 | Works with any server-side platform (PHP, Python, Ruby on Rails, Java, Node.js, Go etc.) that supports standard HTML form file uploads.

    73 |
    74 |
    75 | 76 |
    77 | 78 | 79 | 80 |
    81 |
    82 | 83 | 84 | 85 | Add files... 86 | 87 | 88 | 92 | 96 | 100 | 101 | 102 | 103 |
    104 | 105 |
    106 | 107 |
    108 |
    109 |
    110 | 111 |
     
    112 |
    113 |
    114 | 115 | 116 |
    117 |
    118 |
    119 |
    120 |

    Demo Notes

    121 |
    122 |
    123 |
      124 |
    • The maximum file size for uploads in this demo is 5 MB (default file size is unlimited).
    • 125 |
    • Only image files (JPG, GIF, PNG) are allowed in this demo (by default there is no file type restriction).
    • 126 |
    • Uploaded files will be deleted automatically after 5 minutes (demo setting).
    • 127 |
    • You can drag & drop files from your desktop on this webpage (see Browser support).
    • 128 |
    • Please refer to the project website and documentation for more information.
    • 129 |
    • Built with Twitter's Bootstrap CSS framework and Icons from Glyphicons.
    • 130 |
    131 |
    132 |
    133 |
    134 | 135 | 144 | 145 | 176 | 177 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /js/jquery.fileupload-angular.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload AngularJS Plugin 2.2.0 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, angular */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'angular', 22 | './jquery.fileupload-image', 23 | './jquery.fileupload-audio', 24 | './jquery.fileupload-video', 25 | './jquery.fileupload-validate' 26 | ], factory); 27 | } else { 28 | factory(); 29 | } 30 | }(function () { 31 | 'use strict'; 32 | 33 | angular.module('blueimp.fileupload', []) 34 | 35 | // The fileUpload service provides configuration options 36 | // for the fileUpload directive and default handlers for 37 | // File Upload events: 38 | .provider('fileUpload', function () { 39 | 40 | var scopeEvalAsync = function (expression) { 41 | var scope = angular.element(this) 42 | .fileupload('option', 'scope'); 43 | // Schedule a new $digest cycle if not already inside of one 44 | // and evaluate the given expression: 45 | scope.$evalAsync(expression); 46 | }, 47 | addFileMethods = function (scope, data) { 48 | var files = data.files, 49 | file = files[0]; 50 | angular.forEach(files, function (file, index) { 51 | file._index = index; 52 | file.$state = function () { 53 | return data.state(); 54 | }; 55 | file.$processing = function () { 56 | return data.processing(); 57 | }; 58 | file.$progress = function () { 59 | return data.progress(); 60 | }; 61 | file.$response = function () { 62 | return data.response(); 63 | }; 64 | }); 65 | file.$submit = function () { 66 | if (!file.error) { 67 | return data.submit(); 68 | } 69 | }; 70 | file.$cancel = function () { 71 | return data.abort(); 72 | }; 73 | }, 74 | $config; 75 | $config = this.defaults = { 76 | handleResponse: function (e, data) { 77 | var files = data.result && data.result.files; 78 | if (files) { 79 | data.scope.replace(data.files, files); 80 | } else if (data.errorThrown || 81 | data.textStatus === 'error') { 82 | data.files[0].error = data.errorThrown || 83 | data.textStatus; 84 | } 85 | }, 86 | add: function (e, data) { 87 | if (e.isDefaultPrevented()) { 88 | return false; 89 | } 90 | var scope = data.scope, 91 | filesCopy = []; 92 | angular.forEach(data.files, function (file) { 93 | filesCopy.push(file); 94 | }); 95 | scope.$apply(function () { 96 | addFileMethods(scope, data); 97 | var method = scope.option('prependFiles') ? 98 | 'unshift' : 'push'; 99 | Array.prototype[method].apply(scope.queue, data.files); 100 | }); 101 | data.process(function () { 102 | return scope.process(data); 103 | }).always(function () { 104 | scope.$apply(function () { 105 | addFileMethods(scope, data); 106 | scope.replace(filesCopy, data.files); 107 | }); 108 | }).then(function () { 109 | if ((scope.option('autoUpload') || 110 | data.autoUpload) && 111 | data.autoUpload !== false) { 112 | data.submit(); 113 | } 114 | }); 115 | }, 116 | progress: function (e, data) { 117 | if (e.isDefaultPrevented()) { 118 | return false; 119 | } 120 | data.scope.$apply(); 121 | }, 122 | done: function (e, data) { 123 | if (e.isDefaultPrevented()) { 124 | return false; 125 | } 126 | var that = this; 127 | data.scope.$apply(function () { 128 | data.handleResponse.call(that, e, data); 129 | }); 130 | }, 131 | fail: function (e, data) { 132 | if (e.isDefaultPrevented()) { 133 | return false; 134 | } 135 | var that = this, 136 | scope = data.scope; 137 | if (data.errorThrown === 'abort') { 138 | scope.clear(data.files); 139 | return; 140 | } 141 | scope.$apply(function () { 142 | data.handleResponse.call(that, e, data); 143 | }); 144 | }, 145 | stop: scopeEvalAsync, 146 | processstart: scopeEvalAsync, 147 | processstop: scopeEvalAsync, 148 | getNumberOfFiles: function () { 149 | var scope = this.scope; 150 | return scope.queue.length - scope.processing(); 151 | }, 152 | dataType: 'json', 153 | autoUpload: false 154 | }; 155 | 156 | /** 157 | * Create field data object 158 | * @type {{}} 159 | */ 160 | var fieldData = {}; 161 | 162 | /** 163 | * Add field Data to field data object with fieldname 164 | * @param fieldName 165 | * @param fileData 166 | */ 167 | var addFieldData = function addFieldData(fieldName, fileData) { 168 | fieldData[fieldName].push(fileData); 169 | }; 170 | 171 | /** 172 | * Remove field data 173 | * @param fieldName 174 | * @param fileId 175 | */ 176 | var removeFieldData = function removeFieldData(fieldName, fileId) { 177 | angular.forEach(fieldData[fieldName], function (value, key) { 178 | if (value && value._id) { 179 | if (value._id === fileId) { 180 | fieldData[fieldName].splice(key, 1); 181 | } 182 | } 183 | }); 184 | }; 185 | 186 | /** 187 | * Register the field 188 | * @param fieldName 189 | * @param fieldData 190 | * @todo prefill with existing data 191 | */ 192 | var registerField = function registerField(fieldName) { 193 | if (!fieldData[fieldName]) { 194 | fieldData[fieldName] = []; 195 | } 196 | }; 197 | 198 | this.$get = [ 199 | function () { 200 | return { 201 | fieldData: fieldData, 202 | defaults: $config, 203 | addFieldData: addFieldData, 204 | removeFieldData: removeFieldData, 205 | registerField: registerField 206 | }; 207 | } 208 | ]; 209 | }) 210 | 211 | // Format byte numbers to readable presentations: 212 | .provider('formatFileSizeFilter', function () { 213 | var $config = { 214 | // Byte units following the IEC format 215 | // http://en.wikipedia.org/wiki/Kilobyte 216 | units: [ 217 | {size: 1000000000, suffix: ' GB'}, 218 | {size: 1000000, suffix: ' MB'}, 219 | {size: 1000, suffix: ' KB'} 220 | ] 221 | }; 222 | this.defaults = $config; 223 | this.$get = function () { 224 | return function (bytes) { 225 | if (!angular.isNumber(bytes)) { 226 | return ''; 227 | } 228 | var unit = true, 229 | i = 0, 230 | prefix, 231 | suffix; 232 | while (unit) { 233 | unit = $config.units[i]; 234 | prefix = unit.prefix || ''; 235 | suffix = unit.suffix || ''; 236 | if (i === $config.units.length - 1 || bytes >= unit.size) { 237 | return prefix + (bytes / unit.size).toFixed(2) + suffix; 238 | } 239 | i += 1; 240 | } 241 | }; 242 | }; 243 | }) 244 | 245 | // The FileUploadController initializes the fileupload widget and 246 | // provides scope methods to control the File Upload functionality: 247 | .controller('FileUploadController', [ 248 | '$scope', '$element', '$attrs', '$window', 'fileUpload', 249 | function ($scope, $element, $attrs, $window, fileUpload) { 250 | var uploadMethods = { 251 | progress: function () { 252 | return $element.fileupload('progress'); 253 | }, 254 | active: function () { 255 | return $element.fileupload('active'); 256 | }, 257 | option: function (option, data) { 258 | if (arguments.length === 1) { 259 | return $element.fileupload('option', option); 260 | } 261 | $element.fileupload('option', option, data); 262 | }, 263 | add: function (data) { 264 | return $element.fileupload('add', data); 265 | }, 266 | send: function (data) { 267 | return $element.fileupload('send', data); 268 | }, 269 | process: function (data) { 270 | return $element.fileupload('process', data); 271 | }, 272 | processing: function (data) { 273 | return $element.fileupload('processing', data); 274 | } 275 | }; 276 | $scope.disabled = !$window.jQuery.support.fileInput; 277 | $scope.queue = $scope.queue || []; 278 | $scope.clear = function (files) { 279 | var queue = this.queue, 280 | i = queue.length, 281 | file = files, 282 | length = 1; 283 | if (angular.isArray(files)) { 284 | file = files[0]; 285 | length = files.length; 286 | } 287 | while (i) { 288 | i -= 1; 289 | if (queue[i] === file) { 290 | return queue.splice(i, length); 291 | } 292 | } 293 | }; 294 | $scope.replace = function (oldFiles, newFiles) { 295 | var queue = this.queue, 296 | file = oldFiles[0], 297 | i, 298 | j; 299 | for (i = 0; i < queue.length; i += 1) { 300 | if (queue[i] === file) { 301 | for (j = 0; j < newFiles.length; j += 1) { 302 | queue[i + j] = newFiles[j]; 303 | } 304 | return; 305 | } 306 | } 307 | }; 308 | $scope.applyOnQueue = function (method) { 309 | var list = this.queue.slice(0), 310 | i, 311 | file; 312 | for (i = 0; i < list.length; i += 1) { 313 | file = list[i]; 314 | if (file[method]) { 315 | file[method](); 316 | } 317 | } 318 | }; 319 | $scope.submit = function () { 320 | this.applyOnQueue('$submit'); 321 | }; 322 | $scope.cancel = function () { 323 | this.applyOnQueue('$cancel'); 324 | }; 325 | // Add upload methods to the scope: 326 | angular.extend($scope, uploadMethods); 327 | // The fileupload widget will initialize with 328 | // the options provided via "data-"-parameters, 329 | // as well as those given via options object: 330 | $element.fileupload(angular.extend( 331 | {scope: $scope}, 332 | fileUpload.defaults 333 | )).on('fileuploadadd', function (e, data) { 334 | data.scope = $scope; 335 | }).on('fileuploadfail', function (e, data) { 336 | if (data.errorThrown === 'abort') { 337 | return; 338 | } 339 | if (data.dataType && 340 | data.dataType.indexOf('json') === data.dataType.length - 4) { 341 | try { 342 | data.result = angular.fromJson(data.jqXHR.responseText); 343 | } catch (ignore) { 344 | } 345 | } 346 | }).on([ 347 | 'fileuploadadd', 348 | 'fileuploadsubmit', 349 | 'fileuploadsend', 350 | 'fileuploaddone', 351 | 'fileuploadfail', 352 | 'fileuploadalways', 353 | 'fileuploadprogress', 354 | 'fileuploadprogressall', 355 | 'fileuploadstart', 356 | 'fileuploadstop', 357 | 'fileuploadchange', 358 | 'fileuploadpaste', 359 | 'fileuploaddrop', 360 | 'fileuploaddragover', 361 | 'fileuploadchunksend', 362 | 'fileuploadchunkdone', 363 | 'fileuploadchunkfail', 364 | 'fileuploadchunkalways', 365 | 'fileuploadprocessstart', 366 | 'fileuploadprocess', 367 | 'fileuploadprocessdone', 368 | 'fileuploadprocessfail', 369 | 'fileuploadprocessalways', 370 | 'fileuploadprocessstop' 371 | ].join(' '), function (e, data) { 372 | if ($scope.$emit(e.type, data).defaultPrevented) { 373 | e.preventDefault(); 374 | } 375 | }).on('remove', function () { 376 | // Remove upload methods from the scope, 377 | // when the widget is removed: 378 | var method; 379 | for (method in uploadMethods) { 380 | if (uploadMethods.hasOwnProperty(method)) { 381 | delete $scope[method]; 382 | } 383 | } 384 | }); 385 | // Observe option changes: 386 | $scope.$watch( 387 | $attrs.fileUpload, 388 | function (newOptions) { 389 | if (newOptions) { 390 | $element.fileupload('option', newOptions); 391 | } 392 | } 393 | ); 394 | } 395 | ]) 396 | 397 | // Provide File Upload progress feedback: 398 | .controller('FileUploadProgressController', [ 399 | '$scope', '$attrs', '$parse', 400 | function ($scope, $attrs, $parse) { 401 | var fn = $parse($attrs.fileUploadProgress), 402 | update = function () { 403 | var progress = fn($scope); 404 | 405 | if (!progress || !progress.total) { 406 | return; 407 | } 408 | $scope.num = Math.floor( 409 | progress.loaded / progress.total * 100 410 | ); 411 | }; 412 | update(); 413 | $scope.$watch( 414 | $attrs.fileUploadProgress + '.loaded', 415 | function (newValue, oldValue) { 416 | if (newValue !== oldValue) { 417 | update(); 418 | } 419 | } 420 | ); 421 | } 422 | ]) 423 | 424 | // Display File Upload previews: 425 | .controller('FileUploadPreviewController', [ 426 | '$scope', '$element', '$attrs', 427 | function ($scope, $element, $attrs) { 428 | $scope.$watch( 429 | $attrs.fileUploadPreview + '.preview', 430 | function (preview) { 431 | $element.empty(); 432 | if (preview) { 433 | $element.append(preview); 434 | } 435 | } 436 | ); 437 | } 438 | ]) 439 | 440 | .directive('fileUpload', function () { 441 | return { 442 | controller: 'FileUploadController', 443 | scope: true 444 | }; 445 | }) 446 | 447 | .directive('fileUploadProgress', function () { 448 | return { 449 | controller: 'FileUploadProgressController', 450 | scope: true 451 | }; 452 | }) 453 | 454 | .directive('fileUploadPreview', function () { 455 | return { 456 | controller: 'FileUploadPreviewController' 457 | }; 458 | }) 459 | 460 | // Enhance the HTML5 download attribute to 461 | // allow drag&drop of files to the desktop: 462 | .directive('download', function () { 463 | return function (scope, elm) { 464 | elm.on('dragstart', function (e) { 465 | try { 466 | e.originalEvent.dataTransfer.setData( 467 | 'DownloadURL', 468 | [ 469 | 'application/octet-stream', 470 | elm.prop('download'), 471 | elm.prop('href') 472 | ].join(':') 473 | ); 474 | } catch (ignore) { 475 | } 476 | }); 477 | }; 478 | }); 479 | 480 | })); -------------------------------------------------------------------------------- /js/vendor/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Widget 1.10.4+amd 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2014 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/jQuery.widget/ 10 | */ 11 | 12 | (function (factory) { 13 | if (typeof define === "function" && define.amd) { 14 | // Register as an anonymous AMD module: 15 | define(["jquery"], factory); 16 | } else { 17 | // Browser globals: 18 | factory(jQuery); 19 | } 20 | }(function ($, undefined) { 21 | 22 | var uuid = 0, 23 | slice = Array.prototype.slice, 24 | _cleanData = $.cleanData; 25 | $.cleanData = function (elems) { 26 | for (var i = 0, elem; (elem = elems[i]) != null; i++) { 27 | try { 28 | $(elem).triggerHandler("remove"); 29 | // http://bugs.jquery.com/ticket/8235 30 | } catch (e) { 31 | } 32 | } 33 | _cleanData(elems); 34 | }; 35 | 36 | $.widget = function (name, base, prototype) { 37 | var fullName, existingConstructor, constructor, basePrototype, 38 | // proxiedPrototype allows the provided prototype to remain unmodified 39 | // so that it can be used as a mixin for multiple widgets (#8876) 40 | proxiedPrototype = {}, 41 | namespace = name.split(".")[ 0 ]; 42 | 43 | name = name.split(".")[ 1 ]; 44 | fullName = namespace + "-" + name; 45 | 46 | if (!prototype) { 47 | prototype = base; 48 | base = $.Widget; 49 | } 50 | 51 | // create selector for plugin 52 | $.expr[ ":" ][ fullName.toLowerCase() ] = function (elem) { 53 | return !!$.data(elem, fullName); 54 | }; 55 | 56 | $[ namespace ] = $[ namespace ] || {}; 57 | existingConstructor = $[ namespace ][ name ]; 58 | constructor = $[ namespace ][ name ] = function (options, element) { 59 | // allow instantiation without "new" keyword 60 | if (!this._createWidget) { 61 | return new constructor(options, element); 62 | } 63 | 64 | // allow instantiation without initializing for simple inheritance 65 | // must use "new" keyword (the code above always passes args) 66 | if (arguments.length) { 67 | this._createWidget(options, element); 68 | } 69 | }; 70 | // extend with the existing constructor to carry over any static properties 71 | $.extend(constructor, existingConstructor, { 72 | version: prototype.version, 73 | // copy the object used to create the prototype in case we need to 74 | // redefine the widget later 75 | _proto: $.extend({}, prototype), 76 | // track widgets that inherit from this widget in case this widget is 77 | // redefined after a widget inherits from it 78 | _childConstructors: [] 79 | }); 80 | 81 | basePrototype = new base(); 82 | // we need to make the options hash a property directly on the new instance 83 | // otherwise we'll modify the options hash on the prototype that we're 84 | // inheriting from 85 | basePrototype.options = $.widget.extend({}, basePrototype.options); 86 | $.each(prototype, function (prop, value) { 87 | if (!$.isFunction(value)) { 88 | proxiedPrototype[ prop ] = value; 89 | return; 90 | } 91 | proxiedPrototype[ prop ] = (function () { 92 | var _super = function () { 93 | return base.prototype[ prop ].apply(this, arguments); 94 | }, 95 | _superApply = function (args) { 96 | return base.prototype[ prop ].apply(this, args); 97 | }; 98 | return function () { 99 | var __super = this._super, 100 | __superApply = this._superApply, 101 | returnValue; 102 | 103 | this._super = _super; 104 | this._superApply = _superApply; 105 | 106 | returnValue = value.apply(this, arguments); 107 | 108 | this._super = __super; 109 | this._superApply = __superApply; 110 | 111 | return returnValue; 112 | }; 113 | })(); 114 | }); 115 | constructor.prototype = $.widget.extend(basePrototype, { 116 | // TODO: remove support for widgetEventPrefix 117 | // always use the name + a colon as the prefix, e.g., draggable:start 118 | // don't prefix for widgets that aren't DOM-based 119 | widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name 120 | }, proxiedPrototype, { 121 | constructor: constructor, 122 | namespace: namespace, 123 | widgetName: name, 124 | widgetFullName: fullName 125 | }); 126 | 127 | // If this widget is being redefined then we need to find all widgets that 128 | // are inheriting from it and redefine all of them so that they inherit from 129 | // the new version of this widget. We're essentially trying to replace one 130 | // level in the prototype chain. 131 | if (existingConstructor) { 132 | $.each(existingConstructor._childConstructors, function (i, child) { 133 | var childPrototype = child.prototype; 134 | 135 | // redefine the child widget using the same prototype that was 136 | // originally used, but inherit from the new version of the base 137 | $.widget(childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto); 138 | }); 139 | // remove the list of existing child constructors from the old constructor 140 | // so the old child constructors can be garbage collected 141 | delete existingConstructor._childConstructors; 142 | } else { 143 | base._childConstructors.push(constructor); 144 | } 145 | 146 | $.widget.bridge(name, constructor); 147 | }; 148 | 149 | $.widget.extend = function (target) { 150 | var input = slice.call(arguments, 1), 151 | inputIndex = 0, 152 | inputLength = input.length, 153 | key, 154 | value; 155 | for (; inputIndex < inputLength; inputIndex++) { 156 | for (key in input[ inputIndex ]) { 157 | value = input[ inputIndex ][ key ]; 158 | if (input[ inputIndex ].hasOwnProperty(key) && value !== undefined) { 159 | // Clone objects 160 | if ($.isPlainObject(value)) { 161 | target[ key ] = $.isPlainObject(target[ key ]) ? 162 | $.widget.extend({}, target[ key ], value) : 163 | // Don't extend strings, arrays, etc. with objects 164 | $.widget.extend({}, value); 165 | // Copy everything else by reference 166 | } else { 167 | target[ key ] = value; 168 | } 169 | } 170 | } 171 | } 172 | return target; 173 | }; 174 | 175 | $.widget.bridge = function (name, object) { 176 | var fullName = object.prototype.widgetFullName || name; 177 | $.fn[ name ] = function (options) { 178 | var isMethodCall = typeof options === "string", 179 | args = slice.call(arguments, 1), 180 | returnValue = this; 181 | 182 | // allow multiple hashes to be passed on init 183 | options = !isMethodCall && args.length ? 184 | $.widget.extend.apply(null, [ options ].concat(args)) : 185 | options; 186 | 187 | if (isMethodCall) { 188 | this.each(function () { 189 | var methodValue, 190 | instance = $.data(this, fullName); 191 | if (!instance) { 192 | return $.error("cannot call methods on " + name + " prior to initialization; " + 193 | "attempted to call method '" + options + "'"); 194 | } 195 | if (!$.isFunction(instance[options]) || options.charAt(0) === "_") { 196 | return $.error("no such method '" + options + "' for " + name + " widget instance"); 197 | } 198 | methodValue = instance[ options ].apply(instance, args); 199 | if (methodValue !== instance && methodValue !== undefined) { 200 | returnValue = methodValue && methodValue.jquery ? 201 | returnValue.pushStack(methodValue.get()) : 202 | methodValue; 203 | return false; 204 | } 205 | }); 206 | } else { 207 | this.each(function () { 208 | var instance = $.data(this, fullName); 209 | if (instance) { 210 | instance.option(options || {})._init(); 211 | } else { 212 | $.data(this, fullName, new object(options, this)); 213 | } 214 | }); 215 | } 216 | 217 | return returnValue; 218 | }; 219 | }; 220 | 221 | $.Widget = function (/* options, element */) { 222 | }; 223 | $.Widget._childConstructors = []; 224 | 225 | $.Widget.prototype = { 226 | widgetName: "widget", 227 | widgetEventPrefix: "", 228 | defaultElement: "
    ", 229 | options: { 230 | disabled: false, 231 | 232 | // callbacks 233 | create: null 234 | }, 235 | _createWidget: function (options, element) { 236 | element = $(element || this.defaultElement || this)[ 0 ]; 237 | this.element = $(element); 238 | this.uuid = uuid++; 239 | this.eventNamespace = "." + this.widgetName + this.uuid; 240 | this.options = $.widget.extend({}, 241 | this.options, 242 | this._getCreateOptions(), 243 | options); 244 | 245 | this.bindings = $(); 246 | this.hoverable = $(); 247 | this.focusable = $(); 248 | 249 | if (element !== this) { 250 | $.data(element, this.widgetFullName, this); 251 | this._on(true, this.element, { 252 | remove: function (event) { 253 | if (event.target === element) { 254 | this.destroy(); 255 | } 256 | } 257 | }); 258 | this.document = $(element.style ? 259 | // element within the document 260 | element.ownerDocument : 261 | // element is window or document 262 | element.document || element); 263 | this.window = $(this.document[0].defaultView || this.document[0].parentWindow); 264 | } 265 | 266 | this._create(); 267 | this._trigger("create", null, this._getCreateEventData()); 268 | this._init(); 269 | }, 270 | _getCreateOptions: $.noop, 271 | _getCreateEventData: $.noop, 272 | _create: $.noop, 273 | _init: $.noop, 274 | 275 | destroy: function () { 276 | this._destroy(); 277 | // we can probably remove the unbind calls in 2.0 278 | // all event bindings should go through this._on() 279 | this.element 280 | .unbind(this.eventNamespace) 281 | // 1.9 BC for #7810 282 | // TODO remove dual storage 283 | .removeData(this.widgetName) 284 | .removeData(this.widgetFullName) 285 | // support: jquery <1.6.3 286 | // http://bugs.jquery.com/ticket/9413 287 | .removeData($.camelCase(this.widgetFullName)); 288 | this.widget() 289 | .unbind(this.eventNamespace) 290 | .removeAttr("aria-disabled") 291 | .removeClass( 292 | this.widgetFullName + "-disabled " + 293 | "ui-state-disabled"); 294 | 295 | // clean up events and states 296 | this.bindings.unbind(this.eventNamespace); 297 | this.hoverable.removeClass("ui-state-hover"); 298 | this.focusable.removeClass("ui-state-focus"); 299 | }, 300 | _destroy: $.noop, 301 | 302 | widget: function () { 303 | return this.element; 304 | }, 305 | 306 | option: function (key, value) { 307 | var options = key, 308 | parts, 309 | curOption, 310 | i; 311 | 312 | if (arguments.length === 0) { 313 | // don't return a reference to the internal hash 314 | return $.widget.extend({}, this.options); 315 | } 316 | 317 | if (typeof key === "string") { 318 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } 319 | options = {}; 320 | parts = key.split("."); 321 | key = parts.shift(); 322 | if (parts.length) { 323 | curOption = options[ key ] = $.widget.extend({}, this.options[ key ]); 324 | for (i = 0; i < parts.length - 1; i++) { 325 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; 326 | curOption = curOption[ parts[ i ] ]; 327 | } 328 | key = parts.pop(); 329 | if (arguments.length === 1) { 330 | return curOption[ key ] === undefined ? null : curOption[ key ]; 331 | } 332 | curOption[ key ] = value; 333 | } else { 334 | if (arguments.length === 1) { 335 | return this.options[ key ] === undefined ? null : this.options[ key ]; 336 | } 337 | options[ key ] = value; 338 | } 339 | } 340 | 341 | this._setOptions(options); 342 | 343 | return this; 344 | }, 345 | _setOptions: function (options) { 346 | var key; 347 | 348 | for (key in options) { 349 | this._setOption(key, options[ key ]); 350 | } 351 | 352 | return this; 353 | }, 354 | _setOption: function (key, value) { 355 | this.options[ key ] = value; 356 | 357 | if (key === "disabled") { 358 | this.widget() 359 | .toggleClass(this.widgetFullName + "-disabled ui-state-disabled", !!value) 360 | .attr("aria-disabled", value); 361 | this.hoverable.removeClass("ui-state-hover"); 362 | this.focusable.removeClass("ui-state-focus"); 363 | } 364 | 365 | return this; 366 | }, 367 | 368 | enable: function () { 369 | return this._setOption("disabled", false); 370 | }, 371 | disable: function () { 372 | return this._setOption("disabled", true); 373 | }, 374 | 375 | _on: function (suppressDisabledCheck, element, handlers) { 376 | var delegateElement, 377 | instance = this; 378 | 379 | // no suppressDisabledCheck flag, shuffle arguments 380 | if (typeof suppressDisabledCheck !== "boolean") { 381 | handlers = element; 382 | element = suppressDisabledCheck; 383 | suppressDisabledCheck = false; 384 | } 385 | 386 | // no element argument, shuffle and use this.element 387 | if (!handlers) { 388 | handlers = element; 389 | element = this.element; 390 | delegateElement = this.widget(); 391 | } else { 392 | // accept selectors, DOM elements 393 | element = delegateElement = $(element); 394 | this.bindings = this.bindings.add(element); 395 | } 396 | 397 | $.each(handlers, function (event, handler) { 398 | function handlerProxy() { 399 | // allow widgets to customize the disabled handling 400 | // - disabled as an array instead of boolean 401 | // - disabled class as method for disabling individual parts 402 | if (!suppressDisabledCheck && 403 | ( instance.options.disabled === true || 404 | $(this).hasClass("ui-state-disabled") )) { 405 | return; 406 | } 407 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 408 | .apply(instance, arguments); 409 | } 410 | 411 | // copy the guid so direct unbinding works 412 | if (typeof handler !== "string") { 413 | handlerProxy.guid = handler.guid = 414 | handler.guid || handlerProxy.guid || $.guid++; 415 | } 416 | 417 | var match = event.match(/^(\w+)\s*(.*)$/), 418 | eventName = match[1] + instance.eventNamespace, 419 | selector = match[2]; 420 | if (selector) { 421 | delegateElement.delegate(selector, eventName, handlerProxy); 422 | } else { 423 | element.bind(eventName, handlerProxy); 424 | } 425 | }); 426 | }, 427 | 428 | _off: function (element, eventName) { 429 | eventName = (eventName || "").split(" ").join(this.eventNamespace + " ") + this.eventNamespace; 430 | element.unbind(eventName).undelegate(eventName); 431 | }, 432 | 433 | _delay: function (handler, delay) { 434 | function handlerProxy() { 435 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 436 | .apply(instance, arguments); 437 | } 438 | 439 | var instance = this; 440 | return setTimeout(handlerProxy, delay || 0); 441 | }, 442 | 443 | _hoverable: function (element) { 444 | this.hoverable = this.hoverable.add(element); 445 | this._on(element, { 446 | mouseenter: function (event) { 447 | $(event.currentTarget).addClass("ui-state-hover"); 448 | }, 449 | mouseleave: function (event) { 450 | $(event.currentTarget).removeClass("ui-state-hover"); 451 | } 452 | }); 453 | }, 454 | 455 | _focusable: function (element) { 456 | this.focusable = this.focusable.add(element); 457 | this._on(element, { 458 | focusin: function (event) { 459 | $(event.currentTarget).addClass("ui-state-focus"); 460 | }, 461 | focusout: function (event) { 462 | $(event.currentTarget).removeClass("ui-state-focus"); 463 | } 464 | }); 465 | }, 466 | 467 | _trigger: function (type, event, data) { 468 | var prop, orig, 469 | callback = this.options[ type ]; 470 | 471 | data = data || {}; 472 | event = $.Event(event); 473 | event.type = ( type === this.widgetEventPrefix ? 474 | type : 475 | this.widgetEventPrefix + type ).toLowerCase(); 476 | // the original event may come from any element 477 | // so we need to reset the target on the new event 478 | event.target = this.element[ 0 ]; 479 | 480 | // copy original event properties over to the new event 481 | orig = event.originalEvent; 482 | if (orig) { 483 | for (prop in orig) { 484 | if (!( prop in event )) { 485 | event[ prop ] = orig[ prop ]; 486 | } 487 | } 488 | } 489 | 490 | this.element.trigger(event, data); 491 | return !( $.isFunction(callback) && 492 | callback.apply(this.element[0], [ event ].concat(data)) === false || 493 | event.isDefaultPrevented() ); 494 | } 495 | }; 496 | 497 | $.each({ show: "fadeIn", hide: "fadeOut" }, function (method, defaultEffect) { 498 | $.Widget.prototype[ "_" + method ] = function (element, options, callback) { 499 | if (typeof options === "string") { 500 | options = { effect: options }; 501 | } 502 | var hasOptions, 503 | effectName = !options ? 504 | method : 505 | options === true || typeof options === "number" ? 506 | defaultEffect : 507 | options.effect || defaultEffect; 508 | options = options || {}; 509 | if (typeof options === "number") { 510 | options = { duration: options }; 511 | } 512 | hasOptions = !$.isEmptyObject(options); 513 | options.complete = callback; 514 | if (options.delay) { 515 | element.delay(options.delay); 516 | } 517 | if (hasOptions && $.effects && $.effects.effect[ effectName ]) { 518 | element[ method ](options); 519 | } else if (effectName !== method && element[ effectName ]) { 520 | element[ effectName ](options.duration, options.easing, callback); 521 | } else { 522 | element.queue(function (next) { 523 | $(this)[ method ](); 524 | if (callback) { 525 | callback.call(element[ 0 ]); 526 | } 527 | next(); 528 | }); 529 | } 530 | }; 531 | }); 532 | 533 | })); 534 | --------------------------------------------------------------------------------