Skip to content
forked from mailru/FileAPI

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.

License

Notifications You must be signed in to change notification settings

danialfarid/FileAPI

 
 

Repository files navigation

FileAPI — a set of tools for working with files.

~~~ DEMO ~~~ user pic ~~~

Support

  • Multiupload: all browsers that support HTML5 or Flash
  • Drag'n'Drop upload: files (HTML5) & directories (Chrome 21+)
  • Chunked file upload (HTML5)
  • Upload one file: all browsers
  • Working with Images: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+
    • crop, resize, preview & rotate (HTML5 or Flash)
    • auto orientation by exif (HTML5, if include FileAPI.exif.js or Flash)

Example

<span class="js-fileapi-wrapper" style="position: relative;"> <input id="user-files" type="file" multiple /> </span> <div id="preview-list"> </div>
var input = document.getElementById('user-files'); var previewNode = document.getElementById('preview-list'); // Drag'n'Drop FileAPI.event.dnd(previewNode, function (over){ $(this).css('background', over ? 'red' : ''); }, function (files){ // .. }); FileAPI.event.on(input, 'change', function (evt){ var files = FileAPI.getFiles(evt.target); // or FileAPI.getFiles(evt) // filtering FileAPI.filterFiles(files, function (file, info){ if( /image/.test(file.type) && info ){ returninfo.width >= 320 && info.height >= 240; } else { returnfile.size > 128; } }, function (fileList, ignor){ if( ignor.length ){ // ... } if( !fileList.length ){ // empty file list return; } // do preview var imageList = FileAPI.filter(fileList, function (file){ return /image/.test(file.type); }); FileAPI.each(imageList, function (imageFile){ FileAPI.Image(imageFile) .preview(100, 120) .get(function (err, image){ if( err ){ // ... } else { previewNode.appendChild(image); } }) ; }); // upload on server var xhr = FileAPI.upload({ url: '...', data: { foo: 'bar' }, // POST-data (iframe, flash, html5) headers: { 'x-header': '...' }, // request headers (html5) files: { files: FileAPI.filter(fileList, function (file){ return !/image/.test(file.type); }), pictures: imageList }, imageTransform: { maxWidth: 1024, maxHeight: 768 }, imageAutoOrientation: true, fileprogress: function (evt){ // (flash, html5) var percent = evt.loaded/evt.total*100; // ... }, progress: function (evt){ // (flash, html5) var percent = evt.loaded/evt.total*100; // ... }, complete: function (err, xhr){ // ... } }); }); });

HTML structure (templates)

API

Events

  • FileAPI.event.on(el:HTMLElement, eventType:String, fn:Function)
  • FileAPI.event.off(el:HTMLElement, eventType:String, fn:Function)
  • FileAPI.event.one(el:HTMLElement, eventType:String, fn:Function)
  • FileAPI.event.dnd(el:HTMLElement, onHover:Function, onDrop:Function)
  • jQuery('#el').dnd(onHover, onDrop)

FileAPI.Image

  • .crop(width[, height])
  • .crop(x, y, width[, height])
  • .resize(width[, height])
  • .resize(width, height, type:Enum(min,max,preview))
  • .preview(width[, height])
  • .rotate(deg)
  • .get(fn:Function)

Utils

  • FileAPI.KB
  • FileAPI.MB
  • FileAPI.GB
  • FileAPI.TB
  • FileAPI.support.html5:Boolean
  • FileAPI.support.cors:Boolean
  • FileAPI.support.dnd:Boolean
  • FileAPI.support.flash:Boolean
  • FileAPI.support.canvas:Boolean
  • FileAPI.support.dataURI:Boolean
  • FileAPI.support.chunked:Boolean
  • FileAPI.each(obj:Object|Array, fn:function, context:Mixed)
  • FileAPI.extend(dst:Object, src:Object):Object
  • FileAPI.filter(list:Array, iterator:Function):Array
  • FileAPI.isFile(file:Mixed):Boolean
  • FileAPI.toBinaryString(str:Base64):String

FileAPI.getFiles

FileAPI.event.on('#my-file-1', 'change', onSelect); // or jQuery $('#my-file-2').on('change', onSelect); function onSelect(evt/**Event*/){ // (1) extract fileList from event var files = FileAPI.getFiles(evt); // (2) or so var files = FileAPI.getFiles(evt.target); }

FileAPI.getDropFiles

