Repository: danialfarid/ng-file-upload Branch: master Commit: a4c4187c11bc Files: 58 Total size: 934.9 KB Directory structure: gitextract_ye9rh2i7/ ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── demo/ │ ├── C#/ │ │ ├── UploadController.js │ │ ├── UploadHandler.ashx │ │ └── UploadHandler.ashx.cs │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── df/ │ │ └── angularfileupload/ │ │ ├── CORSFilter.java │ │ ├── FileUpload.java │ │ └── S3Signature.java │ ├── resources/ │ │ ├── META-INF/ │ │ │ ├── jdoconfig.xml │ │ │ └── persistence.xml │ │ └── log4j.properties │ └── webapp/ │ ├── WEB-INF/ │ │ ├── appengine-web.xml │ │ ├── logging.properties │ │ └── web.xml │ ├── common.css │ ├── crossdomain.xml │ ├── donate.html │ ├── index.html │ └── js/ │ ├── FileAPI.flash.swf │ ├── FileAPI.js │ ├── ng-file-upload-all.js │ ├── ng-file-upload-shim.js │ ├── ng-file-upload.js │ ├── ng-img-crop.css │ ├── ng-img-crop.js │ └── upload.js ├── dist/ │ ├── FileAPI.flash.swf │ ├── FileAPI.js │ ├── ng-file-upload-all.js │ ├── ng-file-upload-shim.js │ └── ng-file-upload.js ├── index.js ├── nuget/ │ ├── Package.nuspec │ ├── build.bat │ └── nuget.sh ├── package.json ├── release.sh ├── src/ │ ├── FileAPI.flash.swf │ ├── FileAPI.js │ ├── data-url.js │ ├── drop.js │ ├── exif.js │ ├── model.js │ ├── resize.js │ ├── select.js │ ├── shim-elem.js │ ├── shim-filereader.js │ ├── shim-upload.js │ ├── upload.js │ └── validate.js └── test/ ├── .bowerrc ├── bower.json ├── index.html └── spec/ └── test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules .tmp bower_components .settings .metadata *.war RemoteSystemsTempFiles/ .DS_Store .DS_Store? ehthumbs.db Thumbs.db .Spotlight-V100 .Trashes classes/ ._* *.jar release-local.sh npm-debug.log .idea/ target/ *.iml ================================================ FILE: .jshintrc ================================================ { "node": true, "browser": true, "esnext": true, "camelcase": true, "curly": false, "eqeqeq": true, "eqnull": true, "immed": true, "indent": 4, "latedef": true, "newcap": true, "noarg": true, "quotmark": "single", "undef": true, "unused": true, "trailing": true, "smarttabs": true, "jquery": true, "evil": true, "globals": { "angular":false, "FileAPI":false, "ngFileUpload":true, "FormData":true, "Blob":true, "ActiveXObject":false, "$document":false } } ================================================ FILE: Gruntfile.js ================================================ 'use strict'; module.exports = function (grunt) { // Load grunt tasks automatically require('load-grunt-tasks')(grunt); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), concat: { all: { options: { process: function (content) { return grunt.template.process(content); } }, files: { 'dist/ng-file-upload.js': ['src/upload.js', 'src/model.js', 'src/select.js', 'src/data-url.js', 'src/validate.js', 'src/resize.js', 'src/drop.js', 'src/exif.js'], 'dist/ng-file-upload-shim.js': ['src/shim-upload.js', 'src/shim-elem.js', 'src/shim-filereader.js'], 'dist/ng-file-upload-all.js': ['dist/ng-file-upload-shim.js', 'dist/ng-file-upload.js'] } } }, uglify: { options: { preserveComments: 'some', banner: '/*! <%= pkg.version %> */\n' }, build: { files: [{ 'dist/ng-file-upload.min.js': 'dist/ng-file-upload.js', 'dist/ng-file-upload-shim.min.js': 'dist/ng-file-upload-shim.js', 'dist/ng-file-upload-all.min.js': 'dist/ng-file-upload-all.js', 'dist/FileAPI.min.js': 'dist/FileAPI.js' }] } }, copy: { build: { files: [{ expand: true, cwd: 'dist/', src: '*', dest: 'demo/src/main/webapp/js/', flatten: true, filter: 'isFile' }] }, fileapi: { files: { 'dist/FileAPI.flash.swf': 'src/FileAPI.flash.swf', 'dist/FileAPI.js': 'src/FileAPI.js' } }, bower: { files: [{ expand: true, cwd: 'dist/', src: '*', dest: '../angular-file-upload-bower/', flatten: true, filter: 'isFile' }, { expand: true, cwd: 'dist/', src: '*', dest: '../angular-file-upload-shim-bower/', flatten: true, filter: 'isFile' }] } }, serve: { options: { port: 9000 }, 'path': 'demo/src/main/webapp' }, watch: { js: { files: ['src/{,*/}*.js'], tasks: ['jshint:all', 'concat:all', 'copy:build'] } }, jshint: { options: { jshintrc: '.jshintrc', reporter: require('jshint-stylish') }, all: [ 'Gruntfile.js', 'src/{,*/}*.js', '!src/FileAPI*.*', 'test/spec/{,*/}*.js' ] }, replace: { version: { src: ['nuget/Package.nuspec', '../angular-file-upload-bower/package.js'], overwrite: true, replacements: [{ from: /"version" *: *".*"/g, to: '"version": "<%= pkg.version %>"' }, { from: /.*<\/version>/g, to: '<%= pkg.version %>' }] } }, clean: { dist: { files: [{ dot: true, src: [ 'dist', '!dist/.git*' ] }] } } }); grunt.registerTask('dev', ['jshint:all', 'concat:all', 'uglify', 'copy:build', 'watch']); grunt.registerTask('default', ['jshint:all', 'clean:dist', 'concat:all', 'copy:fileapi', 'uglify', 'copy:build', 'copy:bower', 'replace:version']); }; ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2013 danialfarid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![npm version](https://badge.fury.io/js/ng-file-upload.svg)](http://badge.fury.io/js/ng-file-upload) [![Downloads](http://img.shields.io/npm/dm/ng-file-upload.svg)](https://npmjs.org/package/ng-file-upload) [![Issue Stats](http://issuestats.com/github/danialfarid/ng-file-upload/badge/pr)](http://issuestats.com/github/danialfarid/ng-file-upload) [![Issue Stats](http://issuestats.com/github/danialfarid/ng-file-upload/badge/issue)](http://issuestats.com/github/danialfarid/ng-file-upload)
[![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=danial%2efarid%40gmail%2ecom&lc=CA&item_name=ng%2dfile%2dupload&item_number=ng%2dfile%2dupload¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) [![Gratipay donate button](https://img.shields.io/gratipay/danialfarid.svg?label=donate)](https://gratipay.com/ng-file-upload/) ng-file-upload =================== Lightweight Angular directive to upload files. **See the DEMO page.** Reference docs [here](https://github.com/danialfarid/ng-file-upload/blob/master/README.md#full-reference) **Migration notes**: [version 3.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/3.0.0) [version 3.1.x](https://github.com/danialfarid/ng-file-upload/releases/tag/3.1.0) [version 3.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/3.2.3) [version 4.x.x](https://github.com/danialfarid/ng-file-upload/releases/tag/4.0.0) [version 5.x.x](https://github.com/danialfarid/ng-file-upload/releases/tag/5.0.0) [version 6.x.x](https://github.com/danialfarid/ng-file-upload/releases/tag/6.0.0) [version 6.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/6.2.0) [version 7.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/7.0.0) [version 7.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/7.2.0) [version 8.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/8.0.1) [version 9.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/9.0.0) [version 10.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/10.0.0) [version 11.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/11.0.0) [version 12.0.x](https://github.com/danialfarid/ng-file-upload/releases/tag/12.0.0) [version 12.1.x](https://github.com/danialfarid/ng-file-upload/releases/tag/12.1.0) [version 12.2.x](https://github.com/danialfarid/ng-file-upload/releases/tag/12.2.3) Ask questions on [StackOverflow](http://stackoverflow.com/) under the [ng-file-upload](http://stackoverflow.com/tags/ng-file-upload/) tag.
For bug report or feature request please search through existing [issues](https://github.com/danialfarid/ng-file-upload/issues) first then open a new one [here](https://github.com/danialfarid/ng-file-upload/issues/new). For faster response provide steps to reproduce/versions with a jsfiddle link. If you need support for your company contact [me](mailto:danial.farid@gmail.com).
If you like this plugin give it a thumbs up at [ngmodules](http://ngmodules.org/modules/ng-file-upload) or get me a cup of tea . Contributions are welcomed. Table of Content: * [Features](#features) * [Install](#install) ([Manual](#manual), [Bower](#bower), [NuGet](#nuget), [NPM](#npm)) * [Usage](#usage) * [Old Browsers](#old_browsers) * [Server Side](#server) * [Samples](#server) ([Java](#java), [Spring](#spring), [Node.js](#node), [Rails](#rails), [PHP](#php), [.Net](#net)) * [CORS](#cors) * [Amazon S3 Upload](#s3) ## Features * file upload progress, cancel/abort * file drag and drop (html5 only) * image paste from clipboard and drag and drop from browser pages (html5 only). * image resize and center crop (native) and user controlled crop through [ngImgCrop](https://github.com/alexk111/ngImgCrop). See [crop sample](http://jsfiddle.net/danialfarid/xxo3sk41/590/) (html5 only) * orientation fix for jpeg image files with exif orientation data * resumable uploads: pause/resume upload (html5 only) * native validation support for file type/size, image width/height/aspect ratio, video/audio duration, and `ng-required` with pluggable custom sync or async validations. * show thumbnail or preview of selected images/audio/videos * supports CORS and direct upload of file's binary data using `Upload.$http()` * plenty of sample server side code, available on nuget * on demand flash [FileAPI](https://github.com/mailru/FileAPI) shim loading no extra load for html5 browsers. * HTML5 FileReader.readAsDataURL shim for IE8-9 * available on [npm](https://www.npmjs.com/package/ng-file-upload), [bower](https://libraries.io/bower/ng-file-upload), [meteor](https://atmospherejs.com/danialf/ng-file-upload), [nuget](https://www.nuget.org/packages/angular-file-upload) ## Install * **Manual**: download latest from [here](https://github.com/danialfarid/ng-file-upload-bower/releases/latest) * **Bower**: * `bower install ng-file-upload-shim --save`(for non html5 suppport) * `bower install ng-file-upload --save` * **NuGet**: `PM> Install-Package angular-file-upload` (thanks to [Georgios Diamantopoulos](https://github.com/georgiosd)) * **NPM**: `npm install ng-file-upload` ```html ``` ## Usage ### Samples: * Upload with form submit and validations: [http://jsfiddle.net/danialfarid/maqbzv15/1118/](http://jsfiddle.net/danialfarid/maqbzv15/1118/) * Upload multiple files one by one on file select: [http://jsfiddle.net/danialfarid/2vq88rfs/136/](http://jsfiddle.net/danialfarid/2vq88rfs/136/) * Upload multiple files in one request on file select (html5 only): [http://jsfiddle.net/danialfarid/huhjo9jm/5/](http://jsfiddle.net/danialfarid/huhjo9jm/5/) * Upload single file on file select: [http://jsfiddle.net/danialfarid/0mz6ff9o/135/](http://jsfiddle.net/danialfarid/0mz6ff9o/135/) * Drop and upload with $watch: [http://jsfiddle.net/danialfarid/s8kc7wg0/400/](http://jsfiddle.net/danialfarid/s8kc7wg0/400/) * Image Crop and Upload [http://jsfiddle.net/danialfarid/xxo3sk41/590/](http://jsfiddle.net/danialfarid/xxo3sk41/590/) ```html Upload on form submit or button click
Single Image with validations
Select
Multiple files
Select
Drop files:
Drop
Upload right away after file selection:
Upload on file select
Upload on file select
Drop File:
Drop Images or PDFs files here
File Drag/Drop is not supported for this browser
Image thumbnail: Audio preview: Video preview: ``` Javascript code: ```js //inject directives and services. var app = angular.module('fileUpload', ['ngFileUpload']); app.controller('MyCtrl', ['$scope', 'Upload', function ($scope, Upload) { // upload later on form submit or something similar $scope.submit = function() { if ($scope.form.file.$valid && $scope.file) { $scope.upload($scope.file); } }; // upload on file select or drop $scope.upload = function (file) { Upload.upload({ url: 'upload/url', data: {file: file, 'username': $scope.username} }).then(function (resp) { console.log('Success ' + resp.config.data.file.name + 'uploaded. Response: ' + resp.data); }, function (resp) { console.log('Error status: ' + resp.status); }, function (evt) { var progressPercentage = parseInt(100.0 * evt.loaded / evt.total); console.log('progress: ' + progressPercentage + '% ' + evt.config.data.file.name); }); }; // for multiple files: $scope.uploadFiles = function (files) { if (files && files.length) { for (var i = 0; i < files.length; i++) { Upload.upload({..., data: {file: files[i]}, ...})...; } // or send them all together for HTML5 browsers: Upload.upload({..., data: {file: files}, ...})...; } } }]); ``` ### Full reference #### File select and drop At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to link to the element. `ngf-select` only attributes are marked with * and `ngf-drop` only attributes are marked with +. ```html ngf-drop="" or "upload($files, ...)" ngf-change="upload($files, $file, $newFiles, $duplicateFiles, $invalidFiles, $event)" ng-model="myFiles" ngf-model-options="{updateOn: 'change click drop dropUrl paste', allowInvalid: false, debounce: 0}" ngf-model-invalid="invalidFile(s)" ngf-before-model-change="beforeChange($files, ...)" ng-disabled="boolean" ngf-select-disabled="boolean" ngf-drop-disabled="boolean" ngf-multiple="boolean" ngf-keep="true|false|'distinct'" ngf-fix-orientation="boolean" *ngf-capture="'camera'" or "'other'" *ngf-accept="'image/*'" +ngf-allow-dir="boolean" +ngf-include-dir="boolean" +ngf-drag-over-class="{pattern: 'image/*', accept:'acceptClass', reject:'rejectClass', delay:100}" or "'myDragOverClass'" or "calcDragOverClass($event)" +ngf-drag="drag($isDragging, $class, $event)" +ngf-drop-available="dropSupported" +ngf-stop-propagation="boolean" +ngf-hide-on-drop-not-available="boolean" +ngf-enable-firefox-paste="boolean" ngf-resize="{width: 100, height: 100, quality: .8, type: 'image/jpeg', ratio: '1:2', centerCrop: true, pattern='.jpg', restoreExif: false}" or resizeOptions() ngf-resize-if="$width > 1000 || $height > 1000" or "resizeCondition($file, $width, $height)" ngf-validate-after-resize="boolean" ngf-max-files="10" ngf-pattern="'.pdf,.jpg,video/*,!.jog'" ngf-min-size, ngf-max-size, ngf-max-total-size="100" in bytes or "'10KB'" or "'10MB'" or "'10GB'" ngf-min-height, ngf-max-height, ngf-min-width, ngf-max-width="1000" in pixels only images ngf-ratio="8:10,1.6" ngf-min-ratio, ngf-max-ratio="8:10" ngf-dimensions="$width > 1000 || $height > 1000" or "validateDimension($file, $width, $height)" ngf-min-duration, ngf-max-duration="100.5" in seconds or "'10s'" or "'10m'" or "'10h'" only audio, video ngf-duration="$duration > 1000" or "validateDuration($file, $duration)" ngf-validate="{size: {min: 10, max: '20MB'}, width: {min: 100, max:10000}, height: {min: 100, max: 300} ratio: '2x1', duration: {min: '10s', max: '5m'}, pattern: '.jpg'}" ngf-validate-fn="validate($file)" ngf-validate-async-fn="validate($file)" ngf-validate-force="boolean" ngf-ignore-invalid="'pattern maxSize'" ngf-run-all-validations="boolean" >Upload/Drop File Drag/drop is not supported image ``` #### File preview ```html *ngf-background="file" ngf-resize="{width: 20, height: 20, quality: 0.9}" ngf-no-object-url="true or false" > ngf-size="{width: 20, height: 20, quality: 0.9}" the image will be resized to this size ngf-as-background="boolean" > ``` #### Upload service: ```js var upload = Upload.upload({ *url: 'server/upload/url', // upload.php script, node.js route, or servlet url /* Specify the file and optional data to be sent to the server. Each field including nested objects will be sent as a form data multipart. Samples: {pic: file, username: username} {files: files, otherInfo: {id: id, person: person,...}} multiple files (html5) {profiles: {[{pic: file1, username: username1}, {pic: file2, username: username2}]} nested array multiple files (html5) {file: file, info: Upload.json({id: id, name: name, ...})} send fields as json string {file: file, info: Upload.jsonBlob({id: id, name: name, ...})} send fields as json blob, 'application/json' content_type {picFile: Upload.rename(file, 'profile.jpg'), title: title} send file with picFile key and profile.jpg file name*/ *data: {key: file, otherInfo: uploadInfo}, /* This is to accommodate server implementations expecting nested data object keys in .key or [key] format. Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file data: {rec: {name: 'N', pic: file}}, objectKey: '.k' sent as: rec.name -> N, rec.pic -> file */ objectKey: '[k]' or '.k' // default is '[k]' /* This is to accommodate server implementations expecting array data object keys in '[i]' or '[]' or ''(multiple entries with same key) format. Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],... data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],...*/ arrayKey: '[i]' or '[]' or '.i' or '' //default is '[i]' method: 'POST' or 'PUT'(html5), default POST, headers: {'Authorization': 'xxx'}, // only for html5 withCredentials: boolean, /* See resumable upload guide below the code for more details (html5 only) */ resumeSizeUrl: '/uploaded/size/url?file=' + file.name // uploaded file size so far on the server. resumeSizeResponseReader: function(data) {return data.size;} // reads the uploaded file size from resumeSizeUrl GET response resumeSize: function() {return promise;} // function that returns a prommise which will be // resolved to the upload file size on the server. resumeChunkSize: 10000 or '10KB' or '10MB' // upload in chunks of specified size disableProgress: boolean // default false, experimental as hotfix for potential library conflicts with other plugins ... and all other angular $http() options could be used here. }) // returns a promise upload.then(function(resp) { // file is uploaded successfully console.log('file ' + resp.config.data.file.name + 'is uploaded successfully. Response: ' + resp.data); }, function(resp) { // handle error }, function(evt) { // progress notify console.log('progress: ' + parseInt(100.0 * evt.loaded / evt.total) + '% file :'+ evt.config.data.file.name); }); upload.catch(errorCallback); upload.finally(callback, notifyCallback); /* access or attach event listeners to the underlying XMLHttpRequest */ upload.xhr(function(xhr){ xhr.upload.addEventListener(...) }); /* cancel/abort the upload in progress. */ upload.abort(); /* alternative way of uploading, send the file binary with the file's content-type. Could be used to upload files to CouchDB, imgur, etc... html5 FileReader is needed. This is equivalent to angular $http() but allow you to listen to the progress event for HTML5 browsers.*/ Upload.http({ url: '/server/upload/url', headers : { 'Content-Type': file.type }, data: file }) /* Set the default values for ngf-select and ngf-drop directives*/ Upload.setDefaults({ngfMinSize: 20000, ngfMaxSize:20000000, ...}) // These two defaults could be decreased if you experience out of memory issues // or could be increased if your app needs to show many images on the page. // Each image in ngf-src, ngf-background or ngf-thumbnail is stored and referenced as a blob url // and will only be released if the max value of the followings is reached. Upload.defaults.blobUrlsMaxMemory = 268435456 // default max total size of files stored in blob urls. Upload.defaults.blobUrlsMaxQueueSize = 200 // default max number of blob urls stored by this application. /* Convert a single file or array of files to a single or array of base64 data url representation of the file(s). Could be used to send file in base64 format inside json to the databases */ Upload.base64DataUrl(files).then(function(urls){...}); /* Convert the file to blob url object or base64 data url based on boolean disallowObjectUrl value */ Upload.dataUrl(file, boolean).then(function(url){...}); /* Get image file dimensions*/ Upload.imageDimensions(file).then(function(dimensions){console.log(dimensions.width, dimensions.height);}); /* Get audio/video duration*/ Upload.mediaDuration(file).then(function(durationInSeconds){...}); /* Resizes an image. Returns a promise */ // options: width, height, quality, type, ratio, centerCrop, resizeIf, restoreExif //resizeIf(width, height) returns boolean. See ngf-resize directive for more details of options. Upload.resize(file, options).then(function(resizedFile){...}); /* returns boolean showing if image resize is supported by this browser*/ Upload.isResizeSupported() /* returns boolean showing if resumable upload is supported by this browser*/ Upload.isResumeSupported() /* returns a file which will be uploaded with the newName instead of original file name */ Upload.rename(file, newName) /* converts the object to a Blob object with application/json content type for jsob byte streaming support #359 (html5 only)*/ Upload.jsonBlob(obj) /* converts the value to json to send data as json string. Same as angular.toJson(obj) */ Upload.json(obj) /* converts a dataUrl to Blob object.*/ var blob = upload.dataUrltoBlob(dataurl, name); /* returns true if there is an upload in progress. Can be used to prompt user before closing browser tab */ Upload.isUploadInProgress() boolean /* downloads and converts a given url to Blob object which could be added to files model */ Upload.urlToBlob(url).then(function(blob) {...}); /* returns boolean to check if the object is file and could be used as file in Upload.upload()/http() */ Upload.isFile(obj); /* fixes the exif orientation of the jpeg image file*/ Upload.applyExifRotation(file).then(...) ``` **ng-model** The model value will be a single file instead of an array if all of the followings are true: * `ngf-multiple` is not set or is resolved to false. * `multiple` attribute is not set on the element * `ngf-keep` is not set or is resolved to false. **validation** When any of the validation directives specified the form validation will take place and you can access the value of the validation using `myForm.myFileInputName.$error.` for example `form.file.$error.pattern`. If multiple file selection is allowed you can specify `ngf-model-invalid="invalidFiles"` to assing the invalid files to a model and find the error of each individual file with `file.$error` and description of it with `file.$errorParam`. You can use angular ngf-model-options to allow invalid files to be set to the ng-model `ngf-model-options="{allowInvalid: true}"`. **Upload multiple files**: Only for HTML5 FormData browsers (not IE8-9) you have an array of files or more than one file in your `data` to send them all in one request . Non-html5 browsers due to flash limitation will upload each file one by one in a separate request. You should iterate over the files and send them one by one for a cross browser solution. **drag and drop styling**: For file drag and drop, `ngf-drag-over-class` could be used to style the drop zone. It can be a function that returns a class name based on the $event. Default is "dragover" string. Only in chrome It could be a json object `{accept: 'a', 'reject': 'r', pattern: 'image/*', delay: 10}` that specify the class name for the accepted or rejected drag overs. The `pattern` specified or `ngf-pattern` will be used to validate the file's `mime-type` since that is the only property of the file that is reported by the browser on drag. So you cannot validate the file name/extension, size or other validations on drag. There is also some limitation on some file types which are not reported by Chrome. `delay` default is 100, and is used to fix css3 transition issues from dragging over/out/over [#277](https://github.com/danialfarid/angular-file-upload/issues/277). **Upload.setDefaults()**: If you have many file selects or drops you can set the default values for the directives by calling `Upload.setDefaults(options)`. `options` would be a json object with directive names in camelcase and their default values. **Resumable Uploads:** The plugin supports resumable uploads for large files. On your server you need to keep track of what files are being uploaded and how much of the file is uploaded. * `url` upload endpoint need to reassemble the file chunks by appending uploading content to the end of the file or correct chunk position if it already exists. * `resumeSizeUrl` server endpoint to return uploaded file size so far on the server to be able to resume the upload from where it is ended. It should return zero if the file has not been uploaded yet.
A GET request will be made to that url for each upload to determine if part of the file is already uploaded or not. You need a unique way of identifying the file on the server so you can pass the file name or generated id for the file as a request parameter.
By default it will assume that the response content is an integer or a json object with `size` integer property. If you return other formats from the endpoint you can specify `resumeSizeResponseReader` function to return the size value from the response. Alternatively instead of `resumeSizeUrl` you can use `resumeSize` function which returns a promise that resolves to the size of the uploaded file so far. Make sure when the file is fully uploaded without any error/abort this endpoint returns zero for the file size if you want to let the user to upload the same file again. Or optionally you could have a restart endpoint to set that back to zero to allow re-uploading the same file. * `resumeChunkSize` optionally you can specify this to upload the file in chunks to the server. This will allow uploading to GAE or other servers that have file size limitation and trying to upload the whole request before passing it for internal processing.
If this option is set the requests will have the following extra fields: `_chunkSize`, `_currentChunkSize`, `_chunkNumber` (zero starting), and `_totalSize` to help the server to write the uploaded chunk to the correct position. Uploading in chunks could slow down the overall upload time specially if the chunk size is too small. When you provide `resumeChunkSize` option one of the `resumeSizeUrl` or `resumeSize` is mandatory to know how much of the file is uploaded so far. ## Old browsers For browsers not supporting HTML5 FormData (IE8, IE9, ...) [FileAPI](https://github.com/mailru/FileAPI) module is used. **Note**: You need Flash installed on your browser since `FileAPI` uses Flash to upload files. These two files **`FileAPI.min.js`, `FileAPI.flash.swf`** will be loaded by the module on demand (no need to be included in the html) if the browser does not supports HTML5 FormData to avoid extra load for HTML5 browsers. You can place these two files beside `angular-file-upload-shim(.min).js` on your server to be loaded automatically from the same path or you can specify the path to those files if they are in a different path using the following script: ```html ... ``` **Old browsers known issues**: * Because of a Flash limitation/bug if the server doesn't send any response body the status code of the response will be always `204 'No Content'`. So if you have access to your server upload code at least return a character in the response for the status code to work properly. * Custom headers will not work due to a Flash limitation [#111](https://github.com/danialfarid/ng-file-upload/issues/111) [#224](https://github.com/danialfarid/ng-file-upload/issues/224) [#129](https://github.com/danialfarid/ng-file-upload/issues/129) * Due to Flash bug [#92](https://github.com/danialfarid/ng-file-upload/issues/92) Server HTTP error code 400 will be returned as 200 to the client. So avoid returning 400 on your server side for upload response otherwise it will be treated as a success response on the client side. * In case of an error response (http code >= 400) the custom error message returned from the server may not be available. For some error codes flash just provide a generic error message and ignores the response text. [#310](https://github.com/danialfarid/ng-file-upload/issues/310) * Older browsers won't allow `PUT` requests. [#261](https://github.com/danialfarid/ng-file-upload/issues/261) ## Server Side * **Java** You can find the sample server code in Java/GAE [here](https://github.com/danialfarid/ng-file-upload/blob/master/demo/src/main/java/com/df/angularfileupload/) * **Spring MVC** [Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/spring-mvc-example) provided by [zouroto](https://github.com/zouroto) * **Node.js** [Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/node.js-example) provided by [chovy](https://github.com/chovy). [Another wiki](https://github.com/danialfarid/ng-file-upload/wiki/Node-example) using Express 4.0 and the Multiparty provided by [Jonathan White](https://github.com/JonathanZWhite) * **Rails** * [Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/Rails-Example) provided by [guptapriyank](https://github.com/guptapriyank). * [Blog post](http://www.coshx.com/blog/2015/07/10/file-attachments-in-angular/) provided by [Coshx Labs](http://www.coshx.com/). * **Rails progress event**: If your server is Rails and Apache you may need to modify server configurations for the server to support upload progress. See [#207](https://github.com/danialfarid/ng-file-upload/issues/207) * **PHP** [Wiki Sample](https://github.com/danialfarid/ng-file-upload/wiki/PHP-Example) and related issue [only one file in $_FILES when uploading multiple files](https://github.com/danialfarid/ng-file-upload/issues/475) * **.Net** * [Demo](https://github.com/stewartm83/angular-fileupload-sample) showing how to use ng-file-upload with Asp.Net Web Api. * Sample client and server code [demo/C#](https://github.com/danialfarid/ng-file-upload/tree/master/demo/C%23) provided by [AtomStar](https://github.com/AtomStar) ## CORS To support CORS upload your server needs to allow cross domain requests. You can achieve that by having a filter or interceptor on your upload file server to add CORS headers to the response similar to this: ([sample java code](https://github.com/danialfarid/ng-file-upload/blob/master/demo/src/main/java/com/df/angularfileupload/CORSFilter.java)) ```java httpResp.setHeader("Access-Control-Allow-Methods", "POST, PUT, OPTIONS"); httpResp.setHeader("Access-Control-Allow-Origin", "your.other.server.com"); httpResp.setHeader("Access-Control-Allow-Headers", "Content-Type")); ``` For non-HTML5 IE8-9 browsers you would also need a `crossdomain.xml` file at the root of your server to allow CORS for flash: ([sample xml](https://angular-file-upload.appspot.com/crossdomain.xml)) ```xml ``` #### Amazon AWS S3 Upload For Amazon authentication version 4 [see this comment](https://github.com/danialfarid/ng-file-upload/issues/1128#issuecomment-196203268) The demo page has an option to upload to S3. Here is a sample config options: ```js Upload.upload({ url: 'https://angular-file-upload.s3.amazonaws.com/', //S3 upload url including bucket name method: 'POST', data: { key: file.name, // the key to store the file on S3, could be file name or customized AWSAccessKeyId: , acl: 'private', // sets the access to the uploaded file in the bucket: private, public-read, ... policy: $scope.policy, // base64-encoded json policy (see article below) signature: $scope.signature, // base64-encoded signature based on policy string (see article below) "Content-Type": file.type != '' ? file.type : 'application/octet-stream', // content type of the file (NotEmpty) filename: file.name, // this is needed for Flash polyfill IE8-9 file: file } }); ``` [This article](http://aws.amazon.com/articles/1434/) explains more about these fields and provides instructions on how to generate the policy and signature using a server side tool. These two values are generated from the json policy document which looks like this: ```js { "expiration": "2020-01-01T00:00:00Z", "conditions": [ {"bucket": "angular-file-upload"}, ["starts-with", "$key", ""], {"acl": "private"}, ["starts-with", "$Content-Type", ""], ["starts-with", "$filename", ""], ["content-length-range", 0, 524288000] ] } ``` The [demo](https://angular-file-upload.appspot.com/) page provide a helper tool to generate the policy and signature from you from the json policy document. **Note**: Please use https protocol to access demo page if you are using this tool to generate signature and policy to protect your aws secret key which should never be shared. Make sure that you provide upload and CORS post to your bucket at AWS -> S3 -> bucket name -> Properties -> Edit bucket policy and Edit CORS Configuration. Samples of these two files: ```js { "Version": "2012-10-17", "Statement": [ { "Sid": "UploadFile", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::xxxx:user/xxx" }, "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::angular-file-upload/*" }, { "Sid": "crossdomainAccess", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::angular-file-upload/crossdomain.xml" } ] } ``` ```xml http://angular-file-upload.appspot.com POST GET HEAD 3000 * ``` For IE8-9 flash polyfill you need to have a crossdomain.xml file at the root of you S3 bucket. Make sure the content-type of crossdomain.xml is text/xml and you provide read access to this file in your bucket policy. You can also have a look at [https://github.com/nukulb/s3-angular-file-upload](https://github.com/nukulb/s3-angular-file-upload) for another example with [this](https://github.com/danialfarid/ng-file-upload/issues/814#issuecomment-112198426) fix. ================================================ FILE: demo/C#/UploadController.js ================================================ (function () { 'use strict'; angular .module('app') .controller('UploadCtrl', UploadCtrl); UploadCtrl.$inject = ['$location', '$upload']; function UploadCtrl($location, $upload) { /* jshint validthis:true */ var vm = this; vm.title = 'UploadCtrl'; vm.onFileSelect = function ($files, user) { //$files: an array of files selected, each file has name, size, and type. for (var i = 0; i < $files.length; i++) { var file = $files[i]; vm.upload = $upload.upload({ url: 'Uploads/UploadHandler.ashx', data: { name: user.Name }, file: file, // or list of files ($files) for html5 only }).progress(function (evt) { //console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total)); }).success(function (data, status, headers, config) { alert('Uploaded successfully ' + file.name); }).error(function (err) { alert('Error occured during upload'); }); } }; } })(); ================================================ FILE: demo/C#/UploadHandler.ashx ================================================ <%@ WebHandler Language="C#" CodeBehind="UploadHandler.ashx.cs" Class="MyApp.Uploads.UploadHandler" %> ================================================ FILE: demo/C#/UploadHandler.ashx.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MyApp.Uploads { /// /// Summary description for UploadHandler /// public class UploadHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { if (context.Request.Files.Count > 0) { HttpFileCollection files = context.Request.Files; var userName = context.Request.Form["name"]; for (int i = 0; i < files.Count; i++) { HttpPostedFile file = files[i]; string fname = context.Server.MapPath("Uploads\\" + userName.ToUpper() + "\\" + file.FileName); file.SaveAs(fname); } } context.Response.ContentType = "text/plain"; context.Response.Write("File/s uploaded successfully!"); } public bool IsReusable { get { return false; } } } } ================================================ FILE: demo/pom.xml ================================================ 4.0.0 war 0.0.1 ng-file-upload-demo-server com.df.ng-file-upload.demo.server 1 UTF-8 3.1.0 commons-fileupload commons-fileupload 1.2 com.google.appengine appengine-api-1.0-sdk 1.9.18 com.google.appengine appengine-endpoints 1.9.18 javax.servlet servlet-api 2.5 provided javax.inject javax.inject 1 ${project.build.directory}/${project.build.finalName}/WEB-INF/classes org.apache.maven.plugins 3.2 maven-compiler-plugin 1.7 1.7 org.apache.maven.plugins maven-war-plugin 2.6 true <!– in order to interpolate version from pom into appengine-web.xml –> ${basedir}/src/main/webapp/WEB-INF true WEB-INF com.google.appengine appengine-maven-plugin 1.9.18 false
0.0.0.0
8888 1 -Xdebug -Xrunjdwp:transport=dt_socket,address=1044,server=y,suspend=n
================================================ FILE: demo/src/main/java/com/df/angularfileupload/CORSFilter.java ================================================ package com.df.angularfileupload; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class CORSFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResp = (HttpServletResponse) resp; HttpServletRequest httpReq = (HttpServletRequest) req; httpResp.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS"); httpResp.setHeader("Access-Control-Allow-Origin", "*"); if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) { httpResp.setHeader("Access-Control-Allow-Headers", httpReq.getHeader("Access-Control-Request-Headers")); } chain.doFilter(req, resp); } @Override public void init(FilterConfig arg0) throws ServletException { } @Override public void destroy() { } } ================================================ FILE: demo/src/main/java/com/df/angularfileupload/FileUpload.java ================================================ package com.df.angularfileupload; import com.google.appengine.repackaged.org.joda.time.LocalDateTime; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.servlet.ServletFileUpload; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; public class FileUpload extends HttpServlet { private static final long serialVersionUID = -8244073279641189889L; private final Logger log = Logger.getLogger(FileUpload.class.getName()); class SizeEntry { public int size; public LocalDateTime time; } static Map sizeMap = new ConcurrentHashMap<>(); int counter; @Override protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { clearOldValuesInSizeMap(); String ipAddress = req.getHeader("X-FORWARDED-FOR"); if (ipAddress == null) { ipAddress = req.getRemoteAddr(); } if (req.getMethod().equalsIgnoreCase("GET")) { if (req.getParameter("restart") != null) { sizeMap.remove(ipAddress + req.getParameter("name")); } SizeEntry entry = sizeMap.get(ipAddress + req.getParameter("name")); res.getWriter().write("{\"size\":" + (entry == null ? 0 : entry.size) + "}"); res.setContentType("application/json"); return; } req.setCharacterEncoding("utf-8"); if (!"OPTIONS".equalsIgnoreCase(req.getMethod()) && req.getParameter("errorCode") != null) { // res.getWriter().write(req.getParameter("errorMessage")); // res.getWriter().flush(); res.sendError(Integer.parseInt(req.getParameter("errorCode")), req.getParameter("errorMessage")); return; } StringBuilder sb = new StringBuilder("{\"result\": ["); if (req.getHeader("Content-Type") != null && req.getHeader("Content-Type").startsWith("multipart/form-data")) { ServletFileUpload upload = new ServletFileUpload(); FileItemIterator iterator = upload.getItemIterator(req); while (iterator.hasNext()) { FileItemStream item = iterator.next(); sb.append("{"); sb.append("\"fieldName\":\"").append(item.getFieldName()).append("\","); if (item.getName() != null) { sb.append("\"name\":\"").append(item.getName()).append("\","); } if (item.getName() != null) { sb.append("\"size\":\"").append(size(ipAddress + item.getName(), item.openStream())).append("\""); } else { sb.append("\"value\":\"").append(read(item.openStream()).replace("\"", "'")).append("\""); } sb.append("}"); if (iterator.hasNext()) { sb.append(","); } } } else { sb.append("{\"size\":\"" + size(ipAddress, req.getInputStream()) + "\"}"); } sb.append("]"); sb.append(", \"requestHeaders\": {"); @SuppressWarnings("unchecked") Enumeration headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { String header = headerNames.nextElement(); sb.append("\"").append(header).append("\":\"").append(req.getHeader(header)).append("\""); if (headerNames.hasMoreElements()) { sb.append(","); } } sb.append("}}"); res.setCharacterEncoding("utf-8"); res.getWriter().write(sb.toString()); } catch (Exception ex) { throw new ServletException(ex); } } private void clearOldValuesInSizeMap() { if (counter++ == 100) { for (Map.Entry entry : sizeMap.entrySet()) { if (entry.getValue().time.isBefore(LocalDateTime.now().minusHours(1))) { sizeMap.remove(entry.getKey()); } } counter = 0; } } protected int size(String key, InputStream stream) { int length = sizeMap.get(key) == null ? 0 : sizeMap.get(key).size; try { byte[] buffer = new byte[200000]; int size; while ((size = stream.read(buffer)) != -1) { length += size; SizeEntry entry = new SizeEntry(); entry.size = length; entry.time = LocalDateTime.now(); sizeMap.put(key, entry); // for (int i = 0; i < size; i++) { // System.out.print((char) buffer[i]); // } } } catch (IOException e) { throw new RuntimeException(e); } System.out.println(length); return length; } protected String read(InputStream stream) { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); try { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { throw new RuntimeException(e); } finally { try { reader.close(); } catch (IOException e) { //ignore } } return sb.toString(); } } ================================================ FILE: demo/src/main/java/com/df/angularfileupload/S3Signature.java ================================================ package com.df.angularfileupload; import com.google.api.server.spi.IoUtil; import com.google.appengine.repackaged.com.google.common.io.BaseEncoding; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class S3Signature extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String policy_document = IoUtil.readStream(req.getInputStream()); System.out.println(policy_document); String policy = BaseEncoding.base64().encode(policy_document.getBytes("UTF-8")). replaceAll("\n","").replaceAll("\r",""); Mac hmac; try { hmac = Mac.getInstance("HmacSHA1"); String aws_secret_key = req.getParameter("aws-secret-key"); System.out.println(aws_secret_key); hmac.init(new SecretKeySpec(aws_secret_key.getBytes("UTF-8"), "HmacSHA1")); String signature = BaseEncoding.base64().encode( hmac.doFinal(policy.getBytes("UTF-8"))) .replaceAll("\n", ""); res.setStatus(HttpServletResponse.SC_OK); res.setContentType("application/json"); res.getWriter().write("{\"signature\":\"" + signature + "\",\"policy\":\"" + policy + "\"}"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } } } ================================================ FILE: demo/src/main/resources/META-INF/jdoconfig.xml ================================================ ================================================ FILE: demo/src/main/resources/META-INF/persistence.xml ================================================ org.datanucleus.api.jpa.PersistenceProviderImpl ================================================ FILE: demo/src/main/resources/log4j.properties ================================================ # A default log4j configuration for log4j users. # # To use this configuration, deploy it into your application's WEB-INF/classes # directory. You are also encouraged to edit it as you like. # Configure the console as our one appender log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n # tighten logging on the DataNucleus Categories log4j.category.DataNucleus.JDO=WARN, A1 log4j.category.DataNucleus.Persistence=WARN, A1 log4j.category.DataNucleus.Cache=WARN, A1 log4j.category.DataNucleus.MetaData=WARN, A1 log4j.category.DataNucleus.General=WARN, A1 log4j.category.DataNucleus.Utility=WARN, A1 log4j.category.DataNucleus.Transaction=WARN, A1 log4j.category.DataNucleus.Datastore=WARN, A1 log4j.category.DataNucleus.ClassLoading=WARN, A1 log4j.category.DataNucleus.Plugin=WARN, A1 log4j.category.DataNucleus.ValueGeneration=WARN, A1 log4j.category.DataNucleus.Enhancer=WARN, A1 log4j.category.DataNucleus.SchemaTool=WARN, A1 ================================================ FILE: demo/src/main/webapp/WEB-INF/appengine-web.xml ================================================ angular-file-upload 9-0-0 true ================================================ FILE: demo/src/main/webapp/WEB-INF/logging.properties ================================================ # A default java.util.logging configuration. # (All App Engine logging is through java.util.logging by default). # # To use this configuration, copy it into your application's WEB-INF # folder and add the following to your appengine-web.xml: # # # # # # Set the default logging level for all loggers to WARNING .level = WARNING ================================================ FILE: demo/src/main/webapp/WEB-INF/web.xml ================================================ file_upload com.df.angularfileupload.FileUpload file_upload /upload s3_sign com.df.angularfileupload.S3Signature s3_sign /s3sign index.html SystemServiceServlet com.google.api.server.spi.SystemServiceServlet services SystemServiceServlet /_ah/spi/* cors_filter com.df.angularfileupload.CORSFilter cors_filter * ================================================ FILE: demo/src/main/webapp/common.css ================================================ body { font-family: Helvetica, arial, freesans, clean, sans-serif; } /* object { border: 3px solid red; } */ .upload-buttons input[type="file"] { width: 6.3em \0/ IE9; } .upload-button { Height: 26px; line-height: 30px; padding: 0 10px; background: #CCC; appearance: button; -moz-appearance: button; /* Firefox */ -webkit-appearance: button; /* Safari and Chrome */ position: relative; text-align: center; top: 7px; cursor: pointer; } .sel-file { padding: 1px 5px; font-size: smaller; color: grey; } .response { padding: 0; padding-top: 10px; margin: 3px 0; clear: both; list-style: none; } .response .sel-file li, .response .reqh { color: blue; padding-bottom: 5px; } fieldset { border: 1px solid #DDD; width: 620px; padding: 10px; line-height: 23px; } fieldset label { /*font-size: smaller;*/ } .progress { display: inline-block; width: 100px; border: 3px groove #CCC; } .progress div { font-size: smaller; background: orange; width: 0; } .drop-box { background: #F8F8F8; border: 5px dashed #DDD; width: 170px; text-align: center; padding: 50px 10px; margin-left: 10px; } .up-buttons { float: right; } .drop-box.dragover { border: 5px dashed blue; } .drop-box.dragover-err { border: 5px dashed red; } /* for IE*/ .js-fileapi-wrapper { display: inline-block; vertical-align: middle; } button { padding: 1px 5px; font-size: smaller; margin: 0 3px; } .ng-v { float: right; } .thumb { float: left; width: 18px; height: 18px; padding-right: 10px; } form .thumb { width: 24px; height: 24px; float: none; position: relative; top: 7px; } form .progress { line-height: 15px; } .edit-area { font-size: 14px; background: black; color: #f9f9f9; padding: 5px 1px; } #htmlEdit { margin-bottom: 25px; } .edit-div { font-size: smaller; } .CodeMirror { font-size: 14px; border: 1px solid #ccc; margin-bottom: 15px; } form button { padding: 3px 10px; font-weight: bold; margin-top: 10px; } .sub { font-size: smaller; color: #777; padding-top: 5px; padding-left: 25px; } .err { font-size: 12px; color: #C53F00; margin: 15px; padding: 15px; background-color: #F0F0F0; border: 1px solid black; } .s3 { font-size: smaller; color: #333; margin-left: 20px; } .s3 fieldset { border: 1px solid #AAA; } .s3 label { width: 180px; display: inline-block; } .s3 input { width: 300px; } .s3 .helper { margin-left: 5px; } .howto { margin-left: 10px; line-height: 20px; } .server { margin-bottom: 20px; } .srv-title { font-weight: bold; padding: 5px 0 10px 0; } :not(output):-moz-ui-invalid { box-shadow: none; } .preview { clear: both; } .preview img, .preview audio, .preview video { max-width: 300px; max-height: 150px; float: right; } .custom { font-size: 14px; margin-left: 20px; } ================================================ FILE: demo/src/main/webapp/crossdomain.xml ================================================ ================================================ FILE: demo/src/main/webapp/donate.html ================================================ Angular file upload donate
================================================ FILE: demo/src/main/webapp/index.html ================================================ Angular file upload sample
Angular Version:

Angular file upload Demo

Visit ng-file-upload on github

Upload on form submit Username: *required
Profile Picture: *required
Upload Successful

Play with options
Select File or Drop File

Paste or Drop Image from browser



















Preview image/audio/video:

  • {{f.progress}}%
    {{f.name}} - size: {{f.size}}B - type: {{f.type}} details

    {{f.result}}
    • file name: {{item.name}}
      name: {{item.fieldName}}
      size on the serve: {{item.size}}
      value: {{item.value}}
    request headers: {{f.result.requestHeaders}}
  • Invalid File: {{f.$errorMessages}} {{f.$errorParam}}, {{f.name}} - size: {{f.size}}B - type: {{f.type}}

{{errorMsg}}
How to upload to the server:

Can be used to upload files directory into CouchDB, imgur, etc... without multipart form data (HTML5 FileReader browsers only)

Provide S3 upload parameters. Click here for detailed documentation










If you don't have your policy and signature you can use this tool to generate them by providing these two fields and clicking on sign




Return server error with http code: and message:

donate Feedback/Issues ================================================ FILE: demo/src/main/webapp/js/FileAPI.js ================================================ /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git * FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF. */ /* * JavaScript Canvas to Blob 2.0.5 * https://github.com/blueimp/JavaScript-Canvas-to-Blob * * Copyright 2012, Sebastian Tschan * https://blueimp.net * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT * * Based on stackoverflow user Stoive's code snippet: * http://stackoverflow.com/q/4998908 */ /*jslint nomen: true, regexp: true */ /*global window, atob, Blob, ArrayBuffer, Uint8Array */ (function (window) { 'use strict'; var CanvasPrototype = window.HTMLCanvasElement && window.HTMLCanvasElement.prototype, hasBlobConstructor = window.Blob && (function () { try { return Boolean(new Blob()); } catch (e) { return false; } }()), hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && (function () { try { return new Blob([new Uint8Array(100)]).size === 100; } catch (e) { return false; } }()), BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array && function (dataURI) { var byteString, arrayBuffer, intArray, i, mimeString, bb; if (dataURI.split(',')[0].indexOf('base64') >= 0) { // Convert base64 to raw binary data held in a string: byteString = atob(dataURI.split(',')[1]); } else { // Convert base64/URLEncoded data component to raw binary data: byteString = decodeURIComponent(dataURI.split(',')[1]); } // Write the bytes of the string to an ArrayBuffer: arrayBuffer = new ArrayBuffer(byteString.length); intArray = new Uint8Array(arrayBuffer); for (i = 0; i < byteString.length; i += 1) { intArray[i] = byteString.charCodeAt(i); } // Separate out the mime component: mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // Write the ArrayBuffer (or ArrayBufferView) to a blob: if (hasBlobConstructor) { return new Blob( [hasArrayBufferViewSupport ? intArray : arrayBuffer], {type: mimeString} ); } bb = new BlobBuilder(); bb.append(arrayBuffer); return bb.getBlob(mimeString); }; if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) { if (CanvasPrototype.mozGetAsFile) { CanvasPrototype.toBlob = function (callback, type, quality) { if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) { callback(dataURLtoBlob(this.toDataURL(type, quality))); } else { callback(this.mozGetAsFile('blob', type)); } }; } else if (CanvasPrototype.toDataURL && dataURLtoBlob) { CanvasPrototype.toBlob = function (callback, type, quality) { callback(dataURLtoBlob(this.toDataURL(type, quality))); }; } } window.dataURLtoBlob = dataURLtoBlob; })(window); /*jslint evil: true */ /*global window, URL, webkitURL, ActiveXObject */ (function (window, undef){ 'use strict'; var gid = 1, noop = function (){}, document = window.document, doctype = document.doctype || {}, userAgent = window.navigator.userAgent, // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48 apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL), Blob = window.Blob, File = window.File, FileReader = window.FileReader, FormData = window.FormData, XMLHttpRequest = window.XMLHttpRequest, jQuery = window.jQuery, html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary))) && !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25 cors = html5 && ('withCredentials' in (new XMLHttpRequest)), chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice), // https://github.com/blueimp/JavaScript-Canvas-to-Blob dataURLtoBlob = window.dataURLtoBlob, _rimg = /img/i, _rcanvas = /canvas/i, _rimgcanvas = /img|canvas/i, _rinput = /input/i, _rdata = /^data:[^,]+,/, _toString = {}.toString, Math = window.Math, _SIZE_CONST = function (pow){ pow = new window.Number(Math.pow(1024, pow)); pow.from = function (sz){ return Math.round(sz * this); }; return pow; }, _elEvents = {}, // element event listeners _infoReader = [], // list of file info processors _readerEvents = 'abort progress error load loadend', _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '), currentTarget = 'currentTarget', // for minimize preventDefault = 'preventDefault', // and this too _isArray = function (ar) { return ar && ('length' in ar); }, /** * Iterate over a object or array */ _each = function (obj, fn, ctx){ if( obj ){ if( _isArray(obj) ){ for( var i = 0, n = obj.length; i < n; i++ ){ if( i in obj ){ fn.call(ctx, obj[i], i, obj); } } } else { for( var key in obj ){ if( obj.hasOwnProperty(key) ){ fn.call(ctx, obj[key], key, obj); } } } } }, /** * Merge the contents of two or more objects together into the first object */ _extend = function (dst){ var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; }; for( ; i < args.length; i++ ){ _each(args[i], _ext); } return dst; }, /** * Add event listener */ _on = function (el, type, fn){ if( el ){ var uid = api.uid(el); if( !_elEvents[uid] ){ _elEvents[uid] = {}; } var isFileReader = (FileReader && el) && (el instanceof FileReader); _each(type.split(/\s+/), function (type){ if( jQuery && !isFileReader){ jQuery.event.add(el, type, fn); } else { if( !_elEvents[uid][type] ){ _elEvents[uid][type] = []; } _elEvents[uid][type].push(fn); if( el.addEventListener ){ el.addEventListener(type, fn, false); } else if( el.attachEvent ){ el.attachEvent('on'+type, fn); } else { el['on'+type] = fn; } } }); } }, /** * Remove event listener */ _off = function (el, type, fn){ if( el ){ var uid = api.uid(el), events = _elEvents[uid] || {}; var isFileReader = (FileReader && el) && (el instanceof FileReader); _each(type.split(/\s+/), function (type){ if( jQuery && !isFileReader){ jQuery.event.remove(el, type, fn); } else { var fns = events[type] || [], i = fns.length; while( i-- ){ if( fns[i] === fn ){ fns.splice(i, 1); break; } } if( el.addEventListener ){ el.removeEventListener(type, fn, false); } else if( el.detachEvent ){ el.detachEvent('on'+type, fn); } else { el['on'+type] = null; } } }); } }, _one = function(el, type, fn){ _on(el, type, function _(evt){ _off(el, type, _); fn(evt); }); }, _fixEvent = function (evt){ if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; } if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; } return evt; }, _supportInputAttr = function (attr){ var input = document.createElement('input'); input.setAttribute('type', "file"); return attr in input; }, /** * FileAPI (core object) */ api = { version: '2.0.7', cors: false, html5: true, media: false, formData: true, multiPassResize: true, debug: false, pingUrl: false, multiFlash: false, flashAbortTimeout: 0, withCredentials: true, staticPath: './dist/', flashUrl: 0, // @default: './FileAPI.flash.swf' flashImageUrl: 0, // @default: './FileAPI.flash.image.swf' postNameConcat: function (name, idx){ return name + (idx != null ? '['+ idx +']' : ''); }, ext2mime: { jpg: 'image/jpeg' , tif: 'image/tiff' , txt: 'text/plain' }, // Fallback for flash accept: { 'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd' , 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs' , 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl' }, uploadRetry : 0, networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down chunkSize : 0, chunkUploadRetry : 0, chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down KB: _SIZE_CONST(1), MB: _SIZE_CONST(2), GB: _SIZE_CONST(3), TB: _SIZE_CONST(4), EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=', expando: 'fileapi' + (new Date).getTime(), uid: function (obj){ return obj ? (obj[api.expando] = obj[api.expando] || api.uid()) : (++gid, api.expando + gid) ; }, log: function (){ // ngf fix for IE8 #1071 if( api.debug && api._supportConsoleLog ){ if( api._supportConsoleLogApply ){ console.log.apply(console, arguments); } else { console.log([].join.call(arguments, ' ')); } } }, /** * Create new image * * @param {String} [src] * @param {Function} [fn] 1. error -- boolean, 2. img -- Image element * @returns {HTMLElement} */ newImage: function (src, fn){ var img = document.createElement('img'); if( fn ){ api.event.one(img, 'error load', function (evt){ fn(evt.type == 'error', img); img = null; }); } img.src = src; return img; }, /** * Get XHR * @returns {XMLHttpRequest} */ getXHR: function (){ var xhr; if( XMLHttpRequest ){ xhr = new XMLHttpRequest; } else if( window.ActiveXObject ){ try { xhr = new ActiveXObject('MSXML2.XMLHttp.3.0'); } catch (e) { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } } return xhr; }, isArray: _isArray, support: { dnd: cors && ('ondrop' in document.createElement('div')), cors: cors, html5: html5, chunked: chunked, dataURI: true, accept: _supportInputAttr('accept'), multiple: _supportInputAttr('multiple') }, event: { on: _on , off: _off , one: _one , fix: _fixEvent }, throttle: function(fn, delay) { var id, args; return function _throttle(){ args = arguments; if( !id ){ fn.apply(window, args); id = setTimeout(function (){ id = 0; fn.apply(window, args); }, delay); } }; }, F: function (){}, parseJSON: function (str){ var json; if( window.JSON && JSON.parse ){ json = JSON.parse(str); } else { json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))(); } return json; }, trim: function (str){ str = String(str); return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); }, /** * Simple Defer * @return {Object} */ defer: function (){ var list = [] , result , error , defer = { resolve: function (err, res){ defer.resolve = noop; error = err || false; result = res; while( res = list.shift() ){ res(error, result); } }, then: function (fn){ if( error !== undef ){ fn(error, result); } else { list.push(fn); } } }; return defer; }, queue: function (fn){ var _idx = 0 , _length = 0 , _fail = false , _end = false , queue = { inc: function (){ _length++; }, next: function (){ _idx++; setTimeout(queue.check, 0); }, check: function (){ (_idx >= _length) && !_fail && queue.end(); }, isFail: function (){ return _fail; }, fail: function (){ !_fail && fn(_fail = true); }, end: function (){ if( !_end ){ _end = true; fn(); } } } ; return queue; }, /** * For each object * * @param {Object|Array} obj * @param {Function} fn * @param {*} [ctx] */ each: _each, /** * Async for * @param {Array} array * @param {Function} callback */ afor: function (array, callback){ var i = 0, n = array.length; if( _isArray(array) && n-- ){ (function _next(){ callback(n != i && _next, array[i], i++); })(); } else { callback(false); } }, /** * Merge the contents of two or more objects together into the first object * * @param {Object} dst * @return {Object} */ extend: _extend, /** * Is file? * @param {File} file * @return {Boolean} */ isFile: function (file){ return _toString.call(file) === '[object File]'; }, /** * Is blob? * @param {Blob} blob * @returns {Boolean} */ isBlob: function (blob) { return this.isFile(blob) || (_toString.call(blob) === '[object Blob]'); }, /** * Is canvas element * * @param {HTMLElement} el * @return {Boolean} */ isCanvas: function (el){ return el && _rcanvas.test(el.nodeName); }, getFilesFilter: function (filter){ filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || ''); return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./; }, /** * Read as DataURL * * @param {File|Element} file * @param {Function} fn */ readAsDataURL: function (file, fn){ if( api.isCanvas(file) ){ _emit(file, fn, 'load', api.toDataURL(file)); } else { _readAs(file, fn, 'DataURL'); } }, /** * Read as Binary string * * @param {File} file * @param {Function} fn */ readAsBinaryString: function (file, fn){ if( _hasSupportReadAs('BinaryString') ){ _readAs(file, fn, 'BinaryString'); } else { // Hello IE10! _readAs(file, function (evt){ if( evt.type == 'load' ){ try { // dataURL -> binaryString evt.result = api.toBinaryString(evt.result); } catch (e){ evt.type = 'error'; evt.message = e.toString(); } } fn(evt); }, 'DataURL'); } }, /** * Read as ArrayBuffer * * @param {File} file * @param {Function} fn */ readAsArrayBuffer: function(file, fn){ _readAs(file, fn, 'ArrayBuffer'); }, /** * Read as text * * @param {File} file * @param {String} encoding * @param {Function} [fn] */ readAsText: function(file, encoding, fn){ if( !fn ){ fn = encoding; encoding = 'utf-8'; } _readAs(file, fn, 'Text', encoding); }, /** * Convert image or canvas to DataURL * * @param {Element} el Image or Canvas element * @param {String} [type] mime-type * @return {String} */ toDataURL: function (el, type){ if( typeof el == 'string' ){ return el; } else if( el.toDataURL ){ return el.toDataURL(type || 'image/png'); } }, /** * Canvert string, image or canvas to binary string * * @param {String|Element} val * @return {String} */ toBinaryString: function (val){ return window.atob(api.toDataURL(val).replace(_rdata, '')); }, /** * Read file or DataURL as ImageElement * * @param {File|String} file * @param {Function} fn * @param {Boolean} [progress] */ readAsImage: function (file, fn, progress){ if( api.isFile(file) ){ if( apiURL ){ /** @namespace apiURL.createObjectURL */ var data = apiURL.createObjectURL(file); if( data === undef ){ _emit(file, fn, 'error'); } else { api.readAsImage(data, fn, progress); } } else { api.readAsDataURL(file, function (evt){ if( evt.type == 'load' ){ api.readAsImage(evt.result, fn, progress); } else if( progress || evt.type == 'error' ){ _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total }); } }); } } else if( api.isCanvas(file) ){ _emit(file, fn, 'load', file); } else if( _rimg.test(file.nodeName) ){ if( file.complete ){ _emit(file, fn, 'load', file); } else { var events = 'error abort load'; _one(file, events, function _fn(evt){ if( evt.type == 'load' && apiURL ){ /** @namespace apiURL.revokeObjectURL */ apiURL.revokeObjectURL(file.src); } _off(file, events, _fn); _emit(file, fn, evt, file); }); } } else if( file.iframe ){ _emit(file, fn, { type: 'error' }); } else { // Created image var img = api.newImage(file.dataURL || file); api.readAsImage(img, fn, progress); } }, /** * Make file by name * * @param {String} name * @return {Array} */ checkFileObj: function (name){ var file = {}, accept = api.accept; if( typeof name == 'object' ){ file = name; } else { file.name = (name + '').split(/\\|\//g).pop(); } if( file.type == null ){ file.type = file.name.split('.').pop(); } _each(accept, function (ext, type){ ext = new RegExp(ext.replace(/\s/g, '|'), 'i'); if( ext.test(file.type) || api.ext2mime[file.type] ){ file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type); } }); return file; }, /** * Get drop files * * @param {Event} evt * @param {Function} callback */ getDropFiles: function (evt, callback){ var files = [] , dataTransfer = _getDataTransfer(evt) , entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0]) , queue = api.queue(function (){ callback(files); }) ; _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){ queue.inc(); try { if( entrySupport ){ _readEntryAsFiles(item, function (err, entryFiles){ if( err ){ api.log('[err] getDropFiles:', err); } else { files.push.apply(files, entryFiles); } queue.next(); }); } else { _isRegularFile(item, function (yes){ yes && files.push(item); queue.next(); }); } } catch( err ){ queue.next(); api.log('[err] getDropFiles: ', err); } }); queue.check(); }, /** * Get file list * * @param {HTMLInputElement|Event} input * @param {String|Function} [filter] * @param {Function} [callback] * @return {Array|Null} */ getFiles: function (input, filter, callback){ var files = []; if( callback ){ api.filterFiles(api.getFiles(input), filter, callback); return null; } if( input.jquery ){ // jQuery object input.each(function (){ files = files.concat(api.getFiles(this)); }); input = files; files = []; } if( typeof filter == 'string' ){ filter = api.getFilesFilter(filter); } if( input.originalEvent ){ // jQuery event input = _fixEvent(input.originalEvent); } else if( input.srcElement ){ // IE Event input = _fixEvent(input); } if( input.dataTransfer ){ // Drag'n'Drop input = input.dataTransfer; } else if( input.target ){ // Event input = input.target; } if( input.files ){ // Input[type="file"] files = input.files; if( !html5 ){ // Partial support for file api files[0].blob = input; files[0].iframe = true; } } else if( !html5 && isInputFile(input) ){ if( api.trim(input.value) ){ files = [api.checkFileObj(input.value)]; files[0].blob = input; files[0].iframe = true; } } else if( _isArray(input) ){ files = input; } return api.filter(files, function (file){ return !filter || filter.test(file.name); }); }, /** * Get total file size * @param {Array} files * @return {Number} */ getTotalSize: function (files){ var size = 0, i = files && files.length; while( i-- ){ size += files[i].size; } return size; }, /** * Get image information * * @param {File} file * @param {Function} fn */ getInfo: function (file, fn){ var info = {}, readers = _infoReader.concat(); if( api.isFile(file) ){ (function _next(){ var reader = readers.shift(); if( reader ){ if( reader.test(file.type) ){ reader(file, function (err, res){ if( err ){ fn(err); } else { _extend(info, res); _next(); } }); } else { _next(); } } else { fn(false, info); } })(); } else { fn('not_support_info', info); } }, /** * Add information reader * * @param {RegExp} mime * @param {Function} fn */ addInfoReader: function (mime, fn){ fn.test = function (type){ return mime.test(type); }; _infoReader.push(fn); }, /** * Filter of array * * @param {Array} input * @param {Function} fn * @return {Array} */ filter: function (input, fn){ var result = [], i = 0, n = input.length, val; for( ; i < n; i++ ){ if( i in input ){ val = input[i]; if( fn.call(val, val, i, input) ){ result.push(val); } } } return result; }, /** * Filter files * * @param {Array} files * @param {Function} eachFn * @param {Function} resultFn */ filterFiles: function (files, eachFn, resultFn){ if( files.length ){ // HTML5 or Flash var queue = files.concat(), file, result = [], deleted = []; (function _next(){ if( queue.length ){ file = queue.shift(); api.getInfo(file, function (err, info){ (eachFn(file, err ? false : info) ? result : deleted).push(file); _next(); }); } else { resultFn(result, deleted); } })(); } else { resultFn([], files); } }, upload: function (options){ options = _extend({ jsonp: 'callback' , prepare: api.F , beforeupload: api.F , upload: api.F , fileupload: api.F , fileprogress: api.F , filecomplete: api.F , progress: api.F , complete: api.F , pause: api.F , imageOriginal: true , chunkSize: api.chunkSize , chunkUploadRetry: api.chunkUploadRetry , uploadRetry: api.uploadRetry }, options); if( options.imageAutoOrientation && !options.imageTransform ){ options.imageTransform = { rotate: 'auto' }; } var proxyXHR = new api.XHR(options) , dataArray = this._getFilesDataArray(options.files) , _this = this , _total = 0 , _loaded = 0 , _nextFile , _complete = false ; // calc total size _each(dataArray, function (data){ _total += data.size; }); // Array of files proxyXHR.files = []; _each(dataArray, function (data){ proxyXHR.files.push(data.file); }); // Set upload status props proxyXHR.total = _total; proxyXHR.loaded = 0; proxyXHR.filesLeft = dataArray.length; // emit "beforeupload" event options.beforeupload(proxyXHR, options); // Upload by file _nextFile = function (){ var data = dataArray.shift() , _file = data && data.file , _fileLoaded = false , _fileOptions = _simpleClone(options) ; proxyXHR.filesLeft = dataArray.length; if( _file && _file.name === api.expando ){ _file = null; api.log('[warn] FileAPI.upload() — called without files'); } if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){ // Mark active job _complete = false; // Set current upload file proxyXHR.currentFile = _file; // Prepare file options if (_file && options.prepare(_file, _fileOptions) === false) { _nextFile.call(_this); return; } _fileOptions.file = _file; _this._getFormData(_fileOptions, data, function (form){ if( !_loaded ){ // emit "upload" event options.upload(proxyXHR, options); } var xhr = new api.XHR(_extend({}, _fileOptions, { upload: _file ? function (){ // emit "fileupload" event options.fileupload(_file, xhr, _fileOptions); } : noop, progress: _file ? function (evt){ if( !_fileLoaded ){ // For ignore the double calls. _fileLoaded = (evt.loaded === evt.total); // emit "fileprogress" event options.fileprogress({ type: 'progress' , total: data.total = evt.total , loaded: data.loaded = evt.loaded }, _file, xhr, _fileOptions); // emit "progress" event options.progress({ type: 'progress' , total: _total , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0 }, _file, xhr, _fileOptions); } } : noop, complete: function (err){ _each(_xhrPropsExport, function (name){ proxyXHR[name] = xhr[name]; }); if( _file ){ data.total = (data.total || data.size); data.loaded = data.total; if( !err ) { // emulate 100% "progress" this.progress(data); // fixed throttle event _fileLoaded = true; // bytes loaded _loaded += data.size; // data.size != data.total, it's desirable fix this proxyXHR.loaded = _loaded; } // emit "filecomplete" event options.filecomplete(err, xhr, _file, _fileOptions); } // upload next file setTimeout(function () {_nextFile.call(_this);}, 0); } })); // xhr // ... proxyXHR.abort = function (current){ if (!current) { dataArray.length = 0; } this.current = current; xhr.abort(); }; // Start upload xhr.send(form); }); } else { var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204; options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options); // Mark done state _complete = true; } }; // Next tick setTimeout(_nextFile, 0); // Append more files to the existing request // first - add them to the queue head/tail proxyXHR.append = function (files, first) { files = api._getFilesDataArray([].concat(files)); _each(files, function (data) { _total += data.size; proxyXHR.files.push(data.file); if (first) { dataArray.unshift(data); } else { dataArray.push(data); } }); proxyXHR.statusText = ""; if( _complete ){ _nextFile.call(_this); } }; // Removes file from queue by file reference and returns it proxyXHR.remove = function (file) { var i = dataArray.length, _file; while( i-- ){ if( dataArray[i].file == file ){ _file = dataArray.splice(i, 1); _total -= _file.size; } } return _file; }; return proxyXHR; }, _getFilesDataArray: function (data){ var files = [], oFiles = {}; if( isInputFile(data) ){ var tmp = api.getFiles(data); oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0]; } else if( _isArray(data) && isInputFile(data[0]) ){ _each(data, function (input){ oFiles[input.name || 'file'] = api.getFiles(input); }); } else { oFiles = data; } _each(oFiles, function add(file, name){ if( _isArray(file) ){ _each(file, function (file){ add(file, name); }); } else if( file && (file.name || file.image) ){ files.push({ name: name , file: file , size: file.size , total: file.size , loaded: 0 }); } }); if( !files.length ){ // Create fake `file` object files.push({ file: { name: api.expando } }); } return files; }, _getFormData: function (options, data, fn){ var file = data.file , name = data.name , filename = file.name , filetype = file.type , trans = api.support.transform && options.imageTransform , Form = new api.Form , queue = api.queue(function (){ fn(Form); }) , isOrignTrans = trans && _isOriginTransform(trans) , postNameConcat = api.postNameConcat ; // Append data _each(options.data, function add(val, name){ if( typeof val == 'object' ){ _each(val, function (v, i){ add(v, postNameConcat(name, i)); }); } else { Form.append(name, val); } }); (function _addFile(file/**Object*/){ if( file.image ){ // This is a FileAPI.Image queue.inc(); file.toData(function (err, image){ // @todo: error filename = filename || (new Date).getTime()+'.png'; _addFile(image); queue.next(); }); } else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){ queue.inc(); if( isOrignTrans ){ // Convert to array for transform function trans = [trans]; } api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){ if( isOrignTrans && !err ){ if( !dataURLtoBlob && !api.flashEngine ){ // Canvas.toBlob or Flash not supported, use multipart Form.multipart = true; } Form.append(name, images[0], filename, trans[0].type || filetype); } else { var addOrigin = 0; if( !err ){ _each(images, function (image, idx){ if( !dataURLtoBlob && !api.flashEngine ){ Form.multipart = true; } if( !trans[idx].postName ){ addOrigin = 1; } Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype); }); } if( err || options.imageOriginal ){ Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype); } } queue.next(); }); } else if( filename !== api.expando ){ Form.append(name, file, filename); } })(file); queue.check(); }, reset: function (inp, notRemove){ var parent, clone; if( jQuery ){ clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0]; if( !notRemove ){ jQuery(inp).remove(); } } else { parent = inp.parentNode; clone = parent.insertBefore(inp.cloneNode(true), inp); clone.value = ''; if( !notRemove ){ parent.removeChild(inp); } _each(_elEvents[api.uid(inp)], function (fns, type){ _each(fns, function (fn){ _off(inp, type, fn); _on(clone, type, fn); }); }); } return clone; }, /** * Load remote file * * @param {String} url * @param {Function} fn * @return {XMLHttpRequest} */ load: function (url, fn){ var xhr = api.getXHR(); if( xhr ){ xhr.open('GET', url, true); if( xhr.overrideMimeType ){ xhr.overrideMimeType('text/plain; charset=x-user-defined'); } _on(xhr, 'progress', function (/**Event*/evt){ /** @namespace evt.lengthComputable */ if( evt.lengthComputable ){ fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr); } }); xhr.onreadystatechange = function(){ if( xhr.readyState == 4 ){ xhr.onreadystatechange = null; if( xhr.status == 200 ){ url = url.split('/'); /** @namespace xhr.responseBody */ var file = { name: url[url.length-1] , size: xhr.getResponseHeader('Content-Length') , type: xhr.getResponseHeader('Content-Type') }; file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText); fn({ type: 'load', result: file }, xhr); } else { fn({ type: 'error' }, xhr); } } }; xhr.send(null); } else { fn({ type: 'error' }); } return xhr; }, encode64: function (str){ var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0; if( typeof str !== 'string' ){ str = String(str); } while( i < str.length ){ //all three "& 0xff" added below are there to fix a known bug //with bytes returned by xhr.responseText var byte1 = str.charCodeAt(i++) & 0xff , byte2 = str.charCodeAt(i++) & 0xff , byte3 = str.charCodeAt(i++) & 0xff , enc1 = byte1 >> 2 , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4) , enc3, enc4 ; if( isNaN(byte2) ){ enc3 = enc4 = 64; } else { enc3 = ((byte2 & 15) << 2) | (byte3 >> 6); enc4 = isNaN(byte3) ? 64 : byte3 & 63; } outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4); } return outStr; } } // api ; function _emit(target, fn, name, res, ext){ var evt = { type: name.type || name , target: target , result: res }; _extend(evt, ext); fn(evt); } function _hasSupportReadAs(as){ return FileReader && !!FileReader.prototype['readAs'+as]; } function _readAs(file, fn, as, encoding){ if( api.isBlob(file) && _hasSupportReadAs(as) ){ var Reader = new FileReader; // Add event listener _on(Reader, _readerEvents, function _fn(evt){ var type = evt.type; if( type == 'progress' ){ _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total }); } else if( type == 'loadend' ){ _off(Reader, _readerEvents, _fn); Reader = null; } else { _emit(file, fn, evt, evt.target.result); } }); try { // ReadAs ... if( encoding ){ Reader['readAs'+as](file, encoding); } else { Reader['readAs'+as](file); } } catch (err){ _emit(file, fn, 'error', undef, { error: err.toString() }); } } else { _emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as }); } } function _isRegularFile(file, callback){ // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){ if( FileReader ){ try { var Reader = new FileReader(); _one(Reader, _readerEvents, function (evt){ var isFile = evt.type != 'error'; callback(isFile); if( isFile ){ Reader.abort(); } }); Reader.readAsDataURL(file); } catch( err ){ callback(false); } } else { callback(null); } } else { callback(true); } } function _getAsEntry(item){ var entry; if( item.getAsEntry ){ entry = item.getAsEntry(); } else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); } return entry; } function _readEntryAsFiles(entry, callback){ if( !entry ){ // error callback('invalid entry'); } else if( entry.isFile ){ // Read as file entry.file(function(file){ // success file.fullPath = entry.fullPath; callback(false, [file]); }, function (err){ // error callback('FileError.code: '+err.code); }); } else if( entry.isDirectory ){ var reader = entry.createReader(), result = []; reader.readEntries(function(entries){ // success api.afor(entries, function (next, entry){ _readEntryAsFiles(entry, function (err, files){ if( err ){ api.log(err); } else { result = result.concat(files); } if( next ){ next(); } else { callback(false, result); } }); }); }, function (err){ // error callback('directory_reader: ' + err); }); } else { _readEntryAsFiles(_getAsEntry(entry), callback); } } function _simpleClone(obj){ var copy = {}; _each(obj, function (val, key){ if( val && (typeof val === 'object') && (val.nodeType === void 0) ){ val = _extend({}, val); } copy[key] = val; }); return copy; } function isInputFile(el){ return _rinput.test(el && el.tagName); } function _getDataTransfer(evt){ return (evt.originalEvent || evt || '').dataTransfer || {}; } function _isOriginTransform(trans){ var key; for( key in trans ){ if( trans.hasOwnProperty(key) ){ if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){ return true; } } } return false; } // Add default image info reader api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){ if( !file.__dimensions ){ var defer = file.__dimensions = api.defer(); api.readAsImage(file, function (evt){ var img = evt.target; defer.resolve(evt.type == 'load' ? false : 'error', { width: img.width , height: img.height }); img.src = api.EMPTY_PNG; img = null; }); } file.__dimensions.then(callback); }); /** * Drag'n'Drop special event * * @param {HTMLElement} el * @param {Function} onHover * @param {Function} onDrop */ api.event.dnd = function (el, onHover, onDrop){ var _id, _type; if( !onDrop ){ onDrop = onHover; onHover = api.F; } if( FileReader ){ // Hover _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){ var types = _getDataTransfer(evt).types , i = types && types.length , debounceTrigger = false ; while( i-- ){ if( ~types[i].indexOf('File') ){ evt[preventDefault](); if( _type !== evt.type ){ _type = evt.type; // Store current type of event if( _type != 'dragleave' ){ onHover.call(evt[currentTarget], true, evt); } debounceTrigger = true; } break; // exit from "while" } } if( debounceTrigger ){ clearTimeout(_id); _id = setTimeout(function (){ onHover.call(evt[currentTarget], _type != 'dragleave', evt); }, 50); } }); // Drop _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){ evt[preventDefault](); _type = 0; onHover.call(evt[currentTarget], false, evt); api.getDropFiles(evt, function (files){ onDrop.call(evt[currentTarget], files, evt); }); }); } else { api.log("Drag'n'Drop -- not supported"); } }; /** * Remove drag'n'drop * @param {HTMLElement} el * @param {Function} onHover * @param {Function} onDrop */ api.event.dnd.off = function (el, onHover, onDrop){ _off(el, 'dragenter dragleave dragover', onHover.ff); _off(el, 'drop', onDrop.ff); }; // Support jQuery if( jQuery && !jQuery.fn.dnd ){ jQuery.fn.dnd = function (onHover, onDrop){ return this.each(function (){ api.event.dnd(this, onHover, onDrop); }); }; jQuery.fn.offdnd = function (onHover, onDrop){ return this.each(function (){ api.event.dnd.off(this, onHover, onDrop); }); }; } // @export window.FileAPI = _extend(api, window.FileAPI); // Debug info api.log('FileAPI: ' + api.version); api.log('protocol: ' + window.location.protocol); api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId); // @detect 'x-ua-compatible' _each(document.getElementsByTagName('meta'), function (meta){ if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){ api.log('meta.http-equiv: ' + meta.getAttribute('content')); } }); // configuration try { api._supportConsoleLog = !!console.log; api._supportConsoleLogApply = !!console.log.apply; } catch (err) {} if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; } if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; } if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; } })(window, void 0); /*global window, FileAPI, document */ (function (api, document, undef) { 'use strict'; var min = Math.min, round = Math.round, getCanvas = function () { return document.createElement('canvas'); }, support = false, exifOrientation = { 8: 270 , 3: 180 , 6: 90 , 7: 270 , 4: 180 , 5: 90 } ; try { support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1; } catch (e){} function Image(file){ if( file instanceof Image ){ var img = new Image(file.file); api.extend(img.matrix, file.matrix); return img; } else if( !(this instanceof Image) ){ return new Image(file); } this.file = file; this.size = file.size || 100; this.matrix = { sx: 0, sy: 0, sw: 0, sh: 0, dx: 0, dy: 0, dw: 0, dh: 0, resize: 0, // min, max OR preview deg: 0, quality: 1, // jpeg quality filter: 0 }; } Image.prototype = { image: true, constructor: Image, set: function (attrs){ api.extend(this.matrix, attrs); return this; }, crop: function (x, y, w, h){ if( w === undef ){ w = x; h = y; x = y = 0; } return this.set({ sx: x, sy: y, sw: w, sh: h || w }); }, resize: function (w, h, strategy){ if( /min|max/.test(h) ){ strategy = h; h = w; } return this.set({ dw: w, dh: h || w, resize: strategy }); }, preview: function (w, h){ return this.resize(w, h || w, 'preview'); }, rotate: function (deg){ return this.set({ deg: deg }); }, filter: function (filter){ return this.set({ filter: filter }); }, overlay: function (images){ return this.set({ overlay: images }); }, clone: function (){ return new Image(this); }, _load: function (image, fn){ var self = this; if( /img|video/i.test(image.nodeName) ){ fn.call(self, null, image); } else { api.readAsImage(image, function (evt){ fn.call(self, evt.type != 'load', evt.result); }); } }, _apply: function (image, fn){ var canvas = getCanvas() , m = this.getMatrix(image) , ctx = canvas.getContext('2d') , width = image.videoWidth || image.width , height = image.videoHeight || image.height , deg = m.deg , dw = m.dw , dh = m.dh , w = width , h = height , filter = m.filter , copy // canvas copy , buffer = image , overlay = m.overlay , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); }) , renderImageToCanvas = api.renderImageToCanvas ; // Normalize angle deg = deg - Math.floor(deg/360)*360; // For `renderImageToCanvas` image._type = this.file.type; while(m.multipass && min(w/dw, h/dh) > 2 ){ w = (w/2 + 0.5)|0; h = (h/2 + 0.5)|0; copy = getCanvas(); copy.width = w; copy.height = h; if( buffer !== image ){ renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h); buffer = copy; } else { buffer = copy; renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h); m.sx = m.sy = m.sw = m.sh = 0; } } canvas.width = (deg % 180) ? dh : dw; canvas.height = (deg % 180) ? dw : dh; canvas.type = m.type; canvas.quality = m.quality; ctx.rotate(deg * Math.PI / 180); renderImageToCanvas(ctx.canvas, buffer , m.sx, m.sy , m.sw || buffer.width , m.sh || buffer.height , (deg == 180 || deg == 270 ? -dw : 0) , (deg == 90 || deg == 180 ? -dh : 0) , dw, dh ); dw = canvas.width; dh = canvas.height; // Apply overlay overlay && api.each([].concat(overlay), function (over){ queue.inc(); // preload var img = new window.Image, fn = function (){ var x = over.x|0 , y = over.y|0 , w = over.w || img.width , h = over.h || img.height , rel = over.rel ; // center | right | left x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x); // center | bottom | top y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y); api.event.off(img, 'error load abort', fn); try { ctx.globalAlpha = over.opacity || 1; ctx.drawImage(img, x, y, w, h); } catch (er){} queue.next(); }; api.event.on(img, 'error load abort', fn); img.src = over.src; if( img.complete ){ fn(); } }); if( filter ){ queue.inc(); Image.applyFilter(canvas, filter, queue.next); } queue.check(); }, getMatrix: function (image){ var m = api.extend({}, this.matrix) , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height , dw = m.dw = m.dw || sw , dh = m.dh = m.dh || sh , sf = sw/sh, df = dw/dh , strategy = m.resize ; if( strategy == 'preview' ){ if( dw != sw || dh != sh ){ // Make preview var w, h; if( df >= sf ){ w = sw; h = w / df; } else { h = sh; w = h * df; } if( w != sw || h != sh ){ m.sx = ~~((sw - w)/2); m.sy = ~~((sh - h)/2); sw = w; sh = h; } } } else if( strategy ){ if( !(sw > dw || sh > dh) ){ dw = sw; dh = sh; } else if( strategy == 'min' ){ dw = round(sf < df ? min(sw, dw) : dh*sf); dh = round(sf < df ? dw/sf : min(sh, dh)); } else { dw = round(sf >= df ? min(sw, dw) : dh*sf); dh = round(sf >= df ? dw/sf : min(sh, dh)); } } m.sw = sw; m.sh = sh; m.dw = dw; m.dh = dh; m.multipass = api.multiPassResize; return m; }, _trans: function (fn){ this._load(this.file, function (err, image){ if( err ){ fn(err); } else { try { this._apply(image, fn); } catch (err){ api.log('[err] FileAPI.Image.fn._apply:', err); fn(err); } } }); }, get: function (fn){ if( api.support.transform ){ var _this = this, matrix = _this.matrix; if( matrix.deg == 'auto' ){ api.getInfo(_this.file, function (err, info){ // rotate by exif orientation matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0; _this._trans(fn); }); } else { _this._trans(fn); } } else { fn('not_support_transform'); } return this; }, toData: function (fn){ return this.get(fn); } }; Image.exifOrientation = exifOrientation; Image.transform = function (file, transform, autoOrientation, fn){ function _transform(err, img){ // img -- info object var images = {} , queue = api.queue(function (err){ fn(err, images); }) ; if( !err ){ api.each(transform, function (params, name){ if( !queue.isFail() ){ var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function'; if( isFn ){ params(img, ImgTrans); } else if( params.width ){ ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy); } else { if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){ ImgTrans.resize(params.maxWidth, params.maxHeight, 'max'); } } if( params.crop ){ var crop = params.crop; ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height); } if( params.rotate === undef && autoOrientation ){ params.rotate = 'auto'; } ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' }); if( !isFn ){ ImgTrans.set({ deg: params.rotate , overlay: params.overlay , filter: params.filter , quality: params.quality || 1 }); } queue.inc(); ImgTrans.toData(function (err, image){ if( err ){ queue.fail(); } else { images[name] = image; queue.next(); } }); } }); } else { queue.fail(); } } // @todo: Оло-ло, нужно рефакторить это место if( file.width ){ _transform(false, file); } else { api.getInfo(file, _transform); } }; // @const api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){ api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){ Image[x+'_'+y] = i*3 + j; Image[y+'_'+x] = i*3 + j; }); }); /** * Trabsform element to canvas * * @param {Image|HTMLVideoElement} el * @returns {Canvas} */ Image.toCanvas = function(el){ var canvas = document.createElement('canvas'); canvas.width = el.videoWidth || el.width; canvas.height = el.videoHeight || el.height; canvas.getContext('2d').drawImage(el, 0, 0); return canvas; }; /** * Create image from DataURL * @param {String} dataURL * @param {Object} size * @param {Function} callback */ Image.fromDataURL = function (dataURL, size, callback){ var img = api.newImage(dataURL); api.extend(img, size); callback(img); }; /** * Apply filter (caman.js) * * @param {Canvas|Image} canvas * @param {String|Function} filter * @param {Function} doneFn */ Image.applyFilter = function (canvas, filter, doneFn){ if( typeof filter == 'function' ){ filter(canvas, doneFn); } else if( window.Caman ){ // http://camanjs.com/guides/ window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){ if( typeof filter == 'string' ){ this[filter](); } else { api.each(filter, function (val, method){ this[method](val); }, this); } this.render(doneFn); }); } }; /** * For load-image-ios.js */ api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){ try { return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); } catch (ex) { api.log('renderImageToCanvas failed'); throw ex; } }; // @export api.support.canvas = api.support.transform = support; api.Image = Image; })(FileAPI, document); /* * JavaScript Load Image iOS scaling fixes 1.0.3 * https://github.com/blueimp/JavaScript-Load-Image * * Copyright 2013, Sebastian Tschan * https://blueimp.net * * iOS image scaling fixes based on * https://github.com/stomita/ios-imagefile-megapixel * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ /*jslint nomen: true, bitwise: true */ /*global FileAPI, window, document */ (function (factory) { 'use strict'; factory(FileAPI); }(function (loadImage) { 'use strict'; // Only apply fixes on the iOS platform: if (!window.navigator || !window.navigator.platform || !(/iP(hone|od|ad)/).test(window.navigator.platform)) { return; } var originalRenderMethod = loadImage.renderImageToCanvas; // Detects subsampling in JPEG images: loadImage.detectSubsampling = function (img) { var canvas, context; if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images canvas = document.createElement('canvas'); canvas.width = canvas.height = 1; context = canvas.getContext('2d'); context.drawImage(img, -img.width + 1, 0); // subsampled image becomes half smaller in rendering size. // check alpha channel value to confirm image is covering edge pixel or not. // if alpha value is 0 image is not covering, hence subsampled. return context.getImageData(0, 0, 1, 1).data[3] === 0; } return false; }; // Detects vertical squash in JPEG images: loadImage.detectVerticalSquash = function (img, subsampled) { var naturalHeight = img.naturalHeight || img.height, canvas = document.createElement('canvas'), context = canvas.getContext('2d'), data, sy, ey, py, alpha; if (subsampled) { naturalHeight /= 2; } canvas.width = 1; canvas.height = naturalHeight; context.drawImage(img, 0, 0); data = context.getImageData(0, 0, 1, naturalHeight).data; // search image edge pixel position in case it is squashed vertically: sy = 0; ey = naturalHeight; py = naturalHeight; while (py > sy) { alpha = data[(py - 1) * 4 + 3]; if (alpha === 0) { ey = py; } else { sy = py; } py = (ey + sy) >> 1; } return (py / naturalHeight) || 1; }; // Renders image to canvas while working around iOS image scaling bugs: // https://github.com/blueimp/JavaScript-Load-Image/issues/13 loadImage.renderImageToCanvas = function ( canvas, img, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight ) { if (img._type === 'image/jpeg') { var context = canvas.getContext('2d'), tmpCanvas = document.createElement('canvas'), tileSize = 1024, tmpContext = tmpCanvas.getContext('2d'), subsampled, vertSquashRatio, tileX, tileY; tmpCanvas.width = tileSize; tmpCanvas.height = tileSize; context.save(); subsampled = loadImage.detectSubsampling(img); if (subsampled) { sourceX /= 2; sourceY /= 2; sourceWidth /= 2; sourceHeight /= 2; } vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled); if (subsampled || vertSquashRatio !== 1) { sourceY *= vertSquashRatio; destWidth = Math.ceil(tileSize * destWidth / sourceWidth); destHeight = Math.ceil( tileSize * destHeight / sourceHeight / vertSquashRatio ); destY = 0; tileY = 0; while (tileY < sourceHeight) { destX = 0; tileX = 0; while (tileX < sourceWidth) { tmpContext.clearRect(0, 0, tileSize, tileSize); tmpContext.drawImage( img, sourceX, sourceY, sourceWidth, sourceHeight, -tileX, -tileY, sourceWidth, sourceHeight ); context.drawImage( tmpCanvas, 0, 0, tileSize, tileSize, destX, destY, destWidth, destHeight ); tileX += tileSize; destX += destWidth; } tileY += tileSize; destY += destHeight; } context.restore(); return canvas; } } return originalRenderMethod( canvas, img, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight ); }; })); /*global window, FileAPI */ (function (api, window){ "use strict"; var document = window.document , FormData = window.FormData , Form = function (){ this.items = []; } , encodeURIComponent = window.encodeURIComponent ; Form.prototype = { append: function (name, blob, file, type){ this.items.push({ name: name , blob: blob && blob.blob || (blob == void 0 ? '' : blob) , file: blob && (file || blob.name) , type: blob && (type || blob.type) }); }, each: function (fn){ var i = 0, n = this.items.length; for( ; i < n; i++ ){ fn.call(this, this.items[i]); } }, toData: function (fn, options){ // allow chunked transfer if we have only one file to send // flag is used below and in XHR._send options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1; if( !api.support.html5 ){ api.log('FileAPI.Form.toHtmlData'); this.toHtmlData(fn); } else if( !api.formData || this.multipart || !FormData ){ api.log('FileAPI.Form.toMultipartData'); this.toMultipartData(fn); } else if( options._chunked ){ api.log('FileAPI.Form.toPlainData'); this.toPlainData(fn); } else { api.log('FileAPI.Form.toFormData'); this.toFormData(fn); } }, _to: function (data, complete, next, arg){ var queue = api.queue(function (){ complete(data); }); this.each(function (file){ next(file, data, queue, arg); }); queue.check(); }, toHtmlData: function (fn){ this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){ var blob = file.blob, hidden; if( file.file ){ api.reset(blob, true); // set new name blob.name = file.name; blob.disabled = false; data.appendChild(blob); } else { hidden = document.createElement('input'); hidden.name = file.name; hidden.type = 'hidden'; hidden.value = blob; data.appendChild(hidden); } }); }, toPlainData: function (fn){ this._to({}, fn, function (file, data, queue){ if( file.file ){ data.type = file.file; } if( file.blob.toBlob ){ // canvas queue.inc(); _convertFile(file, function (file, blob){ data.name = file.name; data.file = blob; data.size = blob.length; data.type = file.type; queue.next(); }); } else if( file.file ){ // file data.name = file.blob.name; data.file = file.blob; data.size = file.blob.size; data.type = file.type; } else { // additional data if( !data.params ){ data.params = []; } data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob)); } data.start = -1; data.end = data.file && data.file.FileAPIReadPosition || -1; data.retry = 0; }); }, toFormData: function (fn){ this._to(new FormData, fn, function (file, data, queue){ if( file.blob && file.blob.toBlob ){ queue.inc(); _convertFile(file, function (file, blob){ data.append(file.name, blob, file.file); queue.next(); }); } else if( file.file ){ data.append(file.name, file.blob, file.file); } else { data.append(file.name, file.blob); } if( file.file ){ data.append('_'+file.name, file.file); } }); }, toMultipartData: function (fn){ this._to([], fn, function (file, data, queue, boundary){ queue.inc(); _convertFile(file, function (file, blob){ data.push( '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '') + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '') + '\r\n' + '\r\n'+ (file.file ? blob : encodeURIComponent(blob)) + '\r\n') ); queue.next(); }, true); }, api.expando); } }; function _convertFile(file, fn, useBinaryString){ var blob = file.blob, filename = file.file; if( filename ){ if( !blob.toDataURL ){ // The Blob is not an image. api.readAsBinaryString(blob, function (evt){ if( evt.type == 'load' ){ fn(file, evt.result); } }); return; } var mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' } , type = mime[file.type] ? file.type : 'image/png' , ext = mime[type] || '.png' , quality = blob.quality || 1 ; if( !filename.match(new RegExp(ext+'$', 'i')) ){ // Does not change the current extension, but add a new one. filename += ext.replace('?', ''); } file.file = filename; file.type = type; if( !useBinaryString && blob.toBlob ){ blob.toBlob(function (blob){ fn(file, blob); }, type, quality); } else { fn(file, api.toBinaryString(blob.toDataURL(type, quality))); } } else { fn(file, blob); } } // @export api.Form = Form; })(FileAPI, window); /*global window, FileAPI, Uint8Array */ (function (window, api){ "use strict"; var noop = function (){} , document = window.document , XHR = function (options){ this.uid = api.uid(); this.xhr = { abort: noop , getResponseHeader: noop , getAllResponseHeaders: noop }; this.options = options; }, _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 } ; XHR.prototype = { status: 0, statusText: '', constructor: XHR, getResponseHeader: function (name){ return this.xhr.getResponseHeader(name); }, getAllResponseHeaders: function (){ return this.xhr.getAllResponseHeaders() || {}; }, end: function (status, statusText){ var _this = this, options = _this.options; _this.end = _this.abort = noop; _this.status = status; if( statusText ){ _this.statusText = statusText; } api.log('xhr.end:', status, statusText); options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this); if( _this.xhr && _this.xhr.node ){ setTimeout(function (){ var node = _this.xhr.node; try { node.parentNode.removeChild(node); } catch (e){} try { delete window[_this.uid]; } catch (e){} window[_this.uid] = _this.xhr.node = null; }, 9); } }, abort: function (){ this.end(0, 'abort'); if( this.xhr ){ this.xhr.aborted = true; this.xhr.abort(); } }, send: function (FormData){ var _this = this, options = this.options; FormData.toData(function (data){ // Start uploading options.upload(options, _this); _this._send.call(_this, options, data); }, options); }, _send: function (options, data){ var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url; api.log('XHR._send:', data); if( !options.cache ){ // No cache url += (~url.indexOf('?') ? '&' : '?') + api.uid(); } if( data.nodeName ){ var jsonp = options.jsonp; // prepare callback in GET url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid); // legacy options.upload(options, _this); var onPostMessage = function (evt){ if( ~url.indexOf(evt.origin) ){ try { var result = api.parseJSON(evt.data); if( result.id == uid ){ complete(result.status, result.statusText, result.response); } } catch( err ){ complete(0, err.message); } } }, // jsonp-callack complete = window[uid] = function (status, statusText, response){ _this.readyState = 4; _this.responseText = response; _this.end(status, statusText); api.event.off(window, 'message', onPostMessage); window[uid] = xhr = transport = window[onloadFuncName] = null; } ; _this.xhr.abort = function (){ try { if( transport.stop ){ transport.stop(); } else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); } else { transport.contentWindow.document.execCommand('Stop'); } } catch (er) {} complete(0, "abort"); }; api.event.on(window, 'message', onPostMessage); window[onloadFuncName] = function (){ try { var win = transport.contentWindow , doc = win.document , result = win.result || api.parseJSON(doc.body.innerHTML) ; complete(result.status, result.statusText, result.response); } catch (e){ api.log('[transport.onload]', e); } }; xhr = document.createElement('div'); xhr.innerHTML = '
' + '' + (jsonp && (options.url.indexOf('=?') < 0) ? '' : '') + '
' ; // get form-data & transport var form = xhr.getElementsByTagName('form')[0] , transport = xhr.getElementsByTagName('iframe')[0] ; form.appendChild(data); api.log(form.parentNode.innerHTML); // append to DOM document.body.appendChild(xhr); // keep a reference to node-transport _this.xhr.node = xhr; // send _this.readyState = 2; // loaded form.submit(); form = null; } else { // Clean url url = url.replace(/([a-z]+)=(\?)&?/i, ''); // html5 if (this.xhr && this.xhr.aborted) { api.log("Error: already aborted"); return; } xhr = _this.xhr = api.getXHR(); if (data.params) { url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&"); } xhr.open('POST', url, true); if( api.withCredentials ){ xhr.withCredentials = "true"; } if( !options.headers || !options.headers['X-Requested-With'] ){ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); } api.each(options.headers, function (val, key){ xhr.setRequestHeader(key, val); }); if ( options._chunked ) { // chunked upload if( xhr.upload ){ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ if (!data.retry) { // show progress only for correct chunk uploads options.progress({ type: evt.type , total: data.size , loaded: data.start + evt.loaded , totalSize: data.size }, _this, options); } }, 100), false); } xhr.onreadystatechange = function (){ var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10); _this.status = xhr.status; _this.statusText = xhr.statusText; _this.readyState = xhr.readyState; if( xhr.readyState == 4 ){ try { for( var k in _xhrResponsePostfix ){ _this['response'+k] = xhr['response'+k]; } }catch(_){} xhr.onreadystatechange = null; if (!xhr.status || xhr.status - 201 > 0) { api.log("Error: " + xhr.status); // some kind of error // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action // up - server error if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) { // let's try again the same chunk // only applicable for recoverable error codes 500 && 416 var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout; // inform about recoverable problems options.pause(data.file, options); // smart restart if server reports about the last known byte api.log("X-Last-Known-Byte: " + lkb); if (lkb) { data.end = lkb; } else { data.end = data.start - 1; if (416 == xhr.status) { data.end = data.end - options.chunkSize; } } setTimeout(function () { _this._send(options, data); }, delay); } else { // no mo retries _this.end(xhr.status); } } else { // success data.retry = 0; if (data.end == data.size - 1) { // finished _this.end(xhr.status); } else { // next chunk // shift position if server reports about the last known byte api.log("X-Last-Known-Byte: " + lkb); if (lkb) { data.end = lkb; } data.file.FileAPIReadPosition = data.end; setTimeout(function () { _this._send(options, data); }, 0); } } xhr = null; } }; data.start = data.end + 1; data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start); // Retrieve a slice of file var file = data.file , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1) ; if( data.size && !slice.size ){ setTimeout(function (){ _this.end(-1); }); } else { xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size); xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name)); xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream"); xhr.send(slice); } file = slice = null; } else { // single piece upload if( xhr.upload ){ // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ options.progress(evt, _this, options); }, 100), false); } xhr.onreadystatechange = function (){ _this.status = xhr.status; _this.statusText = xhr.statusText; _this.readyState = xhr.readyState; if( xhr.readyState == 4 ){ for( var k in _xhrResponsePostfix ){ _this['response'+k] = xhr['response'+k]; } xhr.onreadystatechange = null; if (!xhr.status || xhr.status > 201) { api.log("Error: " + xhr.status); if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) { options.retry = (options.retry || 0) + 1; var delay = api.networkDownRetryTimeout; // inform about recoverable problems options.pause(options.file, options); setTimeout(function () { _this._send(options, data); }, delay); } else { //success _this.end(xhr.status); } } else { //success _this.end(xhr.status); } xhr = null; } }; if( api.isArray(data) ){ // multipart xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); var rawData = data.join('') +'--_'+ api.expando +'--'; /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ if( xhr.sendAsBinary ){ xhr.sendAsBinary(rawData); } else { var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; }); xhr.send(new Uint8Array(bytes).buffer); } } else { // FormData xhr.send(data); } } } } }; // @export api.XHR = XHR; })(window, FileAPI); /** * @class FileAPI.Camera * @author RubaXa * @support Chrome 21+, FF 18+, Opera 12+ */ /*global window, FileAPI, jQuery */ /** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */ (function (window, api){ "use strict"; var URL = window.URL || window.webkitURL, document = window.document, navigator = window.navigator, getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia, html5 = !!getMedia ; // Support "media" api.support.media = html5; var Camera = function (video){ this.video = video; }; Camera.prototype = { isActive: function (){ return !!this._active; }, /** * Start camera streaming * @param {Function} callback */ start: function (callback){ var _this = this , video = _this.video , _successId , _failId , _complete = function (err){ _this._active = !err; clearTimeout(_failId); clearTimeout(_successId); // api.event.off(video, 'loadedmetadata', _complete); callback && callback(err, _this); } ; getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){ // Success _this.stream = stream; // api.event.on(video, 'loadedmetadata', function (){ // _complete(null); // }); // Set camera stream video.src = URL.createObjectURL(stream); // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia. // See crbug.com/110938. _successId = setInterval(function (){ if( _detectVideoSignal(video) ){ _complete(null); } }, 1000); _failId = setTimeout(function (){ _complete('timeout'); }, 5000); // Go-go-go! video.play(); }, _complete/*error*/); }, /** * Stop camera streaming */ stop: function (){ try { this._active = false; this.video.pause(); this.stream.stop(); } catch( err ){ } }, /** * Create screenshot * @return {FileAPI.Camera.Shot} */ shot: function (){ return new Shot(this.video); } }; /** * Get camera element from container * * @static * @param {HTMLElement} el * @return {Camera} */ Camera.get = function (el){ return new Camera(el.firstChild); }; /** * Publish camera element into container * * @static * @param {HTMLElement} el * @param {Object} options * @param {Function} [callback] */ Camera.publish = function (el, options, callback){ if( typeof options == 'function' ){ callback = options; options = {}; } // Dimensions of "camera" options = api.extend({}, { width: '100%' , height: '100%' , start: true }, options); if( el.jquery ){ // Extract first element, from jQuery collection el = el[0]; } var doneFn = function (err){ if( err ){ callback(err); } else { // Get camera var cam = Camera.get(el); if( options.start ){ cam.start(callback); } else { callback(null, cam); } } }; el.style.width = _px(options.width); el.style.height = _px(options.height); if( api.html5 && html5 ){ // Create video element var video = document.createElement('video'); // Set dimensions video.style.width = _px(options.width); video.style.height = _px(options.height); // Clean container if( window.jQuery ){ jQuery(el).empty(); } else { el.innerHTML = ''; } // Add "camera" to container el.appendChild(video); // end doneFn(); } else { Camera.fallback(el, options, doneFn); } }; Camera.fallback = function (el, options, callback){ callback('not_support_camera'); }; /** * @class FileAPI.Camera.Shot */ var Shot = function (video){ var canvas = video.nodeName ? api.Image.toCanvas(video) : video; var shot = api.Image(canvas); shot.type = 'image/png'; shot.width = canvas.width; shot.height = canvas.height; shot.size = canvas.width * canvas.height * 4; return shot; }; /** * Add "px" postfix, if value is a number * * @private * @param {*} val * @return {String} */ function _px(val){ return val >= 0 ? val + 'px' : val; } /** * @private * @param {HTMLVideoElement} video * @return {Boolean} */ function _detectVideoSignal(video){ var canvas = document.createElement('canvas'), ctx, res = false; try { ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, 1, 1); res = ctx.getImageData(0, 0, 1, 1).data[4] != 255; } catch( e ){} return res; } // @export Camera.Shot = Shot; api.Camera = Camera; })(window, FileAPI); /** * FileAPI fallback to Flash * * @flash-developer "Vladimir Demidov" */ /*global window, ActiveXObject, FileAPI */ (function (window, jQuery, api) { "use strict"; var document = window.document , location = window.location , navigator = window.navigator , _each = api.each ; api.support.flash = (function (){ var mime = navigator.mimeTypes, has = false; if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){ has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin); } else { try { has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash')); } catch(er){ api.log('Flash -- does not supported.'); } } if( has && /^file:/i.test(location) ){ api.log('[warn] Flash does not work on `file:` protocol.'); } return has; })(); api.support.flash && (0 || !api.html5 || !api.support.html5 || (api.cors && !api.support.cors) || (api.media && !api.support.media) ) && (function (){ var _attr = api.uid() , _retry = 0 , _files = {} , _rhttp = /^https?:/i , flash = { _fn: {}, /** * Publish flash-object * * @param {HTMLElement} el * @param {String} id * @param {Object} [opts] */ publish: function (el, id, opts){ opts = opts || {}; el.innerHTML = _makeFlashHTML({ id: id , src: _getUrl(api.flashUrl, 'r=' + api.version) // , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1') , wmode: opts.camera ? '' : 'transparent' , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent') + '&flashId='+ id + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : '')) + '&timeout='+api.flashAbortTimeout + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '') + '&debug='+(api.debug?"1":"") }, opts); }, /** * Initialization & preload flash object */ init: function (){ var child = document.body && document.body.firstChild; if( child ){ do { if( child.nodeType == 1 ){ api.log('FlashAPI.state: awaiting'); var dummy = document.createElement('div'); dummy.id = '_' + _attr; _css(dummy, { top: 1 , right: 1 , width: 5 , height: 5 , position: 'absolute' , zIndex: 1e6+'' // set max zIndex }); child.parentNode.insertBefore(dummy, child); flash.publish(dummy, _attr); return; } } while( child = child.nextSibling ); } if( _retry < 10 ){ setTimeout(flash.init, ++_retry*50); } }, ready: function (){ api.log('FlashAPI.state: ready'); flash.ready = api.F; flash.isReady = true; flash.patch(); flash.patchCamera && flash.patchCamera(); api.event.on(document, 'mouseover', flash.mouseover); api.event.on(document, 'click', function (evt){ if( flash.mouseover(evt) ){ evt.preventDefault ? evt.preventDefault() : (evt.returnValue = true) ; } }); }, getEl: function (){ return document.getElementById('_'+_attr); }, getWrapper: function (node){ do { if( /js-fileapi-wrapper/.test(node.className) ){ return node; } } while( (node = node.parentNode) && (node !== document.body) ); }, disableMouseover: false, mouseover: function (evt){ if (!flash.disableMouseover) { var target = api.event.fix(evt).target; if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){ var state = target.getAttribute(_attr) , wrapper = flash.getWrapper(target) ; if( api.multiFlash ){ // check state: // i — published // i — initialization // r — ready if( state == 'i' || state == 'r' ){ // publish fail return false; } else if( state != 'p' ){ // set "init" state target.setAttribute(_attr, 'i'); var dummy = document.createElement('div'); if( !wrapper ){ api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found'); return; } _css(dummy, { top: 0 , left: 0 , width: target.offsetWidth , height: target.offsetHeight , zIndex: 1e6+'' // set max zIndex , position: 'absolute' }); wrapper.appendChild(dummy); flash.publish(dummy, api.uid()); // set "publish" state target.setAttribute(_attr, 'p'); } return true; } else if( wrapper ){ // Use one flash element var box = _getDimensions(wrapper); _css(flash.getEl(), box); // Set current input flash.curInp = target; } } else if( !/object|embed/i.test(target.nodeName) ){ _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 }); } } }, onEvent: function (evt){ var type = evt.type; if( type == 'ready' ){ try { // set "ready" state flash.getInput(evt.flashId).setAttribute(_attr, 'r'); } catch (e){ } flash.ready(); setTimeout(function (){ flash.mouseenter(evt); }, 50); return true; } else if( type === 'ping' ){ api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error); } else if( type === 'log' ){ api.log('(flash -> js).log:', evt.target); } else if( type in flash ){ setTimeout(function (){ api.log('FlashAPI.event.'+evt.type+':', evt); flash[type](evt); }, 1); } }, mouseDown: function(evt) { flash.disableMouseover = true; }, cancel: function(evt) { flash.disableMouseover = false; }, mouseenter: function (evt){ var node = flash.getInput(evt.flashId); if( node ){ // Set multiple mode flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null); // Set files filter var accept = [], exts = {}; _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){ api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){ exts[ext] = 1; }); }); _each(exts, function (i, ext){ accept.push( ext ); }); flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*'); } }, get: function (id){ return document[id] || window[id] || document.embeds[id]; }, getInput: function (id){ if( api.multiFlash ){ try { var node = flash.getWrapper(flash.get(id)); if( node ){ return node.getElementsByTagName('input')[0]; } } catch (e){ api.log('[err] Can not find "input" by flashId:', id, e); } } else { return flash.curInp; } }, select: function (evt){ try { var inp = flash.getInput(evt.flashId) , uid = api.uid(inp) , files = evt.target.files , event ; _each(files, function (file){ api.checkFileObj(file); }); _files[uid] = files; if( document.createEvent ){ event = document.createEvent('Event'); event.files = files; event.initEvent('change', true, true); inp.dispatchEvent(event); } else if( jQuery ){ jQuery(inp).trigger({ type: 'change', files: files }); } else { event = document.createEventObject(); event.files = files; inp.fireEvent('onchange', event); } } finally { flash.disableMouseover = false; } }, interval: null, cmd: function (id, name, data, last) { if (flash.uploadInProgress && flash.readInProgress) { setTimeout(function() { flash.cmd(id, name, data, last); }, 100); } else { this.cmdFn(id, name, data, last); } }, cmdFn: function(id, name, data, last) { try { api.log('(js -> flash).'+name+':', data); return flash.get(id.flashId || id).cmd(name, data); } catch (e){ api.log('(js -> flash).onError:', e); if( !last ){ // try again setTimeout(function (){ flash.cmd(id, name, data, true); }, 50); } } }, patch: function (){ api.flashEngine = true; // FileAPI _inherit(api, { readAsDataURL: function (file, callback){ if( _isHtmlFile(file) ){ this.parent.apply(this, arguments); } else { api.log('FlashAPI.readAsBase64'); flash.readInProgress = true; flash.cmd(file, 'readAsBase64', { id: file.id, callback: _wrap(function _(err, base64){ flash.readInProgress = false; _unwrap(_); api.log('FlashAPI.readAsBase64:', err); callback({ type: err ? 'error' : 'load' , error: err , result: 'data:'+ file.type +';base64,'+ base64 }); }) }); } }, readAsText: function (file, encoding, callback){ if( callback ){ api.log('[warn] FlashAPI.readAsText not supported `encoding` param'); } else { callback = encoding; } api.readAsDataURL(file, function (evt){ if( evt.type == 'load' ){ try { evt.result = window.atob(evt.result.split(';base64,')[1]); } catch( err ){ evt.type = 'error'; evt.error = err.toString(); } } callback(evt); }); }, getFiles: function (input, filter, callback){ if( callback ){ api.filterFiles(api.getFiles(input), filter, callback); return null; } var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)]; if( !files ){ // Файлов нету, вызываем родительский метод return this.parent.apply(this, arguments); } if( filter ){ filter = api.getFilesFilter(filter); files = api.filter(files, function (file){ return filter.test(file.name); }); } return files; }, getInfo: function (file, fn){ if( _isHtmlFile(file) ){ this.parent.apply(this, arguments); } else if( file.isShot ){ fn(null, file.info = { width: file.width, height: file.height }); } else { if( !file.__info ){ var defer = file.__info = api.defer(); // flash.cmd(file, 'getFileInfo', { // id: file.id // , callback: _wrap(function _(err, info){ // _unwrap(_); // defer.resolve(err, file.info = info); // }) // }); defer.resolve(null, file.info = null); } file.__info.then(fn); } } }); // FileAPI.Image api.support.transform = true; api.Image && _inherit(api.Image.prototype, { get: function (fn, scaleMode){ this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit return this.parent(fn); }, _load: function (file, fn){ api.log('FlashAPI.Image._load:', file); if( _isHtmlFile(file) ){ this.parent.apply(this, arguments); } else { var _this = this; api.getInfo(file, function (err){ fn.call(_this, err, file); }); } }, _apply: function (file, fn){ api.log('FlashAPI.Image._apply:', file); if( _isHtmlFile(file) ){ this.parent.apply(this, arguments); } else { var m = this.getMatrix(file.info), doneFn = fn; flash.cmd(file, 'imageTransform', { id: file.id , matrix: m , callback: _wrap(function _(err, base64){ api.log('FlashAPI.Image._apply.callback:', err); _unwrap(_); if( err ){ doneFn(err); } else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){ _makeFlashImage({ width: (m.deg % 180) ? m.dh : m.dw , height: (m.deg % 180) ? m.dw : m.dh , scale: m.scaleMode }, base64, doneFn); } else { if( m.filter ){ doneFn = function (err, img){ if( err ){ fn(err); } else { api.Image.applyFilter(img, m.filter, function (){ fn(err, this.canvas); }); } }; } api.newImage('data:'+ file.type +';base64,'+ base64, doneFn); } }) }); } }, toData: function (fn){ var file = this.file , info = file.info , matrix = this.getMatrix(info) ; api.log('FlashAPI.Image.toData'); if( _isHtmlFile(file) ){ this.parent.apply(this, arguments); } else { if( matrix.deg == 'auto' ){ matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0; } fn.call(this, !file.info, { id: file.id , flashId: file.flashId , name: file.name , type: file.type , matrix: matrix }); } } }); api.Image && _inherit(api.Image, { fromDataURL: function (dataURL, size, callback){ if( !api.support.dataURI || dataURL.length > 3e4 ){ _makeFlashImage( api.extend({ scale: 'exactFit' }, size) , dataURL.replace(/^data:[^,]+,/, '') , function (err, el){ callback(el); } ); } else { this.parent(dataURL, size, callback); } } }); // FileAPI.Form _inherit(api.Form.prototype, { toData: function (fn){ var items = this.items, i = items.length; for( ; i--; ){ if( items[i].file && _isHtmlFile(items[i].blob) ){ return this.parent.apply(this, arguments); } } api.log('FlashAPI.Form.toData'); fn(items); } }); // FileAPI.XHR _inherit(api.XHR.prototype, { _send: function (options, formData){ if( formData.nodeName || formData.append && api.support.html5 || api.isArray(formData) && (typeof formData[0] === 'string') ){ // HTML5, Multipart or IFrame return this.parent.apply(this, arguments); } var data = {} , files = {} , _this = this , flashId , fileId ; _each(formData, function (item){ if( item.file ){ files[item.name] = item = _getFileDescr(item.blob); fileId = item.id; flashId = item.flashId; } else { data[item.name] = item.blob; } }); if( !fileId ){ flashId = _attr; } if( !flashId ){ api.log('[err] FlashAPI._send: flashId -- undefined'); return this.parent.apply(this, arguments); } else { api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId); } _this.xhr = { headers: {}, abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); }, getResponseHeader: function (name){ return this.headers[name]; }, getAllResponseHeaders: function (){ return this.headers; } }; var queue = api.queue(function (){ flash.uploadInProgress = true; flash.cmd(flashId, 'upload', { url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, '')) , data: data , files: fileId ? files : null , headers: options.headers || {} , callback: _wrap(function upload(evt){ var type = evt.type, result = evt.result; api.log('FlashAPI.upload.'+type); if( type == 'progress' ){ evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme evt.lengthComputable = true; options.progress(evt); } else if( type == 'complete' ){ flash.uploadInProgress = false; _unwrap(upload); if( typeof result == 'string' ){ _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%"); } _this.end(evt.status || 200); } else if( type == 'abort' || type == 'error' ){ flash.uploadInProgress = false; _this.end(evt.status || 0, evt.message); _unwrap(upload); } }) }); }); // #2174: FileReference.load() call while FileReference.upload() or vice versa _each(files, function (file){ queue.inc(); api.getInfo(file, queue.next); }); queue.check(); } }); } } ; function _makeFlashHTML(opts){ return ('' + '' + '' + '' + '' + '' + '' + '' + '' + '').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; }) ; } function _css(el, css){ if( el && el.style ){ var key, val; for( key in css ){ val = css[key]; if( typeof val == 'number' ){ val += 'px'; } try { el.style[key] = val; } catch (e) {} } } } function _inherit(obj, methods){ _each(methods, function (fn, name){ var prev = obj[name]; obj[name] = function (){ this.parent = prev; return fn.apply(this, arguments); }; }); } function _isHtmlFile(file){ return file && !file.flashId; } function _wrap(fn){ var id = fn.wid = api.uid(); flash._fn[id] = fn; return 'FileAPI.Flash._fn.'+id; } function _unwrap(fn){ try { flash._fn[fn.wid] = null; delete flash._fn[fn.wid]; } catch(e){} } function _getUrl(url, params){ if( !_rhttp.test(url) ){ if( /^\.\//.test(url) || '/' != url.charAt(0) ){ var path = location.pathname; path = path.substr(0, path.lastIndexOf('/')); url = (path +'/'+ url).replace('/./', '/'); } if( '//' != url.substr(0, 2) ){ url = '//' + location.host + url; } if( !_rhttp.test(url) ){ url = location.protocol + url; } } if( params ){ url += (/\?/.test(url) ? '&' : '?') + params; } return url; } function _makeFlashImage(opts, base64, fn){ var key , flashId = api.uid() , el = document.createElement('div') , attempts = 10 ; for( key in opts ){ el.setAttribute(key, opts[key]); el[key] = opts[key]; } _css(el, opts); opts.width = '100%'; opts.height = '100%'; el.innerHTML = _makeFlashHTML(api.extend({ id: flashId , src: _getUrl(api.flashImageUrl, 'r='+ api.uid()) , wmode: 'opaque' , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){ _unwrap(_); if( --attempts > 0 ){ _setImage(); } return true; }) }, opts)); function _setImage(){ try { // Get flash-object by id var img = flash.get(flashId); img.setImage(base64); } catch (e){ api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e); } } fn(false, el); el = null; } function _getFileDescr(file){ return { id: file.id , name: file.name , matrix: file.matrix , flashId: file.flashId }; } function _getDimensions(el){ var box = el.getBoundingClientRect() , body = document.body , docEl = (el && el.ownerDocument).documentElement ; function getOffset(obj) { var left, top; left = top = 0; if (obj.offsetParent) { do { left += obj.offsetLeft; top += obj.offsetTop; } while (obj = obj.offsetParent); } return { left : left, top : top }; }; return { top: getOffset(el).top , left: getOffset(el).left , width: el.offsetWidth , height: el.offsetHeight }; } // @export api.Flash = flash; // Check dataURI support api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){ api.support.dataURI = !(img.width != 1 || img.height != 1); flash.init(); }); })(); })(window, window.jQuery, FileAPI); /** * FileAPI fallback to Flash * * @flash-developer "Vladimir Demidov" */ /*global window, FileAPI */ (function (window, jQuery, api) { "use strict"; var _each = api.each, _cameraQueue = []; if (api.support.flash && (api.media && !api.support.media)) { (function () { function _wrap(fn) { var id = fn.wid = api.uid(); api.Flash._fn[id] = fn; return 'FileAPI.Flash._fn.' + id; } function _unwrap(fn) { try { api.Flash._fn[fn.wid] = null; delete api.Flash._fn[fn.wid]; } catch (e) { } } var flash = api.Flash; api.extend(api.Flash, { patchCamera: function () { api.Camera.fallback = function (el, options, callback) { var camId = api.uid(); api.log('FlashAPI.Camera.publish: ' + camId); flash.publish(el, camId, api.extend(options, { camera: true, onEvent: _wrap(function _(evt) { if (evt.type === 'camera') { _unwrap(_); if (evt.error) { api.log('FlashAPI.Camera.publish.error: ' + evt.error); callback(evt.error); } else { api.log('FlashAPI.Camera.publish.success: ' + camId); callback(null); } } }) })); }; // Run _each(_cameraQueue, function (args) { api.Camera.fallback.apply(api.Camera, args); }); _cameraQueue = []; // FileAPI.Camera:proto api.extend(api.Camera.prototype, { _id: function () { return this.video.id; }, start: function (callback) { var _this = this; flash.cmd(this._id(), 'camera.on', { callback: _wrap(function _(evt) { _unwrap(_); if (evt.error) { api.log('FlashAPI.camera.on.error: ' + evt.error); callback(evt.error, _this); } else { api.log('FlashAPI.camera.on.success: ' + _this._id()); _this._active = true; callback(null, _this); } }) }); }, stop: function () { this._active = false; flash.cmd(this._id(), 'camera.off'); }, shot: function () { api.log('FlashAPI.Camera.shot:', this._id()); var shot = api.Flash.cmd(this._id(), 'shot', {}); shot.type = 'image/png'; shot.flashId = this._id(); shot.isShot = true; return new api.Camera.Shot(shot); } }); } }); api.Camera.fallback = function () { _cameraQueue.push(arguments); }; }()); } }(window, window.jQuery, FileAPI)); if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); } ================================================ FILE: demo/src/main/webapp/js/ng-file-upload-all.js ================================================ /**! * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort, * progress, resize, thumbnail, preview, validation and CORS * FileAPI Flash shim for old browsers not supporting FormData * @author Danial * @version 12.2.13 */ (function () { /** @namespace FileAPI.noContentTimeout */ function patchXHR(fnName, newFn) { window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]); } function redefineProp(xhr, prop, fn) { try { Object.defineProperty(xhr, prop, {get: fn}); } catch (e) {/*ignore*/ } } if (!window.FileAPI) { window.FileAPI = {}; } if (!window.XMLHttpRequest) { throw 'AJAX is not supported. XMLHttpRequest is not defined.'; } FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad; if (FileAPI.shouldLoad) { var initializeUploadListener = function (xhr) { if (!xhr.__listeners) { if (!xhr.upload) xhr.upload = {}; xhr.__listeners = []; var origAddEventListener = xhr.upload.addEventListener; xhr.upload.addEventListener = function (t, fn) { xhr.__listeners[t] = fn; if (origAddEventListener) origAddEventListener.apply(this, arguments); }; } }; patchXHR('open', function (orig) { return function (m, url, b) { initializeUploadListener(this); this.__url = url; try { orig.apply(this, [m, url, b]); } catch (e) { if (e.message.indexOf('Access is denied') > -1) { this.__origError = e; orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]); } } }; }); patchXHR('getResponseHeader', function (orig) { return function (h) { return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h])); }; }); patchXHR('getAllResponseHeaders', function (orig) { return function () { return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this)); }; }); patchXHR('abort', function (orig) { return function () { return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this)); }; }); patchXHR('setRequestHeader', function (orig) { return function (header, value) { if (header === '__setXHR_') { initializeUploadListener(this); var val = value(this); // fix for angular < 1.2.0 if (val instanceof Function) { val(this); } } else { this.__requestHeaders = this.__requestHeaders || {}; this.__requestHeaders[header] = value; orig.apply(this, arguments); } }; }); patchXHR('send', function (orig) { return function () { var xhr = this; if (arguments[0] && arguments[0].__isFileAPIShim) { var formData = arguments[0]; var config = { url: xhr.__url, jsonp: false, //removes the callback form param cache: true, //removes the ?fileapiXXX in the url complete: function (err, fileApiXHR) { if (err && angular.isString(err) && err.indexOf('#2174') !== -1) { // this error seems to be fine the file is being uploaded properly. err = null; } xhr.__completed = true; if (!err && xhr.__listeners.load) xhr.__listeners.load({ type: 'load', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true }); if (!err && xhr.__listeners.loadend) xhr.__listeners.loadend({ type: 'loadend', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true }); if (err === 'abort' && xhr.__listeners.abort) xhr.__listeners.abort({ type: 'abort', loaded: xhr.__loaded, total: xhr.__total, target: xhr, lengthComputable: true }); if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () { return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status; }); if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () { return fileApiXHR.statusText; }); redefineProp(xhr, 'readyState', function () { return 4; }); if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () { return fileApiXHR.response; }); var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined); redefineProp(xhr, 'responseText', function () { return resp; }); redefineProp(xhr, 'response', function () { return resp; }); if (err) redefineProp(xhr, 'err', function () { return err; }); xhr.__fileApiXHR = fileApiXHR; if (xhr.onreadystatechange) xhr.onreadystatechange(); if (xhr.onload) xhr.onload(); }, progress: function (e) { e.target = xhr; if (xhr.__listeners.progress) xhr.__listeners.progress(e); xhr.__total = e.total; xhr.__loaded = e.loaded; if (e.total === e.loaded) { // fix flash issue that doesn't call complete if there is no response text from the server var _this = this; setTimeout(function () { if (!xhr.__completed) { xhr.getAllResponseHeaders = function () { }; _this.complete(null, {status: 204, statusText: 'No Content'}); } }, FileAPI.noContentTimeout || 10000); } }, headers: xhr.__requestHeaders }; config.data = {}; config.files = {}; for (var i = 0; i < formData.data.length; i++) { var item = formData.data[i]; if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) { config.files[item.key] = item.val; } else { config.data[item.key] = item.val; } } setTimeout(function () { if (!FileAPI.hasFlash) { throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; } xhr.__fileApiXHR = FileAPI.upload(config); }, 1); } else { if (this.__origError) { throw this.__origError; } orig.apply(xhr, arguments); } }; }); window.XMLHttpRequest.__isFileAPIShim = true; window.FormData = FormData = function () { return { append: function (key, val, name) { if (val.__isFileAPIBlobShim) { val = val.data[0]; } this.data.push({ key: key, val: val, name: name }); }, data: [], __isFileAPIShim: true }; }; window.Blob = Blob = function (b) { return { data: b, __isFileAPIBlobShim: true }; }; } })(); (function () { /** @namespace FileAPI.forceLoad */ /** @namespace window.FileAPI.jsUrl */ /** @namespace window.FileAPI.jsPath */ function isInputTypeFile(elem) { return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file'; } function hasFlash() { try { var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); if (fo) return true; } catch (e) { if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true; } return false; } function getOffset(obj) { var left = 0, top = 0; if (window.jQuery) { return jQuery(obj).offset(); } if (obj.offsetParent) { do { left += (obj.offsetLeft - obj.scrollLeft); top += (obj.offsetTop - obj.scrollTop); obj = obj.offsetParent; } while (obj); } return { left: left, top: top }; } if (FileAPI.shouldLoad) { FileAPI.hasFlash = hasFlash(); //load FileAPI if (FileAPI.forceLoad) { FileAPI.html5 = false; } if (!FileAPI.upload) { var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src; if (window.FileAPI.jsUrl) { jsUrl = window.FileAPI.jsUrl; } else if (window.FileAPI.jsPath) { basePath = window.FileAPI.jsPath; } else { for (i = 0; i < allScripts.length; i++) { src = allScripts[i].src; index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/); if (index > -1) { basePath = src.substring(0, index + 1); break; } } } if (FileAPI.staticPath == null) FileAPI.staticPath = basePath; script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js'); document.getElementsByTagName('head')[0].appendChild(script); } FileAPI.ngfFixIE = function (elem, fileElem, changeFn) { if (!hasFlash()) { throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; } var fixInputStyle = function () { var label = fileElem.parent(); if (elem.attr('disabled')) { if (label) label.removeClass('js-fileapi-wrapper'); } else { if (!fileElem.attr('__ngf_flash_')) { fileElem.unbind('change'); fileElem.unbind('click'); fileElem.bind('change', function (evt) { fileApiChangeFn.apply(this, [evt]); changeFn.apply(this, [evt]); }); fileElem.attr('__ngf_flash_', 'true'); } label.addClass('js-fileapi-wrapper'); if (!isInputTypeFile(elem)) { label.css('position', 'absolute') .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px') .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') .css('filter', 'alpha(opacity=0)').css('display', elem.css('display')) .css('overflow', 'hidden').css('z-index', '900000') .css('visibility', 'visible'); fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') .css('position', 'absolute').css('top', '0px').css('left', '0px'); } } }; elem.bind('mouseenter', fixInputStyle); var fileApiChangeFn = function (evt) { var files = FileAPI.getFiles(evt); //just a double check for #233 for (var i = 0; i < files.length; i++) { if (files[i].size === undefined) files[i].size = 0; if (files[i].name === undefined) files[i].name = 'file'; if (files[i].type === undefined) files[i].type = 'undefined'; } if (!evt.target) { evt.target = {}; } evt.target.files = files; // if evt.target.files is not writable use helper field if (evt.target.files !== files) { evt.__files_ = files; } (evt.__files_ || evt.target.files).item = function (i) { return (evt.__files_ || evt.target.files)[i] || null; }; }; }; FileAPI.disableFileInput = function (elem, disable) { if (disable) { elem.removeClass('js-fileapi-wrapper'); } else { elem.addClass('js-fileapi-wrapper'); } }; } })(); if (!window.FileReader) { window.FileReader = function () { var _this = this, loadStarted = false; this.listeners = {}; this.addEventListener = function (type, fn) { _this.listeners[type] = _this.listeners[type] || []; _this.listeners[type].push(fn); }; this.removeEventListener = function (type, fn) { if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1); }; this.dispatchEvent = function (evt) { var list = _this.listeners[evt.type]; if (list) { for (var i = 0; i < list.length; i++) { list[i].call(_this, evt); } } }; this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null; var constructEvent = function (type, evt) { var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error}; if (evt.result != null) e.target.result = evt.result; return e; }; var listener = function (evt) { if (!loadStarted) { loadStarted = true; if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt)); } var e; if (evt.type === 'load') { if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt)); e = constructEvent('load', evt); if (_this.onload) _this.onload(e); _this.dispatchEvent(e); } else if (evt.type === 'progress') { e = constructEvent('progress', evt); if (_this.onprogress) _this.onprogress(e); _this.dispatchEvent(e); } else { e = constructEvent('error', evt); if (_this.onerror) _this.onerror(e); _this.dispatchEvent(e); } }; this.readAsDataURL = function (file) { FileAPI.readAsDataURL(file, listener); }; this.readAsText = function (file) { FileAPI.readAsText(file, listener); }; }; } /**! * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, * progress, resize, thumbnail, preview, validation and CORS * @author Danial * @version 12.2.13 */ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) { return function (header, value) { if (header === '__setXHR_') { var val = value(this); // fix for angular < 1.2.0 if (val instanceof Function) { val(this); } } else { orig.apply(this, arguments); } }; })(window.XMLHttpRequest.prototype.setRequestHeader); } var ngFileUpload = angular.module('ngFileUpload', []); ngFileUpload.version = '12.2.13'; ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { var upload = this; upload.promisesCount = 0; this.isResumeSupported = function () { return window.Blob && window.Blob.prototype.slice; }; var resumeSupported = this.isResumeSupported(); function sendHttp(config) { config.method = config.method || 'POST'; config.headers = config.headers || {}; var deferred = config._deferred = config._deferred || $q.defer(); var promise = deferred.promise; function notifyProgress(e) { if (deferred.notify) { deferred.notify(e); } if (promise.progressFunc) { $timeout(function () { promise.progressFunc(e); }); } } function getNotifyEvent(n) { if (config._start != null && resumeSupported) { return { loaded: n.loaded + config._start, total: (config._file && config._file.size) || n.total, type: n.type, config: config, lengthComputable: true, target: n.target }; } else { return n; } } if (!config.disableProgress) { config.headers.__setXHR_ = function () { return function (xhr) { if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return; config.__XHR = xhr; if (config.xhrFn) config.xhrFn(xhr); xhr.upload.addEventListener('progress', function (e) { e.config = config; notifyProgress(getNotifyEvent(e)); }, false); //fix for firefox not firing upload progress end, also IE8-9 xhr.upload.addEventListener('load', function (e) { if (e.lengthComputable) { e.config = config; notifyProgress(getNotifyEvent(e)); } }, false); }; }; } function uploadWithAngular() { $http(config).then(function (r) { if (resumeSupported && config._chunkSize && !config._finished && config._file) { var fileSize = config._file && config._file.size || 0; notifyProgress({ loaded: Math.min(config._end, fileSize), total: fileSize, config: config, type: 'progress' } ); upload.upload(config, true); } else { if (config._finished) delete config._finished; deferred.resolve(r); } }, function (e) { deferred.reject(e); }, function (n) { deferred.notify(n); } ); } if (!resumeSupported) { uploadWithAngular(); } else if (config._chunkSize && config._end && !config._finished) { config._start = config._end; config._end += config._chunkSize; uploadWithAngular(); } else if (config.resumeSizeUrl) { $http.get(config.resumeSizeUrl).then(function (resp) { if (config.resumeSizeResponseReader) { config._start = config.resumeSizeResponseReader(resp.data); } else { config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString()); } if (config._chunkSize) { config._end = config._start + config._chunkSize; } uploadWithAngular(); }, function (e) { throw e; }); } else if (config.resumeSize) { config.resumeSize().then(function (size) { config._start = size; if (config._chunkSize) { config._end = config._start + config._chunkSize; } uploadWithAngular(); }, function (e) { throw e; }); } else { if (config._chunkSize) { config._start = 0; config._end = config._start + config._chunkSize; } uploadWithAngular(); } promise.success = function (fn) { promise.then(function (response) { fn(response.data, response.status, response.headers, config); }); return promise; }; promise.error = function (fn) { promise.then(null, function (response) { fn(response.data, response.status, response.headers, config); }); return promise; }; promise.progress = function (fn) { promise.progressFunc = fn; promise.then(null, null, function (n) { fn(n); }); return promise; }; promise.abort = promise.pause = function () { if (config.__XHR) { $timeout(function () { config.__XHR.abort(); }); } return promise; }; promise.xhr = function (fn) { config.xhrFn = (function (origXhrFn) { return function () { if (origXhrFn) origXhrFn.apply(promise, arguments); fn.apply(promise, arguments); }; })(config.xhrFn); return promise; }; upload.promisesCount++; if (promise['finally'] && promise['finally'] instanceof Function) { promise['finally'](function () { upload.promisesCount--; }); } return promise; } this.isUploadInProgress = function () { return upload.promisesCount > 0; }; this.rename = function (file, name) { file.ngfName = name; return file; }; this.jsonBlob = function (val) { if (val != null && !angular.isString(val)) { val = JSON.stringify(val); } var blob = new window.Blob([val], {type: 'application/json'}); blob._ngfBlob = true; return blob; }; this.json = function (val) { return angular.toJson(val); }; function copy(obj) { var clone = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = obj[key]; } } return clone; } this.isFile = function (file) { return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size)); }; this.upload = function (config, internal) { function toResumeFile(file, formData) { if (file._ngfBlob) return file; config._file = config._file || file; if (config._start != null && resumeSupported) { if (config._end && config._end >= file.size) { config._finished = true; config._end = file.size; } var slice = file.slice(config._start, config._end || file.size); slice.name = file.name; slice.ngfName = file.ngfName; if (config._chunkSize) { formData.append('_chunkSize', config._chunkSize); formData.append('_currentChunkSize', config._end - config._start); formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize)); formData.append('_totalSize', config._file.size); } return slice; } return file; } function addFieldToFormData(formData, val, key) { if (val !== undefined) { if (angular.isDate(val)) { val = val.toISOString(); } if (angular.isString(val)) { formData.append(key, val); } else if (upload.isFile(val)) { var file = toResumeFile(val, formData); var split = key.split(','); if (split[1]) { file.ngfName = split[1].replace(/^\s+|\s+$/g, ''); key = split[0]; } config._fileKey = config._fileKey || key; formData.append(key, file, file.ngfName || file.name); } else { if (angular.isObject(val)) { if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key; val.$$ngfCircularDetection = true; try { for (var k in val) { if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') { var objectKey = config.objectKey == null ? '[i]' : config.objectKey; if (val.length && parseInt(k) > -1) { objectKey = config.arrayKey == null ? objectKey : config.arrayKey; } addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k)); } } } finally { delete val.$$ngfCircularDetection; } } else { formData.append(key, val); } } } } function digestConfig() { config._chunkSize = upload.translateScalars(config.resumeChunkSize); config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; config.headers = config.headers || {}; config.headers['Content-Type'] = undefined; config.transformRequest = config.transformRequest ? (angular.isArray(config.transformRequest) ? config.transformRequest : [config.transformRequest]) : []; config.transformRequest.push(function (data) { var formData = new window.FormData(), key; data = data || config.fields || {}; if (config.file) { data.file = config.file; } for (key in data) { if (data.hasOwnProperty(key)) { var val = data[key]; if (config.formDataAppender) { config.formDataAppender(formData, key, val); } else { addFieldToFormData(formData, val, key); } } } return formData; }); } if (!internal) config = copy(config); if (!config._isDigested) { config._isDigested = true; digestConfig(); } return sendHttp(config); }; this.http = function (config) { config = copy(config); config.transformRequest = config.transformRequest || function (data) { if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) { return data; } return $http.defaults.transformRequest[0].apply(this, arguments); }; config._chunkSize = upload.translateScalars(config.resumeChunkSize); config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; return sendHttp(config); }; this.translateScalars = function (str) { if (angular.isString(str)) { if (str.search(/kb/i) === str.length - 2) { return parseFloat(str.substring(0, str.length - 2) * 1024); } else if (str.search(/mb/i) === str.length - 2) { return parseFloat(str.substring(0, str.length - 2) * 1048576); } else if (str.search(/gb/i) === str.length - 2) { return parseFloat(str.substring(0, str.length - 2) * 1073741824); } else if (str.search(/b/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1)); } else if (str.search(/s/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1)); } else if (str.search(/m/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1) * 60); } else if (str.search(/h/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1) * 3600); } } return str; }; this.urlToBlob = function(url) { var defer = $q.defer(); $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) { var arrayBufferView = new Uint8Array(resp.data); var type = resp.headers('content-type') || 'image/WebP'; var blob = new window.Blob([arrayBufferView], {type: type}); var matches = url.match(/.*\/(.+?)(\?.*)?$/); if (matches.length > 1) { blob.name = matches[1]; } defer.resolve(blob); }, function (e) { defer.reject(e); }); return defer.promise; }; this.setDefaults = function (defaults) { this.defaults = defaults || {}; }; this.defaults = {}; this.version = ngFileUpload.version; } ]); ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) { var upload = UploadExif; upload.getAttrWithDefaults = function (attr, name) { if (attr[name] != null) return attr[name]; var def = upload.defaults[name]; return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def))); }; upload.attrGetter = function (name, attr, scope, params) { var attrVal = this.getAttrWithDefaults(attr, name); if (scope) { try { if (params) { return $parse(attrVal)(scope, params); } else { return $parse(attrVal)(scope); } } catch (e) { // hangle string value without single qoute if (name.search(/min|max|pattern/i)) { return attrVal; } else { throw e; } } } else { return attrVal; } }; upload.shouldUpdateOn = function (type, attr, scope) { var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope); if (modelOptions && modelOptions.updateOn) { return modelOptions.updateOn.split(' ').indexOf(type) > -1; } return true; }; upload.emptyPromise = function () { var d = $q.defer(); var args = arguments; $timeout(function () { d.resolve.apply(d, args); }); return d.promise; }; upload.rejectPromise = function () { var d = $q.defer(); var args = arguments; $timeout(function () { d.reject.apply(d, args); }); return d.promise; }; upload.happyPromise = function (promise, data) { var d = $q.defer(); promise.then(function (result) { d.resolve(result); }, function (error) { $timeout(function () { throw error; }); d.resolve(data); }); return d.promise; }; function applyExifRotations(files, attr, scope) { var promises = [upload.emptyPromise()]; angular.forEach(files, function (f, i) { if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) { promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) { files.splice(i, 1, fixedFile); })); } }); return $q.all(promises); } function resizeFile(files, attr, scope, ngModel) { var resizeVal = upload.attrGetter('ngfResize', attr, scope); if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise(); if (resizeVal instanceof Function) { var defer = $q.defer(); return resizeVal(files).then(function (p) { resizeWithParams(p, files, attr, scope, ngModel).then(function (r) { defer.resolve(r); }, function (e) { defer.reject(e); }); }, function (e) { defer.reject(e); }); } else { return resizeWithParams(resizeVal, files, attr, scope, ngModel); } } function resizeWithParams(params, files, attr, scope, ngModel) { var promises = [upload.emptyPromise()]; function handleFile(f, i) { if (f.type.indexOf('image') === 0) { if (params.pattern && !upload.validatePattern(f, params.pattern)) return; params.resizeIf = function (width, height) { return upload.attrGetter('ngfResizeIf', attr, scope, {$width: width, $height: height, $file: f}); }; var promise = upload.resize(f, params); promises.push(promise); promise.then(function (resizedFile) { files.splice(i, 1, resizedFile); }, function (e) { f.$error = 'resize'; (f.$errorMessages = (f.$errorMessages || {})).resize = true; f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name); ngModel.$ngfValidations.push({name: 'resize', valid: false}); upload.applyModelValidation(ngModel, files); }); } } for (var i = 0; i < files.length; i++) { handleFile(files[i], i); } return $q.all(promises); } upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) { function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) { attr.$$ngfPrevValidFiles = files; attr.$$ngfPrevInvalidFiles = invalidFiles; var file = files && files.length ? files[0] : null; var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null; if (ngModel) { upload.applyModelValidation(ngModel, files); ngModel.$setViewValue(isSingleModel ? file : files); } if (fileChange) { $parse(fileChange)(scope, { $files: files, $file: file, $newFiles: newFiles, $duplicateFiles: dupFiles, $invalidFiles: invalidFiles, $invalidFile: invalidFile, $event: evt }); } var invalidModel = upload.attrGetter('ngfModelInvalid', attr); if (invalidModel) { $timeout(function () { $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles); }); } $timeout(function () { // scope apply changes }); } var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles, invalids = [], valids = []; function removeDuplicates() { function equals(f1, f2) { return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) && f1.type === f2.type; } function isInPrevFiles(f) { var j; for (j = 0; j < prevValidFiles.length; j++) { if (equals(f, prevValidFiles[j])) { return true; } } for (j = 0; j < prevInvalidFiles.length; j++) { if (equals(f, prevInvalidFiles[j])) { return true; } } return false; } if (files) { allNewFiles = []; dupFiles = []; for (var i = 0; i < files.length; i++) { if (isInPrevFiles(files[i])) { dupFiles.push(files[i]); } else { allNewFiles.push(files[i]); } } } } function toArray(v) { return angular.isArray(v) ? v : [v]; } function resizeAndUpdate() { function updateModel() { $timeout(function () { update(keep ? prevValidFiles.concat(valids) : valids, keep ? prevInvalidFiles.concat(invalids) : invalids, files, dupFiles, isSingleModel); }, options && options.debounce ? options.debounce.change || options.debounce : 0); } var resizingFiles = validateAfterResize ? allNewFiles : valids; resizeFile(resizingFiles, attr, scope, ngModel).then(function () { if (validateAfterResize) { upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope) .then(function (validationResult) { valids = validationResult.validsFiles; invalids = validationResult.invalidsFiles; updateModel(); }); } else { updateModel(); } }, function () { for (var i = 0; i < resizingFiles.length; i++) { var f = resizingFiles[i]; if (f.$error === 'resize') { var index = valids.indexOf(f); if (index > -1) { valids.splice(index, 1); invalids.push(f); } updateModel(); } } }); } prevValidFiles = attr.$$ngfPrevValidFiles || []; prevInvalidFiles = attr.$$ngfPrevInvalidFiles || []; if (ngModel && ngModel.$modelValue) { prevValidFiles = toArray(ngModel.$modelValue); } var keep = upload.attrGetter('ngfKeep', attr, scope); allNewFiles = (files || []).slice(0); if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) { removeDuplicates(attr, scope); } var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr); if (keep && !allNewFiles.length) return; upload.attrGetter('ngfBeforeModelChange', attr, scope, { $files: files, $file: files && files.length ? files[0] : null, $newFiles: allNewFiles, $duplicateFiles: dupFiles, $event: evt }); var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope); var options = upload.attrGetter('ngfModelOptions', attr, scope); upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope) .then(function (validationResult) { if (noDelay) { update(allNewFiles, [], files, dupFiles, isSingleModel); } else { if ((!options || !options.allowInvalid) && !validateAfterResize) { valids = validationResult.validFiles; invalids = validationResult.invalidFiles; } else { valids = allNewFiles; } if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) { applyExifRotations(valids, attr, scope).then(function () { resizeAndUpdate(); }); } else { resizeAndUpdate(); } } }); }; return upload; }]); ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { var generatedElems = []; function isDelayedClickSupported(ua) { // fix for android native browser < 4.4 and safari windows var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); if (m && m.length > 2) { var v = Upload.defaults.androidFixMinorVersion || 4; return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); } // safari on windows return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); } function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { /** @namespace attr.ngfSelect */ /** @namespace attr.ngfChange */ /** @namespace attr.ngModel */ /** @namespace attr.ngfModelOptions */ /** @namespace attr.ngfMultiple */ /** @namespace attr.ngfCapture */ /** @namespace attr.ngfValidate */ /** @namespace attr.ngfKeep */ var attrGetter = function (name, scope) { return upload.attrGetter(name, attr, scope); }; function isInputTypeFile() { return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; } function fileChangeAttr() { return attrGetter('ngfChange') || attrGetter('ngfSelect'); } function changeFn(evt) { if (upload.shouldUpdateOn('change', attr, scope)) { var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; /* Handle duplicate call in IE11 */ if (!fileList) return; for (var i = 0; i < fileList.length; i++) { files.push(fileList[i]); } upload.updateModel(ngModel, attr, scope, fileChangeAttr(), files.length ? files : null, evt); } } upload.registerModelChangeValidator(ngModel, attr, scope); var unwatches = []; if (attrGetter('ngfMultiple')) { unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); })); } if (attrGetter('ngfCapture')) { unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { fileElem.attr('capture', attrGetter('ngfCapture', scope)); })); } if (attrGetter('ngfAccept')) { unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { fileElem.attr('accept', attrGetter('ngfAccept', scope)); })); } unwatches.push(attr.$observe('accept', function () { fileElem.attr('accept', attrGetter('accept')); })); function bindAttrToFileInput(fileElem, label) { function updateId(val) { fileElem.attr('id', 'ngf-' + val); label.attr('id', 'ngf-label-' + val); } for (var i = 0; i < elem[0].attributes.length; i++) { var attribute = elem[0].attributes[i]; if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { if (attribute.name === 'id') { updateId(attribute.value); unwatches.push(attr.$observe('id', updateId)); } else { fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' || attribute.name === 'multiple')) ? attribute.name : attribute.value); } } } } function createFileInput() { if (isInputTypeFile()) { return elem; } var fileElem = angular.element(''); var label = angular.element(''); label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') .css('width', '0px').css('height', '0px').css('border', 'none') .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); bindAttrToFileInput(fileElem, label); generatedElems.push({el: elem, ref: label}); document.body.appendChild(label.append(fileElem)[0]); return fileElem; } function clickHandler(evt) { if (elem.attr('disabled')) return false; if (attrGetter('ngfSelectDisabled', scope)) return; var r = detectSwipe(evt); // prevent the click if it is a swipe if (r != null) return r; resetModel(evt); // fix for md when the element is removed from the DOM and added back #460 try { if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { generatedElems.push({el: elem, ref: fileElem.parent()}); document.body.appendChild(fileElem.parent()[0]); fileElem.bind('change', changeFn); } } catch (e) {/*ignore*/ } if (isDelayedClickSupported(navigator.userAgent)) { setTimeout(function () { fileElem[0].click(); }, 0); } else { fileElem[0].click(); } return false; } var initialTouchStartY = 0; var initialTouchStartX = 0; function detectSwipe(evt) { var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); if (touches) { if (evt.type === 'touchstart') { initialTouchStartX = touches[0].clientX; initialTouchStartY = touches[0].clientY; return true; // don't block event default } else { // prevent scroll from triggering event if (evt.type === 'touchend') { var currentX = touches[0].clientX; var currentY = touches[0].clientY; if ((Math.abs(currentX - initialTouchStartX) > 20) || (Math.abs(currentY - initialTouchStartY) > 20)) { evt.stopPropagation(); evt.preventDefault(); return false; } } return true; } } } var fileElem = elem; function resetModel(evt) { if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { fileElem.val(null); upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); } } if (!isInputTypeFile()) { fileElem = createFileInput(); } fileElem.bind('change', changeFn); if (!isInputTypeFile()) { elem.bind('click touchstart touchend', clickHandler); } else { elem.bind('click', resetModel); } function ie10SameFileSelectFix(evt) { if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { if (!fileElem[0].parentNode) { fileElem = null; return; } evt.preventDefault(); evt.stopPropagation(); fileElem.unbind('click'); var clone = fileElem.clone(); fileElem.replaceWith(clone); fileElem = clone; fileElem.attr('__ngf_ie10_Fix_', 'true'); fileElem.bind('change', changeFn); fileElem.bind('click', ie10SameFileSelectFix); fileElem[0].click(); return false; } else { fileElem.removeAttr('__ngf_ie10_Fix_'); } } if (navigator.appVersion.indexOf('MSIE 10') !== -1) { fileElem.bind('click', ie10SameFileSelectFix); } if (ngModel) ngModel.$formatters.push(function (val) { if (val == null || val.length === 0) { if (fileElem.val()) { fileElem.val(null); } } return val; }); scope.$on('$destroy', function () { if (!isInputTypeFile()) fileElem.parent().remove(); angular.forEach(unwatches, function (unwatch) { unwatch(); }); }); $timeout(function () { for (var i = 0; i < generatedElems.length; i++) { var g = generatedElems[i]; if (!document.body.contains(g.el[0])) { generatedElems.splice(i, 1); g.ref.remove(); } } }); if (window.FileAPI && window.FileAPI.ngfFixIE) { window.FileAPI.ngfFixIE(elem, fileElem, changeFn); } } return { restrict: 'AEC', require: '?ngModel', link: function (scope, elem, attr, ngModel) { linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); } }; }]); (function () { ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) { var upload = UploadBase; upload.base64DataUrl = function (file) { if (angular.isArray(file)) { var d = $q.defer(), count = 0; angular.forEach(file, function (f) { upload.dataUrl(f, true)['finally'](function () { count++; if (count === file.length) { var urls = []; angular.forEach(file, function (ff) { urls.push(ff.$ngfDataUrl); }); d.resolve(urls, file); } }); }); return d.promise; } else { return upload.dataUrl(file, true); } }; upload.dataUrl = function (file, disallowObjectUrl) { if (!file) return upload.emptyPromise(file, file); if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) { return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file); } var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise; if (p) return p; var deferred = $q.defer(); $timeout(function () { if (window.FileReader && file && (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) && (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) { //prefer URL.createObjectURL for handling refrences to files of all sizes //since it doesn´t build a large string in memory var URL = window.URL || window.webkitURL; if (URL && URL.createObjectURL && !disallowObjectUrl) { var url; try { url = URL.createObjectURL(file); } catch (e) { $timeout(function () { file.$ngfBlobUrl = ''; deferred.reject(); }); return; } $timeout(function () { file.$ngfBlobUrl = url; if (url) { deferred.resolve(url, file); upload.blobUrls = upload.blobUrls || []; upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0; upload.blobUrls.push({url: url, size: file.size}); upload.blobUrlsTotalSize += file.size || 0; var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456; var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200; while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) { var obj = upload.blobUrls.splice(0, 1)[0]; URL.revokeObjectURL(obj.url); upload.blobUrlsTotalSize -= obj.size; } } }); } else { var fileReader = new FileReader(); fileReader.onload = function (e) { $timeout(function () { file.$ngfDataUrl = e.target.result; deferred.resolve(e.target.result, file); $timeout(function () { delete file.$ngfDataUrl; }, 1000); }); }; fileReader.onerror = function () { $timeout(function () { file.$ngfDataUrl = ''; deferred.reject(); }); }; fileReader.readAsDataURL(file); } } else { $timeout(function () { file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = ''; deferred.reject(); }); } }); if (disallowObjectUrl) { p = file.$$ngfDataUrlPromise = deferred.promise; } else { p = file.$$ngfBlobUrlPromise = deferred.promise; } p['finally'](function () { delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise']; }); return p; }; return upload; }]); function getTagType(el) { if (el.tagName.toLowerCase() === 'img') return 'image'; if (el.tagName.toLowerCase() === 'audio') return 'audio'; if (el.tagName.toLowerCase() === 'video') return 'video'; return /./; } function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) { function constructDataUrl(file) { var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope); Upload.dataUrl(file, disallowObjectUrl)['finally'](function () { $timeout(function () { var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl; if (isBackground) { elem.css('background-image', 'url(\'' + (src || '') + '\')'); } else { elem.attr('src', src); } if (src) { elem.removeClass('ng-hide'); } else { elem.addClass('ng-hide'); } }); }); } $timeout(function () { var unwatch = scope.$watch(attr[directiveName], function (file) { var size = resizeParams; if (directiveName === 'ngfThumbnail') { if (!size) { size = { width: elem[0].naturalWidth || elem[0].clientWidth, height: elem[0].naturalHeight || elem[0].clientHeight }; } if (size.width === 0 && window.getComputedStyle) { var style = getComputedStyle(elem[0]); if (style.width && style.width.indexOf('px') > -1 && style.height && style.height.indexOf('px') > -1) { size = { width: parseInt(style.width.slice(0, -2)), height: parseInt(style.height.slice(0, -2)) }; } } } if (angular.isString(file)) { elem.removeClass('ng-hide'); if (isBackground) { return elem.css('background-image', 'url(\'' + file + '\')'); } else { return elem.attr('src', file); } } if (file && file.type && file.type.search(getTagType(elem[0])) === 0 && (!isBackground || file.type.indexOf('image') === 0)) { if (size && Upload.isResizeSupported()) { size.resizeIf = function (width, height) { return Upload.attrGetter('ngfResizeIf', attr, scope, {$width: width, $height: height, $file: file}); }; Upload.resize(file, size).then( function (f) { constructDataUrl(f); }, function (e) { throw e; } ); } else { constructDataUrl(file); } } else { elem.addClass('ng-hide'); } }); scope.$on('$destroy', function () { unwatch(); }); }); } /** @namespace attr.ngfSrc */ /** @namespace attr.ngfNoObjectUrl */ ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) { return { restrict: 'AE', link: function (scope, elem, attr) { linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc', Upload.attrGetter('ngfResize', attr, scope), false); } }; }]); /** @namespace attr.ngfBackground */ /** @namespace attr.ngfNoObjectUrl */ ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) { return { restrict: 'AE', link: function (scope, elem, attr) { linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground', Upload.attrGetter('ngfResize', attr, scope), true); } }; }]); /** @namespace attr.ngfThumbnail */ /** @namespace attr.ngfAsBackground */ /** @namespace attr.ngfSize */ /** @namespace attr.ngfNoObjectUrl */ ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) { return { restrict: 'AE', link: function (scope, elem, attr) { var size = Upload.attrGetter('ngfSize', attr, scope); linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size, Upload.attrGetter('ngfAsBackground', attr, scope)); } }; }]); ngFileUpload.config(['$compileProvider', function ($compileProvider) { if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/); if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/); }]); ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) { return function (file, disallowObjectUrl, trustedUrl) { if (angular.isString(file)) { return $sce.trustAsResourceUrl(file); } var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl); if (file && !src) { if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) { file.$ngfDataUrlFilterInProgress = true; UploadDataUrl.dataUrl(file, disallowObjectUrl); } return ''; } if (file) delete file.$ngfDataUrlFilterInProgress; return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || ''; }; }]); })(); ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) { var upload = UploadDataUrl; function globStringToRegex(str) { var regexp = '', excludes = []; if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') { regexp = str.substring(1, str.length - 1); } else { var split = str.split(','); if (split.length > 1) { for (var i = 0; i < split.length; i++) { var r = globStringToRegex(split[i]); if (r.regexp) { regexp += '(' + r.regexp + ')'; if (i < split.length - 1) { regexp += '|'; } } else { excludes = excludes.concat(r.excludes); } } } else { if (str.indexOf('!') === 0) { excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$'); } else { if (str.indexOf('.') === 0) { str = '*' + str; } regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$'; regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.'); } } } return {regexp: regexp, excludes: excludes}; } upload.validatePattern = function (file, val) { if (!val) { return true; } var pattern = globStringToRegex(val), valid = true; if (pattern.regexp && pattern.regexp.length) { var regexp = new RegExp(pattern.regexp, 'i'); valid = (file.type != null && regexp.test(file.type)) || (file.name != null && regexp.test(file.name)); } var len = pattern.excludes.length; while (len--) { var exclude = new RegExp(pattern.excludes[len], 'i'); valid = valid && (file.type == null || exclude.test(file.type)) && (file.name == null || exclude.test(file.name)); } return valid; }; upload.ratioToFloat = function (val) { var r = val.toString(), xIndex = r.search(/[x:]/i); if (xIndex > -1) { r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1)); } else { r = parseFloat(r); } return r; }; upload.registerModelChangeValidator = function (ngModel, attr, scope) { if (ngModel) { ngModel.$formatters.push(function (files) { if (ngModel.$dirty) { var filesArray = files; if (files && !angular.isArray(files)) { filesArray = [files]; } upload.validate(filesArray, 0, ngModel, attr, scope).then(function () { upload.applyModelValidation(ngModel, filesArray); }); } return files; }); } }; function markModelAsDirty(ngModel, files) { if (files != null && !ngModel.$dirty) { if (ngModel.$setDirty) { ngModel.$setDirty(); } else { ngModel.$dirty = true; } } } upload.applyModelValidation = function (ngModel, files) { markModelAsDirty(ngModel, files); angular.forEach(ngModel.$ngfValidations, function (validation) { ngModel.$setValidity(validation.name, validation.valid); }); }; upload.getValidationAttr = function (attr, scope, name, validationName, file) { var dName = 'ngf' + name[0].toUpperCase() + name.substr(1); var val = upload.attrGetter(dName, attr, scope, {$file: file}); if (val == null) { val = upload.attrGetter('ngfValidate', attr, scope, {$file: file}); if (val) { var split = (validationName || name).split('.'); val = val[split[0]]; if (split.length > 1) { val = val && val[split[1]]; } } } return val; }; upload.validate = function (files, prevLength, ngModel, attr, scope) { ngModel = ngModel || {}; ngModel.$ngfValidations = ngModel.$ngfValidations || []; angular.forEach(ngModel.$ngfValidations, function (v) { v.valid = true; }); var attrGetter = function (name, params) { return upload.attrGetter(name, attr, scope, params); }; var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' '); var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope); if (files == null || files.length === 0) { return upload.emptyPromise({'validFiles': files, 'invalidFiles': []}); } files = files.length === undefined ? [files] : files.slice(0); var invalidFiles = []; function validateSync(name, validationName, fn) { if (files) { var i = files.length, valid = null; while (i--) { var file = files[i]; if (file) { var val = upload.getValidationAttr(attr, scope, name, validationName, file); if (val != null) { if (!fn(file, val, i)) { if (ignoredErrors.indexOf(name) === -1) { file.$error = name; (file.$errorMessages = (file.$errorMessages || {}))[name] = true; file.$errorParam = val; if (invalidFiles.indexOf(file) === -1) { invalidFiles.push(file); } if (!runAllValidation) { files.splice(i, 1); } valid = false; } else { files.splice(i, 1); } } } } } if (valid !== null) { ngModel.$ngfValidations.push({name: name, valid: valid}); } } } validateSync('pattern', null, upload.validatePattern); validateSync('minSize', 'size.min', function (file, val) { return file.size + 0.1 >= upload.translateScalars(val); }); validateSync('maxSize', 'size.max', function (file, val) { return file.size - 0.1 <= upload.translateScalars(val); }); var totalSize = 0; validateSync('maxTotalSize', null, function (file, val) { totalSize += file.size; if (totalSize > upload.translateScalars(val)) { files.splice(0, files.length); return false; } return true; }); validateSync('validateFn', null, function (file, r) { return r === true || r === null || r === ''; }); if (!files.length) { return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles}); } function validateAsync(name, validationName, type, asyncFn, fn) { function resolveResult(defer, file, val) { function resolveInternal(fn) { if (fn()) { if (ignoredErrors.indexOf(name) === -1) { file.$error = name; (file.$errorMessages = (file.$errorMessages || {}))[name] = true; file.$errorParam = val; if (invalidFiles.indexOf(file) === -1) { invalidFiles.push(file); } if (!runAllValidation) { var i = files.indexOf(file); if (i > -1) files.splice(i, 1); } defer.resolve(false); } else { var j = files.indexOf(file); if (j > -1) files.splice(j, 1); defer.resolve(true); } } else { defer.resolve(true); } } if (val != null) { asyncFn(file, val).then(function (d) { resolveInternal(function () { return !fn(d, val); }); }, function () { resolveInternal(function () { return attrGetter('ngfValidateForce', {$file: file}); }); }); } else { defer.resolve(true); } } var promises = [upload.emptyPromise(true)]; if (files) { files = files.length === undefined ? [files] : files; angular.forEach(files, function (file) { var defer = $q.defer(); promises.push(defer.promise); if (type && (file.type == null || file.type.search(type) !== 0)) { defer.resolve(true); return; } if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) { upload.imageDimensions(file).then(function (d) { resolveResult(defer, file, attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height})); }, function () { defer.resolve(false); }); } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) { upload.mediaDuration(file).then(function (d) { resolveResult(defer, file, attrGetter('ngfDuration', {$file: file, $duration: d})); }, function () { defer.resolve(false); }); } else { resolveResult(defer, file, upload.getValidationAttr(attr, scope, name, validationName, file)); } }); } var deffer = $q.defer(); $q.all(promises).then(function (values) { var isValid = true; for (var i = 0; i < values.length; i++) { if (!values[i]) { isValid = false; break; } } ngModel.$ngfValidations.push({name: name, valid: isValid}); deffer.resolve(isValid); }); return deffer.promise; } var deffer = $q.defer(); var promises = []; promises.push(validateAsync('maxHeight', 'height.max', /image/, this.imageDimensions, function (d, val) { return d.height <= val; })); promises.push(validateAsync('minHeight', 'height.min', /image/, this.imageDimensions, function (d, val) { return d.height >= val; })); promises.push(validateAsync('maxWidth', 'width.max', /image/, this.imageDimensions, function (d, val) { return d.width <= val; })); promises.push(validateAsync('minWidth', 'width.min', /image/, this.imageDimensions, function (d, val) { return d.width >= val; })); promises.push(validateAsync('dimensions', null, /image/, function (file, val) { return upload.emptyPromise(val); }, function (r) { return r; })); promises.push(validateAsync('ratio', null, /image/, this.imageDimensions, function (d, val) { var split = val.toString().split(','), valid = false; for (var i = 0; i < split.length; i++) { if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) { valid = true; } } return valid; })); promises.push(validateAsync('maxRatio', 'ratio.max', /image/, this.imageDimensions, function (d, val) { return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001; })); promises.push(validateAsync('minRatio', 'ratio.min', /image/, this.imageDimensions, function (d, val) { return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001; })); promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/, this.mediaDuration, function (d, val) { return d <= upload.translateScalars(val); })); promises.push(validateAsync('minDuration', 'duration.min', /audio|video/, this.mediaDuration, function (d, val) { return d >= upload.translateScalars(val); })); promises.push(validateAsync('duration', null, /audio|video/, function (file, val) { return upload.emptyPromise(val); }, function (r) { return r; })); promises.push(validateAsync('validateAsyncFn', null, null, function (file, val) { return val; }, function (r) { return r === true || r === null || r === ''; })); $q.all(promises).then(function () { if (runAllValidation) { for (var i = 0; i < files.length; i++) { var file = files[i]; if (file.$error) { files.splice(i--, 1); } } } runAllValidation = false; validateSync('maxFiles', null, function (file, val, i) { return prevLength + i < val; }); deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles}); }); return deffer.promise; }; upload.imageDimensions = function (file) { if (file.$ngfWidth && file.$ngfHeight) { var d = $q.defer(); $timeout(function () { d.resolve({width: file.$ngfWidth, height: file.$ngfHeight}); }); return d.promise; } if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise; var deferred = $q.defer(); $timeout(function () { if (file.type.indexOf('image') !== 0) { deferred.reject('not image'); return; } upload.dataUrl(file).then(function (dataUrl) { var img = angular.element('').attr('src', dataUrl) .css('visibility', 'hidden').css('position', 'fixed') .css('max-width', 'none !important').css('max-height', 'none !important'); function success() { var width = img[0].naturalWidth || img[0].clientWidth; var height = img[0].naturalHeight || img[0].clientHeight; img.remove(); file.$ngfWidth = width; file.$ngfHeight = height; deferred.resolve({width: width, height: height}); } function error() { img.remove(); deferred.reject('load error'); } img.on('load', success); img.on('error', error); var secondsCounter = 0; function checkLoadErrorInCaseOfNoCallback() { $timeout(function () { if (img[0].parentNode) { if (img[0].clientWidth) { success(); } else if (secondsCounter++ > 10) { error(); } else { checkLoadErrorInCaseOfNoCallback(); } } }, 1000); } checkLoadErrorInCaseOfNoCallback(); angular.element(document.getElementsByTagName('body')[0]).append(img); }, function () { deferred.reject('load error'); }); }); file.$ngfDimensionPromise = deferred.promise; file.$ngfDimensionPromise['finally'](function () { delete file.$ngfDimensionPromise; }); return file.$ngfDimensionPromise; }; upload.mediaDuration = function (file) { if (file.$ngfDuration) { var d = $q.defer(); $timeout(function () { d.resolve(file.$ngfDuration); }); return d.promise; } if (file.$ngfDurationPromise) return file.$ngfDurationPromise; var deferred = $q.defer(); $timeout(function () { if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) { deferred.reject('not media'); return; } upload.dataUrl(file).then(function (dataUrl) { var el = angular.element(file.type.indexOf('audio') === 0 ? '