├── .static ├── .gitignore ├── server ├── 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 │ ├── docker-compose.yml │ ├── index.php │ └── Dockerfile ├── 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 ├── bower-version-update.js ├── js ├── main.js ├── cors │ ├── jquery.xdr-transport.js │ └── jquery.postmessage-transport.js ├── app.js ├── jquery.fileupload-audio.js ├── jquery.fileupload-video.js ├── jquery.fileupload-validate.js ├── jquery.fileupload-jquery-ui.js ├── jquery.fileupload-process.js ├── jquery.iframe-transport.js ├── jquery.fileupload-image.js ├── vendor │ └── jquery.ui.widget.js └── jquery.fileupload-angular.js ├── cors ├── result.html └── postmessage.html ├── CONTRIBUTING.md ├── .npmignore ├── README.md ├── bower.json ├── .jshintrc ├── basic.html ├── test └── index.html ├── index.html ├── basic-plus.html ├── jquery-ui.html └── angularjs.html /.static: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | node_modules 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/jQuery-File-Upload/master/img/loading.gif -------------------------------------------------------------------------------- /img/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/jQuery-File-Upload/master/img/progressbar.gif -------------------------------------------------------------------------------- /server/gae-go/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/jQuery-File-Upload/master/server/gae-go/static/favicon.ico -------------------------------------------------------------------------------- /server/gae-python/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicjs/jQuery-File-Upload/master/server/gae-python/static/favicon.ico -------------------------------------------------------------------------------- /server/php/docker-compose.yml: -------------------------------------------------------------------------------- 1 | apache: 2 | build: ./ 3 | ports: 4 | - "80:80" 5 | volumes: 6 | - "../../:/var/www/html" 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS Example 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/gae-python/app.yaml: -------------------------------------------------------------------------------- 1 | application: jquery-file-upload 2 | version: 1 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | libraries: 8 | - name: PIL 9 | version: latest 10 | 11 | handlers: 12 | - url: /(favicon\.ico|robots\.txt) 13 | static_files: static/\1 14 | upload: static/(.*) 15 | expiration: '1d' 16 | - url: /.* 17 | script: main.app 18 | -------------------------------------------------------------------------------- /server/php/index.php: -------------------------------------------------------------------------------- 1 | '); 9 | $('.template-download .name').html(media.original_name); 10 | $('.template-download .size').html(Math.round(media.size / 1000) + 'KB'); 11 | }, 300); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /cors/result.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | jQuery Iframe Transport Plugin Redirect Page 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please follow these pull request guidelines: 2 | 3 | 1. Update your fork to the latest upstream version. 4 | 5 | 2. Follow the coding conventions of the original source files (indentation, spaces, brackets layout). 6 | 7 | 3. Code changes must pass JSHint validation with the `.jshintrc` settings of this project. 8 | 9 | 4. Code changes must pass the QUnit tests defined in the `test` folder. 10 | 11 | 5. New features should be covered by accompanying QUnit tests. 12 | 13 | 6. Keep your commits as atomic as possible, i.e. create a new commit for every single bug fix or feature added. 14 | 15 | 7. Always add meaningful commit messages. 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !css/jquery.fileupload-noscript.css 3 | !css/jquery.fileupload-ui-noscript.css 4 | !css/jquery.fileupload-ui.css 5 | !css/jquery.fileupload.css 6 | !img/loading.gif 7 | !img/progressbar.gif 8 | !js/cors/jquery.postmessage-transport.js 9 | !js/cors/jquery.xdr-transport.js 10 | !js/vendor/jquery.ui.widget.js 11 | !js/jquery.fileupload-angular.js 12 | !js/jquery.fileupload-audio.js 13 | !js/jquery.fileupload-image.js 14 | !js/jquery.fileupload-jquery-ui.js 15 | !js/jquery.fileupload-process.js 16 | !js/jquery.fileupload-ui.js 17 | !js/jquery.fileupload-validate.js 18 | !js/jquery.fileupload-video.js 19 | !js/jquery.fileupload.js 20 | !js/jquery.iframe-transport.js 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery File Upload Plugin 2 | ## Demo 3 | [Demo File Upload](http://jquery-file-upload.cosmicapp.co/) 4 | 5 | ## Description 6 | This repo demonstrates how to upload media to your [Cosmic JS](https://cosmicjs.com) Bucket using the popular jQuery-File-Upload library by [blueimp](https://github.com/blueimp). 7 | 8 | ## Getting Started 9 | ``` 10 | git clone https://github.com/cosmicjs/jQuery-File-Upload 11 | cd jQuery-File-Upload 12 | http-server 13 | ``` 14 | Go to http://localhost:8080 to upload media to a demo Bucket. 15 | 16 | Create your own Bucket at [Cosmic JS](https://cosmicjs.com) 17 | 18 | Connect to your own Bucket by editing the URL endpoint in [js/main.js](https://github.com/cosmicjs/jQuery-File-Upload/blob/master/js/main.js) 19 | -------------------------------------------------------------------------------- /css/jquery.fileupload.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS 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 | display: inline-block; 17 | } 18 | .fileinput-button input { 19 | position: absolute; 20 | top: 0; 21 | right: 0; 22 | margin: 0; 23 | opacity: 0; 24 | -ms-filter: 'alpha(opacity=0)'; 25 | font-size: 200px !important; 26 | direction: ltr; 27 | cursor: pointer; 28 | } 29 | 30 | /* Fixes for IE < 8 */ 31 | @media screen\9 { 32 | .fileinput-button input { 33 | filter: alpha(opacity=0); 34 | font-size: 100%; 35 | height: 100%; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/php/files/.htaccess: -------------------------------------------------------------------------------- 1 | # The following directives prevent the execution of script files 2 | # in the context of the website. 3 | # They also force the content-type application/octet-stream and 4 | # force browsers to display a download dialog for non-image files. 5 | SetHandler default-handler 6 | ForceType application/octet-stream 7 | Header set Content-Disposition attachment 8 | 9 | # The following unsets the forced type and Content-Disposition headers 10 | # for known image files: 11 | 12 | ForceType none 13 | Header unset Content-Disposition 14 | 15 | 16 | # The following directive prevents browsers from MIME-sniffing the content-type. 17 | # This is an important complement to the ForceType directive above: 18 | Header set X-Content-Type-Options nosniff 19 | 20 | # Uncomment the following lines to prevent unauthorized download of files: 21 | #AuthName "Authorization required" 22 | #AuthType Basic 23 | #require valid-user 24 | -------------------------------------------------------------------------------- /server/php/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-apache 2 | 3 | # Enable the Apache Headers module: 4 | RUN ln -s /etc/apache2/mods-available/headers.load \ 5 | /etc/apache2/mods-enabled/headers.load 6 | 7 | # Enable the Apache Rewrite module: 8 | RUN ln -s /etc/apache2/mods-available/rewrite.load \ 9 | /etc/apache2/mods-enabled/rewrite.load 10 | 11 | # Install GD, Imagick and ImageMagick as image conversion options: 12 | RUN DEBIAN_FRONTEND=noninteractive \ 13 | apt-get update && apt-get install -y --no-install-recommends \ 14 | libpng-dev \ 15 | libjpeg-dev \ 16 | libmagickwand-dev \ 17 | imagemagick \ 18 | && pecl install \ 19 | imagick \ 20 | && docker-php-ext-enable \ 21 | imagick \ 22 | && docker-php-ext-configure \ 23 | gd --with-jpeg-dir=/usr/include/ \ 24 | && docker-php-ext-install \ 25 | gd \ 26 | # Uninstall obsolete packages: 27 | && apt-get autoremove -y \ 28 | libpng-dev \ 29 | libjpeg-dev \ 30 | libmagickwand-dev \ 31 | # Remove obsolete files: 32 | && apt-get clean \ 33 | && rm -rf \ 34 | /tmp/* \ 35 | /usr/share/doc/* \ 36 | /var/cache/* \ 37 | /var/lib/apt/lists/* \ 38 | /var/tmp/* 39 | -------------------------------------------------------------------------------- /css/jquery.fileupload-ui.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 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 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 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-file-upload", 3 | "version": "9.14.1", 4 | "title": "jQuery File Upload", 5 | "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images.", 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/blueimp/jQuery-File-Upload", 28 | "author": { 29 | "name": "Sebastian Tschan", 30 | "url": "https://blueimp.net" 31 | }, 32 | "maintainers": [ 33 | { 34 | "name": "Sebastian Tschan", 35 | "url": "https://blueimp.net" 36 | } 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/blueimp/jQuery-File-Upload.git" 41 | }, 42 | "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues", 43 | "license": "MIT", 44 | "dependencies": { 45 | "jquery": ">=1.6", 46 | "blueimp-tmpl": ">=2.5.4", 47 | "blueimp-load-image": ">=1.13.0", 48 | "blueimp-canvas-to-blob": ">=2.1.1" 49 | }, 50 | "main": [ 51 | "js/jquery.fileupload.js" 52 | ], 53 | "ignore": [ 54 | "/*.*", 55 | "/cors", 56 | "css/demo-ie8.css", 57 | "css/demo.css", 58 | "css/style.css", 59 | "js/app.js", 60 | "js/main.js", 61 | "server", 62 | "test" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /cors/postmessage.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | jQuery File Upload Plugin postMessage API 18 | 19 | 20 | 21 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /js/cors/jquery.xdr-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery XDomainRequest Transport Plugin 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, require, 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 if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory(window.jQuery); 28 | } 29 | }(function ($) { 30 | 'use strict'; 31 | if (window.XDomainRequest && !$.support.cors) { 32 | $.ajaxTransport(function (s) { 33 | if (s.crossDomain && s.async) { 34 | if (s.timeout) { 35 | s.xdrTimeout = s.timeout; 36 | delete s.timeout; 37 | } 38 | var xdr; 39 | return { 40 | send: function (headers, completeCallback) { 41 | var addParamChar = /\?/.test(s.url) ? '&' : '?'; 42 | function callback(status, statusText, responses, responseHeaders) { 43 | xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; 44 | xdr = null; 45 | completeCallback(status, statusText, responses, responseHeaders); 46 | } 47 | xdr = new XDomainRequest(); 48 | // XDomainRequest only supports GET and POST: 49 | if (s.type === 'DELETE') { 50 | s.url = s.url + addParamChar + '_method=DELETE'; 51 | s.type = 'POST'; 52 | } else if (s.type === 'PUT') { 53 | s.url = s.url + addParamChar + '_method=PUT'; 54 | s.type = 'POST'; 55 | } else if (s.type === 'PATCH') { 56 | s.url = s.url + addParamChar + '_method=PATCH'; 57 | s.type = 'POST'; 58 | } 59 | xdr.open(s.type, s.url); 60 | xdr.onload = function () { 61 | callback( 62 | 200, 63 | 'OK', 64 | {text: xdr.responseText}, 65 | 'Content-Type: ' + xdr.contentType 66 | ); 67 | }; 68 | xdr.onerror = function () { 69 | callback(404, 'Not Found'); 70 | }; 71 | if (s.xdrTimeout) { 72 | xdr.ontimeout = function () { 73 | callback(0, 'timeout'); 74 | }; 75 | xdr.timeout = s.xdrTimeout; 76 | } 77 | xdr.send((s.hasContent && s.data) || null); 78 | }, 79 | abort: function () { 80 | if (xdr) { 81 | xdr.onerror = $.noop(); 82 | xdr.abort(); 83 | } 84 | } 85 | }; 86 | } 87 | }); 88 | } 89 | })); 90 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin Angular JS Example 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 window, angular */ 14 | 15 | ;(function () { 16 | 'use strict'; 17 | 18 | var isOnGitHub = window.location.hostname === 'blueimp.github.io', 19 | url = isOnGitHub ? '//jquery-file-upload.appspot.com/' : 'server/php/'; 20 | 21 | angular.module('demo', [ 22 | 'blueimp.fileupload' 23 | ]) 24 | .config([ 25 | '$httpProvider', 'fileUploadProvider', 26 | function ($httpProvider, fileUploadProvider) { 27 | delete $httpProvider.defaults.headers.common['X-Requested-With']; 28 | fileUploadProvider.defaults.redirect = window.location.href.replace( 29 | /\/[^\/]*$/, 30 | '/cors/result.html?%s' 31 | ); 32 | if (isOnGitHub) { 33 | // Demo settings: 34 | angular.extend(fileUploadProvider.defaults, { 35 | // Enable image resizing, except for Android and Opera, 36 | // which actually support image resizing, but fail to 37 | // send Blob objects via XHR requests: 38 | disableImageResize: /Android(?!.*Chrome)|Opera/ 39 | .test(window.navigator.userAgent), 40 | maxFileSize: 999000, 41 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i 42 | }); 43 | } 44 | } 45 | ]) 46 | 47 | .controller('DemoFileUploadController', [ 48 | '$scope', '$http', '$filter', '$window', 49 | function ($scope, $http) { 50 | $scope.options = { 51 | url: url 52 | }; 53 | if (!isOnGitHub) { 54 | $scope.loadingFiles = true; 55 | $http.get(url) 56 | .then( 57 | function (response) { 58 | $scope.loadingFiles = false; 59 | $scope.queue = response.data.files || []; 60 | }, 61 | function () { 62 | $scope.loadingFiles = false; 63 | } 64 | ); 65 | } 66 | } 67 | ]) 68 | 69 | .controller('FileDestroyController', [ 70 | '$scope', '$http', 71 | function ($scope, $http) { 72 | var file = $scope.file, 73 | state; 74 | if (file.url) { 75 | file.$state = function () { 76 | return state; 77 | }; 78 | file.$destroy = function () { 79 | state = 'pending'; 80 | return $http({ 81 | url: file.deleteUrl, 82 | method: file.deleteType 83 | }).then( 84 | function () { 85 | state = 'resolved'; 86 | $scope.clear(file); 87 | }, 88 | function () { 89 | state = 'rejected'; 90 | } 91 | ); 92 | }; 93 | } else if (!file.$cancel && !file._index) { 94 | file.$cancel = function () { 95 | $scope.clear(file); 96 | }; 97 | } 98 | } 99 | ]); 100 | 101 | }()); 102 | -------------------------------------------------------------------------------- /js/jquery.fileupload-audio.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Audio Preview Plugin 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, require, 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 if (typeof exports === 'object') { 25 | // Node/CommonJS: 26 | factory( 27 | require('jquery'), 28 | require('blueimp-load-image/js/load-image'), 29 | require('./jquery.fileupload-process') 30 | ); 31 | } else { 32 | // Browser globals: 33 | factory( 34 | window.jQuery, 35 | window.loadImage 36 | ); 37 | } 38 | }(function ($, loadImage) { 39 | 'use strict'; 40 | 41 | // Prepend to the default processQueue: 42 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 43 | { 44 | action: 'loadAudio', 45 | // Use the action as prefix for the "@" options: 46 | prefix: true, 47 | fileTypes: '@', 48 | maxFileSize: '@', 49 | disabled: '@disableAudioPreview' 50 | }, 51 | { 52 | action: 'setAudio', 53 | name: '@audioPreviewName', 54 | disabled: '@disableAudioPreview' 55 | } 56 | ); 57 | 58 | // The File Upload Audio Preview plugin extends the fileupload widget 59 | // with audio preview functionality: 60 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 61 | 62 | options: { 63 | // The regular expression for the types of audio files to load, 64 | // matched against the file type: 65 | loadAudioFileTypes: /^audio\/.*$/ 66 | }, 67 | 68 | _audioElement: document.createElement('audio'), 69 | 70 | processActions: { 71 | 72 | // Loads the audio file given via data.files and data.index 73 | // as audio element if the browser supports playing it. 74 | // Accepts the options fileTypes (regular expression) 75 | // and maxFileSize (integer) to limit the files to load: 76 | loadAudio: function (data, options) { 77 | if (options.disabled) { 78 | return data; 79 | } 80 | var file = data.files[data.index], 81 | url, 82 | audio; 83 | if (this._audioElement.canPlayType && 84 | this._audioElement.canPlayType(file.type) && 85 | ($.type(options.maxFileSize) !== 'number' || 86 | file.size <= options.maxFileSize) && 87 | (!options.fileTypes || 88 | options.fileTypes.test(file.type))) { 89 | url = loadImage.createObjectURL(file); 90 | if (url) { 91 | audio = this._audioElement.cloneNode(false); 92 | audio.src = url; 93 | audio.controls = true; 94 | data.audio = audio; 95 | return data; 96 | } 97 | } 98 | return data; 99 | }, 100 | 101 | // Sets the audio element as a property of the file object: 102 | setAudio: function (data, options) { 103 | if (data.audio && !options.disabled) { 104 | data.files[data.index][options.name || 'preview'] = data.audio; 105 | } 106 | return data; 107 | } 108 | 109 | } 110 | 111 | }); 112 | 113 | })); 114 | -------------------------------------------------------------------------------- /js/jquery.fileupload-video.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Video Preview Plugin 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, require, 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 if (typeof exports === 'object') { 25 | // Node/CommonJS: 26 | factory( 27 | require('jquery'), 28 | require('blueimp-load-image/js/load-image'), 29 | require('./jquery.fileupload-process') 30 | ); 31 | } else { 32 | // Browser globals: 33 | factory( 34 | window.jQuery, 35 | window.loadImage 36 | ); 37 | } 38 | }(function ($, loadImage) { 39 | 'use strict'; 40 | 41 | // Prepend to the default processQueue: 42 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 43 | { 44 | action: 'loadVideo', 45 | // Use the action as prefix for the "@" options: 46 | prefix: true, 47 | fileTypes: '@', 48 | maxFileSize: '@', 49 | disabled: '@disableVideoPreview' 50 | }, 51 | { 52 | action: 'setVideo', 53 | name: '@videoPreviewName', 54 | disabled: '@disableVideoPreview' 55 | } 56 | ); 57 | 58 | // The File Upload Video Preview plugin extends the fileupload widget 59 | // with video preview functionality: 60 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 61 | 62 | options: { 63 | // The regular expression for the types of video files to load, 64 | // matched against the file type: 65 | loadVideoFileTypes: /^video\/.*$/ 66 | }, 67 | 68 | _videoElement: document.createElement('video'), 69 | 70 | processActions: { 71 | 72 | // Loads the video file given via data.files and data.index 73 | // as video element if the browser supports playing it. 74 | // Accepts the options fileTypes (regular expression) 75 | // and maxFileSize (integer) to limit the files to load: 76 | loadVideo: function (data, options) { 77 | if (options.disabled) { 78 | return data; 79 | } 80 | var file = data.files[data.index], 81 | url, 82 | video; 83 | if (this._videoElement.canPlayType && 84 | this._videoElement.canPlayType(file.type) && 85 | ($.type(options.maxFileSize) !== 'number' || 86 | file.size <= options.maxFileSize) && 87 | (!options.fileTypes || 88 | options.fileTypes.test(file.type))) { 89 | url = loadImage.createObjectURL(file); 90 | if (url) { 91 | video = this._videoElement.cloneNode(false); 92 | video.src = url; 93 | video.controls = true; 94 | data.video = video; 95 | return data; 96 | } 97 | } 98 | return data; 99 | }, 100 | 101 | // Sets the video element as a property of the file object: 102 | setVideo: function (data, options) { 103 | if (data.video && !options.disabled) { 104 | data.files[data.index][options.name || 'preview'] = data.video; 105 | } 106 | return data; 107 | } 108 | 109 | } 110 | 111 | }); 112 | 113 | })); 114 | -------------------------------------------------------------------------------- /js/jquery.fileupload-validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Validation Plugin 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, require, 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 if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory( 25 | require('jquery'), 26 | require('./jquery.fileupload-process') 27 | ); 28 | } else { 29 | // Browser globals: 30 | factory( 31 | window.jQuery 32 | ); 33 | } 34 | }(function ($) { 35 | 'use strict'; 36 | 37 | // Append to the default processQueue: 38 | $.blueimp.fileupload.prototype.options.processQueue.push( 39 | { 40 | action: 'validate', 41 | // Always trigger this action, 42 | // even if the previous action was rejected: 43 | always: true, 44 | // Options taken from the global options map: 45 | acceptFileTypes: '@', 46 | maxFileSize: '@', 47 | minFileSize: '@', 48 | maxNumberOfFiles: '@', 49 | disabled: '@disableValidation' 50 | } 51 | ); 52 | 53 | // The File Upload Validation plugin extends the fileupload widget 54 | // with file validation functionality: 55 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 56 | 57 | options: { 58 | /* 59 | // The regular expression for allowed file types, matches 60 | // against either file type or file name: 61 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 62 | // The maximum allowed file size in bytes: 63 | maxFileSize: 10000000, // 10 MB 64 | // The minimum allowed file size in bytes: 65 | minFileSize: undefined, // No minimal file size 66 | // The limit of files to be uploaded: 67 | maxNumberOfFiles: 10, 68 | */ 69 | 70 | // Function returning the current number of files, 71 | // has to be overriden for maxNumberOfFiles validation: 72 | getNumberOfFiles: $.noop, 73 | 74 | // Error and info messages: 75 | messages: { 76 | maxNumberOfFiles: 'Maximum number of files exceeded', 77 | acceptFileTypes: 'File type not allowed', 78 | maxFileSize: 'File is too large', 79 | minFileSize: 'File is too small' 80 | } 81 | }, 82 | 83 | processActions: { 84 | 85 | validate: function (data, options) { 86 | if (options.disabled) { 87 | return data; 88 | } 89 | var dfd = $.Deferred(), 90 | settings = this.options, 91 | file = data.files[data.index], 92 | fileSize; 93 | if (options.minFileSize || options.maxFileSize) { 94 | fileSize = file.size; 95 | } 96 | if ($.type(options.maxNumberOfFiles) === 'number' && 97 | (settings.getNumberOfFiles() || 0) + data.files.length > 98 | options.maxNumberOfFiles) { 99 | file.error = settings.i18n('maxNumberOfFiles'); 100 | } else if (options.acceptFileTypes && 101 | !(options.acceptFileTypes.test(file.type) || 102 | options.acceptFileTypes.test(file.name))) { 103 | file.error = settings.i18n('acceptFileTypes'); 104 | } else if (fileSize > options.maxFileSize) { 105 | file.error = settings.i18n('maxFileSize'); 106 | } else if ($.type(fileSize) === 'number' && 107 | fileSize < options.minFileSize) { 108 | file.error = settings.i18n('minFileSize'); 109 | } else { 110 | delete file.error; 111 | } 112 | if (file.error || data.files.error) { 113 | data.files.error = true; 114 | dfd.rejectWith(this, [data]); 115 | } else { 116 | dfd.resolveWith(this, [data]); 117 | } 118 | return dfd.promise(); 119 | } 120 | 121 | } 122 | 123 | }); 124 | 125 | })); 126 | -------------------------------------------------------------------------------- /js/cors/jquery.postmessage-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage Transport Plugin 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, require, 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 if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | var counter = 0, 30 | names = [ 31 | 'accepts', 32 | 'cache', 33 | 'contents', 34 | 'contentType', 35 | 'crossDomain', 36 | 'data', 37 | 'dataType', 38 | 'headers', 39 | 'ifModified', 40 | 'mimeType', 41 | 'password', 42 | 'processData', 43 | 'timeout', 44 | 'traditional', 45 | 'type', 46 | 'url', 47 | 'username' 48 | ], 49 | convert = function (p) { 50 | return p; 51 | }; 52 | 53 | $.ajaxSetup({ 54 | converters: { 55 | 'postmessage text': convert, 56 | 'postmessage json': convert, 57 | 'postmessage html': convert 58 | } 59 | }); 60 | 61 | $.ajaxTransport('postmessage', function (options) { 62 | if (options.postMessage && window.postMessage) { 63 | var iframe, 64 | loc = $('').prop('href', options.postMessage)[0], 65 | target = loc.protocol + '//' + loc.host, 66 | xhrUpload = options.xhr().upload; 67 | // IE always includes the port for the host property of a link 68 | // element, but not in the location.host or origin property for the 69 | // default http port 80 and https port 443, so we strip it: 70 | if (/^(http:\/\/.+:80)|(https:\/\/.+:443)$/.test(target)) { 71 | target = target.replace(/:(80|443)$/, ''); 72 | } 73 | return { 74 | send: function (_, completeCallback) { 75 | counter += 1; 76 | var message = { 77 | id: 'postmessage-transport-' + counter 78 | }, 79 | eventName = 'message.' + message.id; 80 | iframe = $( 81 | '' 84 | ).bind('load', function () { 85 | $.each(names, function (i, name) { 86 | message[name] = options[name]; 87 | }); 88 | message.dataType = message.dataType.replace('postmessage ', ''); 89 | $(window).bind(eventName, function (e) { 90 | e = e.originalEvent; 91 | var data = e.data, 92 | ev; 93 | if (e.origin === target && data.id === message.id) { 94 | if (data.type === 'progress') { 95 | ev = document.createEvent('Event'); 96 | ev.initEvent(data.type, false, true); 97 | $.extend(ev, data); 98 | xhrUpload.dispatchEvent(ev); 99 | } else { 100 | completeCallback( 101 | data.status, 102 | data.statusText, 103 | {postmessage: data.result}, 104 | data.headers 105 | ); 106 | iframe.remove(); 107 | $(window).unbind(eventName); 108 | } 109 | } 110 | }); 111 | iframe[0].contentWindow.postMessage( 112 | message, 113 | target 114 | ); 115 | }).appendTo(document.body); 116 | }, 117 | abort: function () { 118 | if (iframe) { 119 | iframe.remove(); 120 | } 121 | } 122 | }; 123 | } 124 | }); 125 | 126 | })); 127 | -------------------------------------------------------------------------------- /.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" : 4, // {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" : true, // 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 | -------------------------------------------------------------------------------- /js/jquery.fileupload-jquery-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload jQuery UI Plugin 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, require, 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-ui' 22 | ], factory); 23 | } else if (typeof exports === 'object') { 24 | // Node/CommonJS: 25 | factory( 26 | require('jquery'), 27 | require('./jquery.fileupload-ui') 28 | ); 29 | } else { 30 | // Browser globals: 31 | factory(window.jQuery); 32 | } 33 | }(function ($) { 34 | 'use strict'; 35 | 36 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 37 | 38 | options: { 39 | processdone: function (e, data) { 40 | data.context.find('.start').button('enable'); 41 | }, 42 | progress: function (e, data) { 43 | if (data.context) { 44 | data.context.find('.progress').progressbar( 45 | 'option', 46 | 'value', 47 | parseInt(data.loaded / data.total * 100, 10) 48 | ); 49 | } 50 | }, 51 | progressall: function (e, data) { 52 | var $this = $(this); 53 | $this.find('.fileupload-progress') 54 | .find('.progress').progressbar( 55 | 'option', 56 | 'value', 57 | parseInt(data.loaded / data.total * 100, 10) 58 | ).end() 59 | .find('.progress-extended').each(function () { 60 | $(this).html( 61 | ($this.data('blueimp-fileupload') || 62 | $this.data('fileupload')) 63 | ._renderExtendedProgress(data) 64 | ); 65 | }); 66 | } 67 | }, 68 | 69 | _renderUpload: function (func, files) { 70 | var node = this._super(func, files), 71 | showIconText = $(window).width() > 480; 72 | node.find('.progress').empty().progressbar(); 73 | node.find('.start').button({ 74 | icons: {primary: 'ui-icon-circle-arrow-e'}, 75 | text: showIconText 76 | }); 77 | node.find('.cancel').button({ 78 | icons: {primary: 'ui-icon-cancel'}, 79 | text: showIconText 80 | }); 81 | if (node.hasClass('fade')) { 82 | node.hide(); 83 | } 84 | return node; 85 | }, 86 | 87 | _renderDownload: function (func, files) { 88 | var node = this._super(func, files), 89 | showIconText = $(window).width() > 480; 90 | node.find('.delete').button({ 91 | icons: {primary: 'ui-icon-trash'}, 92 | text: showIconText 93 | }); 94 | if (node.hasClass('fade')) { 95 | node.hide(); 96 | } 97 | return node; 98 | }, 99 | 100 | _startHandler: function (e) { 101 | $(e.currentTarget).button('disable'); 102 | this._super(e); 103 | }, 104 | 105 | _transition: function (node) { 106 | var deferred = $.Deferred(); 107 | if (node.hasClass('fade')) { 108 | node.fadeToggle( 109 | this.options.transitionDuration, 110 | this.options.transitionEasing, 111 | function () { 112 | deferred.resolveWith(node); 113 | } 114 | ); 115 | } else { 116 | deferred.resolveWith(node); 117 | } 118 | return deferred; 119 | }, 120 | 121 | _create: function () { 122 | this._super(); 123 | this.element 124 | .find('.fileupload-buttonbar') 125 | .find('.fileinput-button').each(function () { 126 | var input = $(this).find('input:file').detach(); 127 | $(this) 128 | .button({icons: {primary: 'ui-icon-plusthick'}}) 129 | .append(input); 130 | }) 131 | .end().find('.start') 132 | .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) 133 | .end().find('.cancel') 134 | .button({icons: {primary: 'ui-icon-cancel'}}) 135 | .end().find('.delete') 136 | .button({icons: {primary: 'ui-icon-trash'}}) 137 | .end().find('.progress').progressbar(); 138 | }, 139 | 140 | _destroy: function () { 141 | this.element 142 | .find('.fileupload-buttonbar') 143 | .find('.fileinput-button').each(function () { 144 | var input = $(this).find('input:file').detach(); 145 | $(this) 146 | .button('destroy') 147 | .append(input); 148 | }) 149 | .end().find('.start') 150 | .button('destroy') 151 | .end().find('.cancel') 152 | .button('destroy') 153 | .end().find('.delete') 154 | .button('destroy') 155 | .end().find('.progress').progressbar('destroy'); 156 | this._super(); 157 | } 158 | 159 | }); 160 | 161 | })); 162 | -------------------------------------------------------------------------------- /js/jquery.fileupload-process.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Processing Plugin 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, require, 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 if (typeof exports === 'object') { 24 | // Node/CommonJS: 25 | factory( 26 | require('jquery'), 27 | require('./jquery.fileupload') 28 | ); 29 | } else { 30 | // Browser globals: 31 | factory( 32 | window.jQuery 33 | ); 34 | } 35 | }(function ($) { 36 | 'use strict'; 37 | 38 | var originalAdd = $.blueimp.fileupload.prototype.options.add; 39 | 40 | // The File Upload Processing plugin extends the fileupload widget 41 | // with file processing functionality: 42 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 43 | 44 | options: { 45 | // The list of processing actions: 46 | processQueue: [ 47 | /* 48 | { 49 | action: 'log', 50 | type: 'debug' 51 | } 52 | */ 53 | ], 54 | add: function (e, data) { 55 | var $this = $(this); 56 | data.process(function () { 57 | return $this.fileupload('process', data); 58 | }); 59 | originalAdd.call(this, e, data); 60 | } 61 | }, 62 | 63 | processActions: { 64 | /* 65 | log: function (data, options) { 66 | console[options.type]( 67 | 'Processing "' + data.files[data.index].name + '"' 68 | ); 69 | } 70 | */ 71 | }, 72 | 73 | _processFile: function (data, originalData) { 74 | var that = this, 75 | dfd = $.Deferred().resolveWith(that, [data]), 76 | chain = dfd.promise(); 77 | this._trigger('process', null, data); 78 | $.each(data.processQueue, function (i, settings) { 79 | var func = function (data) { 80 | if (originalData.errorThrown) { 81 | return $.Deferred() 82 | .rejectWith(that, [originalData]).promise(); 83 | } 84 | return that.processActions[settings.action].call( 85 | that, 86 | data, 87 | settings 88 | ); 89 | }; 90 | chain = chain.then(func, settings.always && func); 91 | }); 92 | chain 93 | .done(function () { 94 | that._trigger('processdone', null, data); 95 | that._trigger('processalways', null, data); 96 | }) 97 | .fail(function () { 98 | that._trigger('processfail', null, data); 99 | that._trigger('processalways', null, data); 100 | }); 101 | return chain; 102 | }, 103 | 104 | // Replaces the settings of each processQueue item that 105 | // are strings starting with an "@", using the remaining 106 | // substring as key for the option map, 107 | // e.g. "@autoUpload" is replaced with options.autoUpload: 108 | _transformProcessQueue: function (options) { 109 | var processQueue = []; 110 | $.each(options.processQueue, function () { 111 | var settings = {}, 112 | action = this.action, 113 | prefix = this.prefix === true ? action : this.prefix; 114 | $.each(this, function (key, value) { 115 | if ($.type(value) === 'string' && 116 | value.charAt(0) === '@') { 117 | settings[key] = options[ 118 | value.slice(1) || (prefix ? prefix + 119 | key.charAt(0).toUpperCase() + key.slice(1) : key) 120 | ]; 121 | } else { 122 | settings[key] = value; 123 | } 124 | 125 | }); 126 | processQueue.push(settings); 127 | }); 128 | options.processQueue = processQueue; 129 | }, 130 | 131 | // Returns the number of files currently in the processsing queue: 132 | processing: function () { 133 | return this._processing; 134 | }, 135 | 136 | // Processes the files given as files property of the data parameter, 137 | // returns a Promise object that allows to bind callbacks: 138 | process: function (data) { 139 | var that = this, 140 | options = $.extend({}, this.options, data); 141 | if (options.processQueue && options.processQueue.length) { 142 | this._transformProcessQueue(options); 143 | if (this._processing === 0) { 144 | this._trigger('processstart'); 145 | } 146 | $.each(data.files, function (index) { 147 | var opts = index ? $.extend({}, options) : options, 148 | func = function () { 149 | if (data.errorThrown) { 150 | return $.Deferred() 151 | .rejectWith(that, [data]).promise(); 152 | } 153 | return that._processFile(opts, data); 154 | }; 155 | opts.index = index; 156 | that._processing += 1; 157 | that._processingQueue = that._processingQueue.then(func, func) 158 | .always(function () { 159 | that._processing -= 1; 160 | if (that._processing === 0) { 161 | that._trigger('processstop'); 162 | } 163 | }); 164 | }); 165 | } 166 | return this._processingQueue; 167 | }, 168 | 169 | _create: function () { 170 | this._super(); 171 | this._processing = 0; 172 | this._processingQueue = $.Deferred().resolveWith(this) 173 | .promise(); 174 | } 175 | 176 | }); 177 | 178 | })); 179 | -------------------------------------------------------------------------------- /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 999 KB (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 or less (demo files are stored in memory).
  • 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 the 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 | Add files... 41 | 42 | 43 | 47 | 51 | 55 | 56 | 57 | 58 |
    59 | 60 |
    61 | 62 |
    63 |
    64 |
    65 | 66 |
     
    67 |
    68 |
    69 | 70 | 71 |
    72 |
    73 | 74 | 105 | 106 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /server/gae-python/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # jQuery File Upload Plugin GAE Python Example 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 google.appengine.api import memcache, images 14 | import json 15 | import os 16 | import re 17 | import urllib 18 | import webapp2 19 | 20 | DEBUG=os.environ.get('SERVER_SOFTWARE', '').startswith('Dev') 21 | WEBSITE = 'https://blueimp.github.io/jQuery-File-Upload/' 22 | MIN_FILE_SIZE = 1 # bytes 23 | # Max file size is memcache limit (1MB) minus key size minus overhead: 24 | MAX_FILE_SIZE = 999000 # bytes 25 | IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)') 26 | ACCEPT_FILE_TYPES = IMAGE_TYPES 27 | THUMB_MAX_WIDTH = 80 28 | THUMB_MAX_HEIGHT = 80 29 | THUMB_SUFFIX = '.'+str(THUMB_MAX_WIDTH)+'x'+str(THUMB_MAX_HEIGHT)+'.png' 30 | EXPIRATION_TIME = 300 # seconds 31 | # If set to None, only allow redirects to the referer protocol+host. 32 | # Set to a regexp for custom pattern matching against the redirect value: 33 | REDIRECT_ALLOW_TARGET = None 34 | 35 | class CORSHandler(webapp2.RequestHandler): 36 | def cors(self): 37 | headers = self.response.headers 38 | headers['Access-Control-Allow-Origin'] = '*' 39 | headers['Access-Control-Allow-Methods'] =\ 40 | 'OPTIONS, HEAD, GET, POST, DELETE' 41 | headers['Access-Control-Allow-Headers'] =\ 42 | 'Content-Type, Content-Range, Content-Disposition' 43 | 44 | def initialize(self, request, response): 45 | super(CORSHandler, self).initialize(request, response) 46 | self.cors() 47 | 48 | def json_stringify(self, obj): 49 | return json.dumps(obj, separators=(',', ':')) 50 | 51 | def options(self, *args, **kwargs): 52 | pass 53 | 54 | class UploadHandler(CORSHandler): 55 | def validate(self, file): 56 | if file['size'] < MIN_FILE_SIZE: 57 | file['error'] = 'File is too small' 58 | elif file['size'] > MAX_FILE_SIZE: 59 | file['error'] = 'File is too big' 60 | elif not ACCEPT_FILE_TYPES.match(file['type']): 61 | file['error'] = 'Filetype not allowed' 62 | else: 63 | return True 64 | return False 65 | 66 | def validate_redirect(self, redirect): 67 | if redirect: 68 | if REDIRECT_ALLOW_TARGET: 69 | return REDIRECT_ALLOW_TARGET.match(redirect) 70 | referer = self.request.headers['referer'] 71 | if referer: 72 | from urlparse import urlparse 73 | parts = urlparse(referer) 74 | redirect_allow_target = '^' + re.escape( 75 | parts.scheme + '://' + parts.netloc + '/' 76 | ) 77 | return re.match(redirect_allow_target, redirect) 78 | return False 79 | 80 | def get_file_size(self, file): 81 | file.seek(0, 2) # Seek to the end of the file 82 | size = file.tell() # Get the position of EOF 83 | file.seek(0) # Reset the file position to the beginning 84 | return size 85 | 86 | def write_blob(self, data, info): 87 | key = urllib.quote(info['type'].encode('utf-8'), '') +\ 88 | '/' + str(hash(data)) +\ 89 | '/' + urllib.quote(info['name'].encode('utf-8'), '') 90 | try: 91 | memcache.set(key, data, time=EXPIRATION_TIME) 92 | except: #Failed to add to memcache 93 | return (None, None) 94 | thumbnail_key = None 95 | if IMAGE_TYPES.match(info['type']): 96 | try: 97 | img = images.Image(image_data=data) 98 | img.resize( 99 | width=THUMB_MAX_WIDTH, 100 | height=THUMB_MAX_HEIGHT 101 | ) 102 | thumbnail_data = img.execute_transforms() 103 | thumbnail_key = key + THUMB_SUFFIX 104 | memcache.set( 105 | thumbnail_key, 106 | thumbnail_data, 107 | time=EXPIRATION_TIME 108 | ) 109 | except: #Failed to resize Image or add to memcache 110 | thumbnail_key = None 111 | return (key, thumbnail_key) 112 | 113 | def handle_upload(self): 114 | results = [] 115 | for name, fieldStorage in self.request.POST.items(): 116 | if type(fieldStorage) is unicode: 117 | continue 118 | result = {} 119 | result['name'] = urllib.unquote(fieldStorage.filename) 120 | result['type'] = fieldStorage.type 121 | result['size'] = self.get_file_size(fieldStorage.file) 122 | if self.validate(result): 123 | key, thumbnail_key = self.write_blob( 124 | fieldStorage.value, 125 | result 126 | ) 127 | if key is not None: 128 | result['url'] = self.request.host_url + '/' + key 129 | result['deleteUrl'] = result['url'] 130 | result['deleteType'] = 'DELETE' 131 | if thumbnail_key is not None: 132 | result['thumbnailUrl'] = self.request.host_url +\ 133 | '/' + thumbnail_key 134 | else: 135 | result['error'] = 'Failed to store uploaded file.' 136 | results.append(result) 137 | return results 138 | 139 | def head(self): 140 | pass 141 | 142 | def get(self): 143 | self.redirect(WEBSITE) 144 | 145 | def post(self): 146 | if (self.request.get('_method') == 'DELETE'): 147 | return self.delete() 148 | result = {'files': self.handle_upload()} 149 | s = self.json_stringify(result) 150 | redirect = self.request.get('redirect') 151 | if self.validate_redirect(redirect): 152 | return self.redirect(str( 153 | redirect.replace('%s', urllib.quote(s, ''), 1) 154 | )) 155 | if 'application/json' in self.request.headers.get('Accept'): 156 | self.response.headers['Content-Type'] = 'application/json' 157 | self.response.write(s) 158 | 159 | class FileHandler(CORSHandler): 160 | def normalize(self, str): 161 | return urllib.quote(urllib.unquote(str), '') 162 | 163 | def get(self, content_type, data_hash, file_name): 164 | content_type = self.normalize(content_type) 165 | file_name = self.normalize(file_name) 166 | key = content_type + '/' + data_hash + '/' + file_name 167 | data = memcache.get(key) 168 | if data is None: 169 | return self.error(404) 170 | # Prevent browsers from MIME-sniffing the content-type: 171 | self.response.headers['X-Content-Type-Options'] = 'nosniff' 172 | content_type = urllib.unquote(content_type) 173 | if not IMAGE_TYPES.match(content_type): 174 | # Force a download dialog for non-image types: 175 | content_type = 'application/octet-stream' 176 | elif file_name.endswith(THUMB_SUFFIX): 177 | content_type = 'image/png' 178 | self.response.headers['Content-Type'] = content_type 179 | # Cache for the expiration time: 180 | self.response.headers['Cache-Control'] = 'public,max-age=%d' \ 181 | % EXPIRATION_TIME 182 | self.response.write(data) 183 | 184 | def delete(self, content_type, data_hash, file_name): 185 | content_type = self.normalize(content_type) 186 | file_name = self.normalize(file_name) 187 | key = content_type + '/' + data_hash + '/' + file_name 188 | result = {key: memcache.delete(key)} 189 | content_type = urllib.unquote(content_type) 190 | if IMAGE_TYPES.match(content_type): 191 | thumbnail_key = key + THUMB_SUFFIX 192 | result[thumbnail_key] = memcache.delete(thumbnail_key) 193 | if 'application/json' in self.request.headers.get('Accept'): 194 | self.response.headers['Content-Type'] = 'application/json' 195 | s = self.json_stringify(result) 196 | self.response.write(s) 197 | 198 | app = webapp2.WSGIApplication( 199 | [ 200 | ('/', UploadHandler), 201 | ('/(.+)/([^/]+)/([^/]+)', FileHandler) 202 | ], 203 | debug=DEBUG 204 | ) 205 | -------------------------------------------------------------------------------- /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 | 59 |
    60 |

    Cosmic JS jQuery File Upload Demo

    61 |

    Basic Plus UI version

    62 |
    63 |

    This example shows you how to upload files to the Cosmic JS API. Sign up for Cosmic JS to upload files to your own Bucket.

    64 |
    65 |
    66 | 67 |
    68 | 69 | 70 | 71 |
    72 |
    73 | 74 | 75 | 76 | Add file... 77 | 78 | 79 | 83 | 87 | 91 | 92 | 93 | 94 |
    95 | 96 |
    97 | 98 |
    99 |
    100 |
    101 | 102 |
     
    103 |
    104 |
    105 | 106 | 107 |
    108 |
    109 |
    110 | 111 | 120 | 121 | 152 | 153 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /server/gae-go/app/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin GAE Go Example 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 | "bufio" 16 | "bytes" 17 | "encoding/json" 18 | "fmt" 19 | "github.com/disintegration/gift" 20 | "golang.org/x/net/context" 21 | "google.golang.org/appengine" 22 | "google.golang.org/appengine/memcache" 23 | "hash/crc32" 24 | "image" 25 | "image/gif" 26 | "image/jpeg" 27 | "image/png" 28 | "io" 29 | "log" 30 | "mime/multipart" 31 | "net/http" 32 | "net/url" 33 | "path/filepath" 34 | "regexp" 35 | "strings" 36 | ) 37 | 38 | const ( 39 | WEBSITE = "https://blueimp.github.io/jQuery-File-Upload/" 40 | MIN_FILE_SIZE = 1 // bytes 41 | // Max file size is memcache limit (1MB) minus key size minus overhead: 42 | MAX_FILE_SIZE = 999000 // bytes 43 | IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)" 44 | ACCEPT_FILE_TYPES = IMAGE_TYPES 45 | THUMB_MAX_WIDTH = 80 46 | THUMB_MAX_HEIGHT = 80 47 | EXPIRATION_TIME = 300 // seconds 48 | // If empty, only allow redirects to the referer protocol+host. 49 | // Set to a regexp string for custom pattern matching: 50 | REDIRECT_ALLOW_TARGET = "" 51 | ) 52 | 53 | var ( 54 | imageTypes = regexp.MustCompile(IMAGE_TYPES) 55 | acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES) 56 | thumbSuffix = "." + fmt.Sprint(THUMB_MAX_WIDTH) + "x" + 57 | fmt.Sprint(THUMB_MAX_HEIGHT) 58 | ) 59 | 60 | func escape(s string) string { 61 | return strings.Replace(url.QueryEscape(s), "+", "%20", -1) 62 | } 63 | 64 | func extractKey(r *http.Request) string { 65 | // Use RequestURI instead of r.URL.Path, as we need the encoded form: 66 | path := strings.Split(r.RequestURI, "?")[0] 67 | // Also adjust double encoded slashes: 68 | return strings.Replace(path[1:], "%252F", "%2F", -1) 69 | } 70 | 71 | func check(err error) { 72 | if err != nil { 73 | panic(err) 74 | } 75 | } 76 | 77 | type FileInfo struct { 78 | Key string `json:"-"` 79 | ThumbnailKey string `json:"-"` 80 | Url string `json:"url,omitempty"` 81 | ThumbnailUrl string `json:"thumbnailUrl,omitempty"` 82 | Name string `json:"name"` 83 | Type string `json:"type"` 84 | Size int64 `json:"size"` 85 | Error string `json:"error,omitempty"` 86 | DeleteUrl string `json:"deleteUrl,omitempty"` 87 | DeleteType string `json:"deleteType,omitempty"` 88 | } 89 | 90 | func (fi *FileInfo) ValidateType() (valid bool) { 91 | if acceptFileTypes.MatchString(fi.Type) { 92 | return true 93 | } 94 | fi.Error = "Filetype not allowed" 95 | return false 96 | } 97 | 98 | func (fi *FileInfo) ValidateSize() (valid bool) { 99 | if fi.Size < MIN_FILE_SIZE { 100 | fi.Error = "File is too small" 101 | } else if fi.Size > MAX_FILE_SIZE { 102 | fi.Error = "File is too big" 103 | } else { 104 | return true 105 | } 106 | return false 107 | } 108 | 109 | func (fi *FileInfo) CreateUrls(r *http.Request, c context.Context) { 110 | u := &url.URL{ 111 | Scheme: r.URL.Scheme, 112 | Host: appengine.DefaultVersionHostname(c), 113 | Path: "/", 114 | } 115 | uString := u.String() 116 | fi.Url = uString + fi.Key 117 | fi.DeleteUrl = fi.Url 118 | fi.DeleteType = "DELETE" 119 | if fi.ThumbnailKey != "" { 120 | fi.ThumbnailUrl = uString + fi.ThumbnailKey 121 | } 122 | } 123 | 124 | func (fi *FileInfo) SetKey(checksum uint32) { 125 | fi.Key = escape(string(fi.Type)) + "/" + 126 | escape(fmt.Sprint(checksum)) + "/" + 127 | escape(string(fi.Name)) 128 | } 129 | 130 | func (fi *FileInfo) createThumb(buffer *bytes.Buffer, c context.Context) { 131 | if imageTypes.MatchString(fi.Type) { 132 | src, _, err := image.Decode(bytes.NewReader(buffer.Bytes())) 133 | check(err) 134 | filter := gift.New(gift.ResizeToFit( 135 | THUMB_MAX_WIDTH, 136 | THUMB_MAX_HEIGHT, 137 | gift.LanczosResampling, 138 | )) 139 | dst := image.NewNRGBA(filter.Bounds(src.Bounds())) 140 | filter.Draw(dst, src) 141 | buffer.Reset() 142 | bWriter := bufio.NewWriter(buffer) 143 | switch fi.Type { 144 | case "image/jpeg", "image/pjpeg": 145 | err = jpeg.Encode(bWriter, dst, nil) 146 | case "image/gif": 147 | err = gif.Encode(bWriter, dst, nil) 148 | default: 149 | err = png.Encode(bWriter, dst) 150 | } 151 | check(err) 152 | bWriter.Flush() 153 | thumbnailKey := fi.Key + thumbSuffix + filepath.Ext(fi.Name) 154 | item := &memcache.Item{ 155 | Key: thumbnailKey, 156 | Value: buffer.Bytes(), 157 | } 158 | err = memcache.Set(c, item) 159 | check(err) 160 | fi.ThumbnailKey = thumbnailKey 161 | } 162 | } 163 | 164 | func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) { 165 | fi = &FileInfo{ 166 | Name: p.FileName(), 167 | Type: p.Header.Get("Content-Type"), 168 | } 169 | if !fi.ValidateType() { 170 | return 171 | } 172 | defer func() { 173 | if rec := recover(); rec != nil { 174 | log.Println(rec) 175 | fi.Error = rec.(error).Error() 176 | } 177 | }() 178 | var buffer bytes.Buffer 179 | hash := crc32.NewIEEE() 180 | mw := io.MultiWriter(&buffer, hash) 181 | lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1} 182 | _, err := io.Copy(mw, lr) 183 | check(err) 184 | fi.Size = MAX_FILE_SIZE + 1 - lr.N 185 | if !fi.ValidateSize() { 186 | return 187 | } 188 | fi.SetKey(hash.Sum32()) 189 | item := &memcache.Item{ 190 | Key: fi.Key, 191 | Value: buffer.Bytes(), 192 | } 193 | context := appengine.NewContext(r) 194 | err = memcache.Set(context, item) 195 | check(err) 196 | fi.createThumb(&buffer, context) 197 | fi.CreateUrls(r, context) 198 | return 199 | } 200 | 201 | func getFormValue(p *multipart.Part) string { 202 | var b bytes.Buffer 203 | io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB 204 | return b.String() 205 | } 206 | 207 | func handleUploads(r *http.Request) (fileInfos []*FileInfo) { 208 | fileInfos = make([]*FileInfo, 0) 209 | mr, err := r.MultipartReader() 210 | check(err) 211 | r.Form, err = url.ParseQuery(r.URL.RawQuery) 212 | check(err) 213 | part, err := mr.NextPart() 214 | for err == nil { 215 | if name := part.FormName(); name != "" { 216 | if part.FileName() != "" { 217 | fileInfos = append(fileInfos, handleUpload(r, part)) 218 | } else { 219 | r.Form[name] = append(r.Form[name], getFormValue(part)) 220 | } 221 | } 222 | part, err = mr.NextPart() 223 | } 224 | return 225 | } 226 | 227 | func validateRedirect(r *http.Request, redirect string) bool { 228 | if redirect != "" { 229 | var redirectAllowTarget *regexp.Regexp 230 | if REDIRECT_ALLOW_TARGET != "" { 231 | redirectAllowTarget = regexp.MustCompile(REDIRECT_ALLOW_TARGET) 232 | } else { 233 | referer := r.Referer() 234 | if referer == "" { 235 | return false 236 | } 237 | refererUrl, err := url.Parse(referer) 238 | if err != nil { 239 | return false 240 | } 241 | redirectAllowTarget = regexp.MustCompile("^" + regexp.QuoteMeta( 242 | refererUrl.Scheme+"://"+refererUrl.Host+"/", 243 | )) 244 | } 245 | return redirectAllowTarget.MatchString(redirect) 246 | } 247 | return false 248 | } 249 | 250 | func get(w http.ResponseWriter, r *http.Request) { 251 | if r.URL.Path == "/" { 252 | http.Redirect(w, r, WEBSITE, http.StatusFound) 253 | return 254 | } 255 | // Use RequestURI instead of r.URL.Path, as we need the encoded form: 256 | key := extractKey(r) 257 | parts := strings.Split(key, "/") 258 | if len(parts) == 3 { 259 | context := appengine.NewContext(r) 260 | item, err := memcache.Get(context, key) 261 | if err == nil { 262 | w.Header().Add("X-Content-Type-Options", "nosniff") 263 | contentType, _ := url.QueryUnescape(parts[0]) 264 | if !imageTypes.MatchString(contentType) { 265 | contentType = "application/octet-stream" 266 | } 267 | w.Header().Add("Content-Type", contentType) 268 | w.Header().Add( 269 | "Cache-Control", 270 | fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME), 271 | ) 272 | w.Write(item.Value) 273 | return 274 | } 275 | } 276 | http.Error(w, "404 Not Found", http.StatusNotFound) 277 | } 278 | 279 | func post(w http.ResponseWriter, r *http.Request) { 280 | result := make(map[string][]*FileInfo, 1) 281 | result["files"] = handleUploads(r) 282 | b, err := json.Marshal(result) 283 | check(err) 284 | if redirect := r.FormValue("redirect"); validateRedirect(r, redirect) { 285 | if strings.Contains(redirect, "%s") { 286 | redirect = fmt.Sprintf( 287 | redirect, 288 | escape(string(b)), 289 | ) 290 | } 291 | http.Redirect(w, r, redirect, http.StatusFound) 292 | return 293 | } 294 | w.Header().Set("Cache-Control", "no-cache") 295 | jsonType := "application/json" 296 | if strings.Index(r.Header.Get("Accept"), jsonType) != -1 { 297 | w.Header().Set("Content-Type", jsonType) 298 | } 299 | fmt.Fprintln(w, string(b)) 300 | } 301 | 302 | func delete(w http.ResponseWriter, r *http.Request) { 303 | key := extractKey(r) 304 | parts := strings.Split(key, "/") 305 | if len(parts) == 3 { 306 | result := make(map[string]bool, 1) 307 | context := appengine.NewContext(r) 308 | err := memcache.Delete(context, key) 309 | if err == nil { 310 | result[key] = true 311 | contentType, _ := url.QueryUnescape(parts[0]) 312 | if imageTypes.MatchString(contentType) { 313 | thumbnailKey := key + thumbSuffix + filepath.Ext(parts[2]) 314 | err := memcache.Delete(context, thumbnailKey) 315 | if err == nil { 316 | result[thumbnailKey] = true 317 | } 318 | } 319 | } 320 | w.Header().Set("Content-Type", "application/json") 321 | b, err := json.Marshal(result) 322 | check(err) 323 | fmt.Fprintln(w, string(b)) 324 | } else { 325 | http.Error(w, "405 Method not allowed", http.StatusMethodNotAllowed) 326 | } 327 | } 328 | 329 | func handle(w http.ResponseWriter, r *http.Request) { 330 | params, err := url.ParseQuery(r.URL.RawQuery) 331 | check(err) 332 | w.Header().Add("Access-Control-Allow-Origin", "*") 333 | w.Header().Add( 334 | "Access-Control-Allow-Methods", 335 | "OPTIONS, HEAD, GET, POST, DELETE", 336 | ) 337 | w.Header().Add( 338 | "Access-Control-Allow-Headers", 339 | "Content-Type, Content-Range, Content-Disposition", 340 | ) 341 | switch r.Method { 342 | case "OPTIONS", "HEAD": 343 | return 344 | case "GET": 345 | get(w, r) 346 | case "POST": 347 | if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" { 348 | delete(w, r) 349 | } else { 350 | post(w, r) 351 | } 352 | case "DELETE": 353 | delete(w, r) 354 | default: 355 | http.Error(w, "501 Not Implemented", http.StatusNotImplemented) 356 | } 357 | } 358 | 359 | func init() { 360 | http.HandleFunc("/", handle) 361 | } 362 | -------------------------------------------------------------------------------- /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 999 KB (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 or less (demo files are stored in memory).
    • 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 the 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 | -------------------------------------------------------------------------------- /js/jquery.iframe-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Iframe Transport Plugin 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, require, 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 if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | // Helper variable to create unique names for the transport iframes: 30 | var counter = 0; 31 | 32 | // The iframe transport accepts four additional options: 33 | // options.fileInput: a jQuery collection of file input fields 34 | // options.paramName: the parameter name for the file form data, 35 | // overrides the name property of the file input field(s), 36 | // can be a string or an array of strings. 37 | // options.formData: an array of objects with name and value properties, 38 | // equivalent to the return data of .serializeArray(), e.g.: 39 | // [{name: 'a', value: 1}, {name: 'b', value: 2}] 40 | // options.initialIframeSrc: the URL of the initial iframe src, 41 | // by default set to "javascript:false;" 42 | $.ajaxTransport('iframe', function (options) { 43 | if (options.async) { 44 | // javascript:false as initial iframe src 45 | // prevents warning popups on HTTPS in IE6: 46 | /*jshint scripturl: true */ 47 | var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', 48 | /*jshint scripturl: false */ 49 | form, 50 | iframe, 51 | addParamChar; 52 | return { 53 | send: function (_, completeCallback) { 54 | form = $('
    '); 55 | form.attr('accept-charset', options.formAcceptCharset); 56 | addParamChar = /\?/.test(options.url) ? '&' : '?'; 57 | // XDomainRequest only supports GET and POST: 58 | if (options.type === 'DELETE') { 59 | options.url = options.url + addParamChar + '_method=DELETE'; 60 | options.type = 'POST'; 61 | } else if (options.type === 'PUT') { 62 | options.url = options.url + addParamChar + '_method=PUT'; 63 | options.type = 'POST'; 64 | } else if (options.type === 'PATCH') { 65 | options.url = options.url + addParamChar + '_method=PATCH'; 66 | options.type = 'POST'; 67 | } 68 | // IE versions below IE8 cannot set the name property of 69 | // elements that have already been added to the DOM, 70 | // so we set the name along with the iframe HTML markup: 71 | counter += 1; 72 | iframe = $( 73 | '' 75 | ).bind('load', function () { 76 | var fileInputClones, 77 | paramNames = $.isArray(options.paramName) ? 78 | options.paramName : [options.paramName]; 79 | iframe 80 | .unbind('load') 81 | .bind('load', function () { 82 | var response; 83 | // Wrap in a try/catch block to catch exceptions thrown 84 | // when trying to access cross-domain iframe contents: 85 | try { 86 | response = iframe.contents(); 87 | // Google Chrome and Firefox do not throw an 88 | // exception when calling iframe.contents() on 89 | // cross-domain requests, so we unify the response: 90 | if (!response.length || !response[0].firstChild) { 91 | throw new Error(); 92 | } 93 | } catch (e) { 94 | response = undefined; 95 | } 96 | // The complete callback returns the 97 | // iframe content document as response object: 98 | completeCallback( 99 | 200, 100 | 'success', 101 | {'iframe': response} 102 | ); 103 | // Fix for IE endless progress bar activity bug 104 | // (happens on form submits to iframe targets): 105 | $('') 106 | .appendTo(form); 107 | window.setTimeout(function () { 108 | // Removing the form in a setTimeout call 109 | // allows Chrome's developer tools to display 110 | // the response result 111 | form.remove(); 112 | }, 0); 113 | }); 114 | form 115 | .prop('target', iframe.prop('name')) 116 | .prop('action', options.url) 117 | .prop('method', options.type); 118 | if (options.formData) { 119 | $.each(options.formData, function (index, field) { 120 | $('') 121 | .prop('name', field.name) 122 | .val(field.value) 123 | .appendTo(form); 124 | }); 125 | } 126 | if (options.fileInput && options.fileInput.length && 127 | options.type === 'POST') { 128 | fileInputClones = options.fileInput.clone(); 129 | // Insert a clone for each file input field: 130 | options.fileInput.after(function (index) { 131 | return fileInputClones[index]; 132 | }); 133 | if (options.paramName) { 134 | options.fileInput.each(function (index) { 135 | $(this).prop( 136 | 'name', 137 | paramNames[index] || options.paramName 138 | ); 139 | }); 140 | } 141 | // Appending the file input fields to the hidden form 142 | // removes them from their original location: 143 | form 144 | .append(options.fileInput) 145 | .prop('enctype', 'multipart/form-data') 146 | // enctype must be set as encoding for IE: 147 | .prop('encoding', 'multipart/form-data'); 148 | // Remove the HTML5 form attribute from the input(s): 149 | options.fileInput.removeAttr('form'); 150 | } 151 | form.submit(); 152 | // Insert the file input fields at their original location 153 | // by replacing the clones with the originals: 154 | if (fileInputClones && fileInputClones.length) { 155 | options.fileInput.each(function (index, input) { 156 | var clone = $(fileInputClones[index]); 157 | // Restore the original name and form properties: 158 | $(input) 159 | .prop('name', clone.prop('name')) 160 | .attr('form', clone.attr('form')); 161 | clone.replaceWith(input); 162 | }); 163 | } 164 | }); 165 | form.append(iframe).appendTo(document.body); 166 | }, 167 | abort: function () { 168 | if (iframe) { 169 | // javascript:false as iframe src aborts the request 170 | // and prevents warning popups on HTTPS in IE6. 171 | // concat is used to avoid the "Script URL" JSLint error: 172 | iframe 173 | .unbind('load') 174 | .prop('src', initialIframeSrc); 175 | } 176 | if (form) { 177 | form.remove(); 178 | } 179 | } 180 | }; 181 | } 182 | }); 183 | 184 | // The iframe transport returns the iframe content document as response. 185 | // The following adds converters from iframe to text, json, html, xml 186 | // and script. 187 | // Please note that the Content-Type for JSON responses has to be text/plain 188 | // or text/html, if the browser doesn't include application/json in the 189 | // Accept header, else IE will show a download dialog. 190 | // The Content-Type for XML responses on the other hand has to be always 191 | // application/xml or text/xml, so IE properly parses the XML response. 192 | // See also 193 | // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation 194 | $.ajaxSetup({ 195 | converters: { 196 | 'iframe text': function (iframe) { 197 | return iframe && $(iframe[0].body).text(); 198 | }, 199 | 'iframe json': function (iframe) { 200 | return iframe && $.parseJSON($(iframe[0].body).text()); 201 | }, 202 | 'iframe html': function (iframe) { 203 | return iframe && $(iframe[0].body).html(); 204 | }, 205 | 'iframe xml': function (iframe) { 206 | var xmlDoc = iframe && iframe[0]; 207 | return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : 208 | $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || 209 | $(xmlDoc.body).html()); 210 | }, 211 | 'iframe script': function (iframe) { 212 | return iframe && $.globalEval($(iframe[0].body).text()); 213 | } 214 | } 215 | }); 216 | 217 | })); 218 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 89 | 90 | 91 | Add files... 92 | 93 | 94 | 98 | 102 | 103 | 104 |
    105 | 106 |
    107 | 108 |
    109 | 110 |
     
    111 |
    112 |
    113 | 114 | 115 | 116 | 122 | 132 | 136 | 150 | 151 |
    117 |
    118 | 119 |
    120 |
    121 |
    123 |

    124 | 125 | {{file.name}} 126 | {{file.name}} 127 | 128 | {{file.name}} 129 |

    130 | {{file.error}} 131 |
    133 |

    {{file.size | formatFileSize}}

    134 |
    135 |
    137 | 141 | 145 | 149 |
    152 |
    153 |
    154 |
    155 |
    156 |

    Demo Notes

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