function onDrop(evt){ FileAPI.getDropFiles(evt, function (files){ if( files.length ){ // ... } }); } // OR var el = document.getElementById('el'); FileAPI.event.dnd(el, function (over/**Boolean*/, evt/**Event*/){ el.style.background = over ? 'red' : ''; }, function (files/**Array*/, evt/**Event*/){ // ... });

FileAPI.getInfo

FileAPI.getInfo(imageFile/**File*/, function (err/**Boolean*/, info/**Object*/){ if( !err ){ switch( info.exif.Orientation ){ // ... } } }); // ... FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){ // http://www.nihilogic.dk/labs/exif/exif.js // http://www.nihilogic.dk/labs/binaryajax/binaryajax.js FileAPI.readAsBinaryString(file, function (evt){ if( evt.type == 'load' ){ var binaryString = evt.result; var oFile = new BinaryFile(binaryString, 0, file.size); var exif = EXIF.readFromBinaryFile(oFile); callback(false, { 'exif': exif || {} }); } else if( evt.type == 'error' ){ callback('read_as_binary_string'); } else if( evt.type == 'progress' ){ // ... } }); });

FileAPI.filterFiles

FileAPI.filterFiles(files, function (file, info){ if( /image/.test(file.type) && info ){ returninfo.width > 320 && info.height > 240; } returnfile.size < 10 * FileAPI.MB; }, function (result, ignor){ // ... });

FileAPI.readAsImage, FileAPI.readAsDataURL (FileAPI.readAsBinaryString)

FileAPI.readAsImage(file, function (evt){ if( evt.type == 'load' ){ var images = document.getElementById('images'); images.appendChild(evt.result); } else { // ... } }); FileAPI.readAsDataURL(file, function (evt){ if( evt.type == 'load' ){ // success var result = evt.result; } else if( evt.type == 'progress' ){ var pr = evt.loaded/evt.total * 100; } else { // error } });

FileAPI.upload

var xhr = FileAPI.upload({ url: '...', data: { foo: 'bar' }, headers: { 'x-header': '...' }, files: { images: FileAPI.filter(files, function (file){ return /image/.test(file.type); }), customFile: { file: 'generate.txt', blob: customFileBlob } }, chunkSize: 0, // or chunk size in bytes, eg: FileAPI.MB*.5 (html5) chunkUploadRetry: 0, // number of retries during upload chunks (html5) imageTransform: { maxWidth: 1024, maxHeight: 768 }, imageAutoOrientation: true, prepare: function (file, options){ // prepare options for current file options.data.filename = file.name; }, upload: function (xhr, options){ // start uploading }, fileupload: function (xhr, options){ // start file uploading }, fileprogress: function (evt){ // progress file uploading var filePercent = evt.loaded/evt.total*100; }, filecomplete: function (err, xhr){ if( !err ){ var response = xhr.responseText; } }, progress: function (evt){ // total progress uploading var totalPercent = evt.loaded/evt.total*100; }, complete: function (err, xhr){ if( !err ){ // Congratulations, the uploading was successful! } } });

imageTransform

  • width:Number
  • height:Number
  • preview:Boolean
  • maxWidth:Number
  • maxHeight:Number
  • rotate:Number
FileAPI.upload({ // .. imageOriginal: false, // don't send original on server imageTransform: { // (1) Resize to 120x200 resize: { width: 120, height: 200 } // (2) create preview 320x240 thumb: { width: 320, height: 240, preview: true } // (3) Resize by max side max: { maxWidth: 800, maxHeight: 600 } // (4) Custom resize custom: function (info, transform){ return transform .crop(100, 100, 300, 200) .resize(100, 50) ; } } });

imageAutoOrientation

// (1) all images FileAPI.upload({ // .. imageAutoOrientation: true }); // (2) or so FileAPI.upload({ // .. imageAutoOrientation: true, imageTransform: { width: .., height: .. } }); // (3) or so FileAPI.upload({ // .. imageTransform: { rotate: 'auto' } }); // (4) only "800x600", original not modified FileAPI.upload({ // .. imageTransform: { "800x600": { width: 800, height: 600, rotate: 'auto' } } });

Flash settings

<script> var FileAPI = { // @required staticPath: '/js/' // @default: "./" // @optional , flashUrl: '/js/FileAPI.flash.swf' // @default: FileAPI.staticPath + "FileAPI.flash.swf" , flashImageUrl: '/js/FileAPI.flash.image.swf' // @default: FileAPI.staticPath + "FileAPI.flash.image.swf" }; </script> <script src="/js/FileAPI.min.js"></script>

Flash-request (FileReference)

The following sample HTTP POST request is sent from Flash Player to a server-side script if no parameters are specified:

 POST /handler.cfm HTTP/1.1 Accept: text/* Content-Type: multipart/form-data; boundary=----------Ij5ae0ae0KM7GI3KM7 User-Agent: Shockwave Flash Host: www.example.com Content-Length: 421 Connection: Keep-Alive Cache-Control: no-cache ------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7 Content-Disposition: form-data; name="Filename" MyFile.jpg ------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7 Content-Disposition: form-data; name="Filedata"; filename="MyFile.jpg" Content-Type: application/octet-stream FileDataHere ------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7 Content-Disposition: form-data; name="Upload" Submit Query ------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7-- 

Chunked file upload (html5)

FileAPI.upload({ url: '...' , files: fileList , chunkSize: .5 * FileAPI.MB // 512KB , chunkUploadRetry: 1 , complete: function (err, xhr){} });

Client and server communicate to each other using the following HTTP headers and status codes.

Client explicitly sets the following headers:

Any other headers are set by a target browser and are not used by client. Library does not provide any facilities to track a file uniqueness across requests, it's left on developer's consideration.

Client recognizes the following response codes:

  • 200, 201 - chunk is successfully saved
  • 416, 500 - recoverable error, library tries to resend chunk 'chunkUploadRetry' times then fails

All the other codes - fatal error, user's involvement is recommend.


{ name: 'fileName', type: 'mime-type', size: 'fileSize' }

XMLHttpRequest

{ status: Number, statusText: Number, readyState: Number, response: Blob, responseXML: XML, responseText: String, responseBody: String, getResponseHeader: function (name/**String*/)/**String*/{}, getAllResponseHeaders: function ()/**Object*/{}, abort: function (){} }

Cross-Domain upload-controller headers

<? header('Access-Control-Allow-Methods: POST, OPTIONS'); header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type'); // and other custom headers header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); // a comma-separated list of domains header('Access-Control-Allow-Credentials: true'); if( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ){ exit;	} if( $_SERVER['REQUEST_METHOD'] == 'POST' ){ // ...	} ?>

iframe

POST-query

/controller.php	?foo=bar	&images=...	&callback=... 

POST-response

<script type="text/javascript"> (function (ctx, jsonp){ if( ctx && ctx[jsonp] ){	ctx[jsonp](<?=$statusCode/*200 — OK*/?>, "<?=addslashes($statusText)?>", "<?=addslashes($response)?>");	} })(this.parent, "<?=htmlspecialchars($_POST['callback'])?>"); </script>

HTML structure (templates)

Default

<span class="js-fileapi-wrapper" style="position: relative; display: inline-block;"> <input name="files" type="file" multiple /> </span>

Button

<style> .upload-btn { width: 130px; height: 25px; overflow: hidden; position: relative; border: 3px solid #06c; border-radius: 5px; background: #0cf; }	.upload-btn:hover { background: #09f;	}	.upload-btn__txt { z-index: 1; position: relative; color: #fff; font-size: 18px; font-family: "Helvetica Neue"; line-height: 24px; text-align: center; text-shadow: 0 1px 1px #000;	}	.upload-btn__inp { top: -10px; right: -40px; z-index: 2; position: absolute; cursor: pointer; opacity: 0; filter: alpha(opacity=0); font-size: 50px;	} </style> <div class="upload-btn js-fileapi-wrapper"> <div class="upload-btn__txt">Upload files</div> <input class="upload-btn__inp" name="files" type="file" multiple /> </div>

Link

<style> .upload-link { color: #36c; display: inline-block; *zoom: 1; *display: inline; overflow: hidden; position: relative; padding-bottom: 2px; text-decoration: none; }	.upload-link__txt { z-index: 1; position: relative; border-bottom: 1px dotted #36c;	}	.upload-link:hover .upload-link__txt { color: #f00; border-bottom-color: #f00;	}	.upload-link__inp { top: -10px; right: -40px; z-index: 2; position: absolute; cursor: pointer; opacity: 0; filter: alpha(opacity=0); font-size: 50px;	} </style> <a class="upload-link js-fileapi-wrapper"> <span class="upload-link__txt">Upload photo</span> <input class="upload-link__inp" name="photo" type="file" accept=".jpg,.jpeg,.gif" /> </a>

Changelog

    • FileAPI.withCredentials: true
  • #90: Fixed progress event

1.2.5

  • #86: Smarter upload recovery
  • #87: Fixed upload files into browsers that do not support FormData
  • Fixed support "accept" attribute for Flash.
  • Fixed detection of HTML5 support for FireFox 3.6
    • FileAPI.html5 option, default "true"

1.2.4

  • Fixed auto orientation image by EXIF (Flash)
  • Fixed image dimensions after rotate (Flash)
  • #82: "undefined" data-fields cause exceptions
  • #83: Allow requests without files
  • #84: Fixed connection abort when waiting for connection recovery

1.2.3

  • #77: Fixed flash.abort(), #75
    • FileAPI.addMime
    • FileAPI.accept — fallback for flash.

1.2.2

  • #67: Added correct httpStatus for upload fail, #62
  • #68 Added "Content-Type" for chunked upload, #65
  • #69: Fixed network down recovery
  • Fixed progress event, #66
  • Increase flash stage size, #73
    • array index from POST-param "name", #72
    • dependency on FileAPI.Image for FileAPI.Flash

1.2.1

1.2.0

  • #57: Chunked file upload

1.1.0

  • #54: added FileAPI.flashUrl and FileAPI.flashImageUrl

1.0.1

  • #51: remove circular references from file-objects (Flash transport)
  • added changelog

1.0.0

  • first release

About

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.

Resources

License

Stars

Watchers

Forks

Packages

No packages published