Fetching contributors…
Cannot retrieve contributors at this time
1356 lines (1068 sloc) 35.3 KB

FileAPI

Набор JavaScript инструментов для работы с файлами.

Get started

    <div>
        <!-- "js-fileapi-wrapper" -- обязательный class -->
        <div class="js-fileapi-wrapper upload-btn" id="choose">
            <div class="upload-btn__txt">Choose files</div>
            <input name="files" type="file" multiple />
        </div>
        <div id="images"><!-- предпросмотр --></div>
    </div>

    <script>window.FileAPI = { staticPath: '/js/FileAPI/dist/' };</script>
    <script src="/js/FileAPI/dist/FileAPI.min.js"></script>
    <script>
        FileAPI.event.on(choose, 'change', function (evt){
            var files = FileAPI.getFiles(evt); // Retrieve file list

            FileAPI.filterFiles(files, function (file, info/**Object*/){
                if( /^image/.test(file.type) ){
                    return  info.width >= 320 && info.height >= 240;
                }
                return  false;
            }, function (files/**Array*/, rejected/**Array*/){
                if( files.length ){
                    // Создаем предпросмотр 100x100
                    FileAPI.each(files, function (file){
                        FileAPI.Image(file).preview(100).get(function (err, img){
                            images.appendChild(img);
                        });
                    });

                    // Загружаем файлы
                    FileAPI.upload({
                        url: './ctrl.php',
                        files: { images: files },
                        progress: function (evt){ /* ... */ },
                        complete: function (err, xhr){ /* ... */ }
                    });
                }
            });
        });
    </script>

Setup options

Отредактируйте файл crossdomain.xml и разместите его в корне домена, на который будут загружаться файлы.

    <script>
        window.FileAPI = {
              debug: false  // дебаг режим, смотрите Console
            , cors: false   // если используете CORS -- `true`
            , media: false  // если используете веб-камеру -- `true`
            , staticPath: '/js/FileAPI/dist/' // путь к '*.swf'
            , postNameConcat: function (name, idx){
                // Default: object[foo]=1&object[bar][baz]=2
                // .NET: https://github.com/mailru/FileAPI/issues/121#issuecomment-24590395
                return  name + (idx != null ? '['+ idx +']' : '');
            }
        };
    </script>
    <script src="/js/FileAPI/dist/FileAPI.min.js"></script>

    <!-- ИЛИ -->

    <script>
        window.FileAPI = { /* etc. */ };
        require(['FileAPI'], function (FileAPI){
            // ...
        });
    </script>

getFiles(input:HTMLInputElement|Event|$.Event):Array

Получить список файлов из input элемента, или event, также поддерживается jQuery.

  • input — HTMLInputElement, change и drop события, jQuery коллекция или jQuery.Event
var el = document.getElement('my-input');
FileAPI.event.on(el, function (evt/**Event*/){
    // Получить список файлов из `input`
    var files = FileAPI.getFiles(el);

    // или события
    var files = FileAPI.getFiles(evt);
});

getInfo(file:Object, callback:Function):void

Получить информацию о файле (см. FileAPI.addInfoReader).

// Получить информацию о изображении (FileAPI.exif.js подключен)
FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
    if( !err ){
        console.log(info); // { width: 800, height: 600, exif: {..} }
    }
});

// Получить информацию о mp3 файле (FileAPI.id3.js included)
FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
    if( !err ){
        console.log(info); // { title: "...", album: "...", artists: "...", ... }
    }
});

filterFiles(files:Array, filter:Function, callback:Function):void

Отфильтровать список файлов, используя дополнительную информацию о них. см. FileAPI.getInfo или FileAPI.addInfoReader.

  • files — оригинальный список файлов
  • filter — функция, принимает два аргумента: file — сам файл, info — дополнительная информация
  • callback — функция: list — список файлов, подошедшие под условия, other — все остальные.
// Получаем список файлов
var files = FileAPI.getFiles(input);

// Фильтруем список
FileAPI.filterFiles(files, function (file/**Object*/, info/**Object*/){
    if( /^image/.test(file.type) && info ){
        return  info.width > 320 && info.height > 240;
    } else {
        return  file.size < 20 * FileAPI.MB;
    }
}, function (list/**Array*/, other/**Array*/){
    if( list.length ){
        // ..
    }
});

getDropFiles(evt:Event|$.Event, callback:Function):void

Получить весь список файлов, включая директории.

  • evt — drop event
  • callback — функция, принимает один аргумент — список файлов
FileAPI.event.on(document, 'drop', function (evt/**Event*/){
    evt.preventDefault();

    // Получаем все файлы
    FileAPI.getDropFiles(evt, function (files/**Array*/){
        // ...
    });
});

upload(opts:Object):XmlHttpRequest

Загрузка файлов на сервер (последовательно). Возвращает XHR-подобный объект. Помните, для корректной работы flash-транспорта, тело ответа сервера не должно быть пустым, например можно ответить простым текстом "ok".

  • opts — объект настроек, см. раздел Upload options
var el = document.getElementById('my-input');
FileAPI.event.on(el, 'change', function (evt/**Event*/){
    var files = FileAPI.getFiles(evt);
    var xhr = FileAPI.upload({
        url: 'http://rubaxa.org/FileAPI/server/ctrl.php',
        files: { file: files[0] },
        complete: function (err, xhr){
            if( !err ){
                var result = xhr.responseText;
                // ...
            }
        }
    });
});

addInfoReader(mime:RegExp, handler:Function):void

Добавить обработчик, для сбора информации о файле. см. также: FileAPI.getInfo и FileAPI.filterFiles.

  • mime — маска mime-type
  • handler — функция, принимает два аргумента: file объект и complete функция обратного вызова
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/**Object*/){
        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' ){
            // ...
        }
    });
});

readAsDataURL(file:Object, callback:Function):void

Чтение содержимого указанного файла как dataURL.

  • file — файл для чтения
  • callback — функция обработчик
FileAPI.readAsDataURL(file, function (evt/**Object*/){
    if( evt.type == 'load' ){
        // Всё хорошо
        var dataURL = evt.result;
    } else if( evt.type =='progress' ){
        var pr = evt.loaded/evt.total * 100;
    } else {
        // Ошибка
    }
})

readAsBinaryString(file:Object, callback:Function):void

Чтение содержимого указанного файла как BinaryString.

  • file — файл для чтения
  • callback — функция обработчик
FileAPI.readAsBinaryString(file, function (evt/**Object*/){
    if( evt.type == 'load' ){
        // Всё хорошо
        var binaryString = evt.result;
    } else if( evt.type =='progress' ){
        var pr = evt.loaded/evt.total * 100;
    } else {
        // Ошибка
    }
})

readAsArrayBuffer(file:Object, callback:Function):void

Чтение содержимого указанного файла как ArrayBuffer.

  • file — файл для чтения
  • callback — функция обработчик
FileAPI.readAsArrayBuffer(file, function (evt/**Object*/){
    if( evt.type == 'load' ){
        // Всё хорошо
        var arrayBuffer = evt.result;
    } else if( evt.type =='progress' ){
        var pr = evt.loaded/evt.total * 100;
    } else {
        // Ошибка
    }
})

readAsText(file:Object, callback:Function):void

Чтение содержимого указанного файла как text.

  • file — файл для чтения
  • callback — функция обработчик
FileAPI.readAsText(file, function (evt/**Object*/){
    if( evt.type == 'load' ){
        // Всё хорошо
        var text = evt.result;
    } else if( evt.type =='progress' ){
        var pr = evt.loaded/evt.total * 100;
    } else {
        // Ошибка
    }
})

readAsText(file:Object, encoding:String, callback:Function):void

Чтение содержимого указанного файла как text в нужной кодировке.

  • encoding — строкой с указанием кодировки. По умолчанию UTF-8.
FileAPI.readAsText(file, "utf-8", function (evt/**Object*/){
    if( evt.type == 'load' ){
        // Всё хорошо
        var text = evt.result;
    } else if( evt.type =='progress' ){
        var pr = evt.loaded/evt.total * 100;
    } else {
        // Ошибка
    }
})

Опции загрузки

url:String

Строка, содержащая адрес, на который отправляется запрос.


data:Object

Дополнительные данные, которые должны быть отправлены вместе с файлом.

var xhr = FileAPI.upload({
    url: '...',
    data: { 'session-id': 123 },
    files: { ... },
});

headers:Object

Дополнительные заголовки запроса, только HTML5.

var xhr = FileAPI.upload({
    url: '...',
    headers: { 'x-upload': 'fileapi' },
    files: { .. },
});

chunkSize:Number

Размер части файла в байтах, только HTML5.

var xhr = FileAPI.upload({
    url: '...',
    files: { images: fileList },
    chunkSize: 0.5 * FileAPI.MB
});

chunkUploadRetry:Number

Количество попыток загрузки одной части, только HTML5.

var xhr = FileAPI.upload({
    url: '...',
    files: { images: fileList },
    chunkSize: 0.5 * FileAPI.MB,
    chunkUploadRetry: 3
});

imageTransform:Object

Правила модификации оригинально изображения.

var xhr = FileAPI.upload({
    url: '...',
    files: { image: imageFiles },
    // Changes the original image
    imageTransform: {
        // Ресайз по боьшой строне
        maxWidth: 800,
        maxHeight: 600,
        // Добавляем водяной знак
        overlay: [{ x: 10, y: 10, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
    }
});

imageTransform:Object

Правила для нарезки дополнительных изображения на клиенте.

var xhr = FileAPI.upload({
    url: '...',
    files: { image: imageFiles },
    imageTransform: {
        // Ресайз по большой строне
        'huge': { maxWidth: 800, maxHeight: 600 },
        // Ресайз и кроп
        'medium': { width: 320, height: 240, preview: true },
        // ресайз и кроп + водяной знак
        'small': {
            width: 100, height: 100,
            // Добавляем водяной знак
            overlay: [{ x: 5, y: 5, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
        }
    }
});

imageTransform:Object

Конвертация всех изображений в jpeg или png.

var xhr = FileAPI.upload({
    url: '...',
    files: { image: imageFiles },
    imageTransform: {
        type: 'image/jpeg',
        quality: 0.86 // качество jpeg
    }
});

imageOriginal:Boolean

Отправлять исходное изображение на сервер или нет, если определен imageTransform вариант.

imageAutoOrientation:Boolean

Автоматический поворот изображения на основе EXIF.

prepare:Function

Подготовка опций загрузки для конкретного файла.

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    prepare: function (file/**Object*/, options/**Object*/){
        options.data.secret = utils.getSecretKey(file.name);
    }
});

upload:Function

Начало загрузки

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    upload: function (xhr/**Object*/, options/**Object*/){
        // ...
    }
});

fileupload:Function

Начало загрузки файла

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    fileupload: function (file/**Object*/, xhr/**Object*/, options/**Object*/){
        // ...
    }
});

progress:Function

Общий прогресс загрузки файлов.

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    progress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
        var pr = evt.loaded/evt.total * 100;
    }
});

fileprogress:Function

Прогресс загрузки файла.

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    fileprogress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
        var pr = evt.loaded/evt.total * 100;
    }
});

complete:Function

Завершение загрузки всех файлов.

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    complete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
        if( !err ){
            // Все файлы загружены успешно
        }
    }
});

filecomplete:Function

Конец загрузки файла.

var xhr = FileAPI.upload({
    url: '...',
    files: { .. }
    filecomplete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
        if( !err ){
            // Файл загружен успешно
            var result = xhr.responseText;
        }
    }
});

File object

name

Имя файла.

type

MIME type

size

Размер файла в байтах.


FileAPI.event

on(el:HTMLElement, events:String, handler:Function):void

Добавить функцию обработки события.

  • el — DOM элемент.
  • events — одно или нескольких разделенных пробелами типов событий.
  • handler — функция обработчик события.

off(el:HTMLElement, events:String, handler:Function):void

Удалить обработчик события.

  • el — DOM элемент
  • events — одно или нескольких разделенных пробелами типов событий.
  • handler — функции обработчика ранее назначения на event.

one(el:HTMLElement, events:String, handler:Function):void

Добавить функцию обработки события. Обработчик выполняется не более одного раза.

  • el — DOM элемент.
  • events — одно или нескольких разделенных пробелами типов событий.
  • handler — функция обработчик события.

dnd(el:HTMLElement, hover:Function, handler:Function):void

Добавить функцию обработки событий drag и drop.

  • el — DOM элемент
  • hover — dragenter и dragleave слушатель
  • handler — обработчик события drop
var el = document.getElementById('dropzone');
FileAPI.event.dnd(el, function (over){
    el.style.backgroundColor = over ? '#f60': '';
}, function (files){
    if( files.length ){
        // Загружаем их.
    }
});

// или jQuery
$('#dropzone').dnd(hoverFn, dropFn);

dnd.off(el:HTMLElement, hover:Function, handler:Function):void

Удалить функцию обработки событий drag и drop.

  • el — DOM элемент
  • hover — dragenter и dragleave слушатель
  • handler — обработчик события drop
// Native
FileAPI.event.dnd.off(el, hoverFn, dropFn);

// jQuery
$('#dropzone').dndoff(hoverFn, dropFn);

FileAPI.Image

Класс для работы с изображениями

constructor(file:Object):void

Конструктор получает только один параметр, файл.

  • file — файл изображения
FileAPI.Image(imageFile).get(function (err/**String*/, img/**HTMLElement*/){
    if( !err ){
        document.body.appendChild( img );
    }
});

crop(width:Number, height:Number):FileAPI.Image

Кроп изображения по ширине и высоте.

  • width — новая ширина изображения
  • height — новая высота изображения
FileAPI.Image(imageFile)
    .crop(640, 480)
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

crop(x:Number, y:Number, width:Number, height:Number):FileAPI.Image

Кроп изображения по ширине и высоте, а также смещению по x и y.

  • x — смещение относительно по x левого угла
  • y — смещение относительно по y левого угла
FileAPI.Image(imageFile)
    .crop(100, 50, 320, 240)
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

resize(width:Number, height:Number[, strategy:String]):FileAPI.Image

Ресайз.

  • width — новая ширина
  • height — новая высота
  • strategy — enum: min, max, preview, width, height. По умолчанию undefined.
FileAPI.Image(imageFile)
    .resize(320, 240)
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

// По большей стороне
FileAPI.Image(imageFile)
    .resize(320, 240, 'max')
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

// По заданной высоте.
FileAPI.Image(imageFile)
    .resize(240, 'height')
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

preview(width:Number[, height:Number]):FileAPI.Image

Кроп и ресайз изображения.

  • width — новая ширина
  • height — новая высота
FileAPI.Image(imageFile)
    .preview(100, 100)
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

rotate(deg:Number):FileAPI.Image

Поворот.

  • deg — угол поворота в градусах
FileAPI.Image(imageFile)
    .rotate(90)
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

filter(callback:Function):FileAPI.Image

Применить фильтр функцию. Только HTML5.

  • callback — принимает два рагумента, canvas элемент и метод done.
FileAPI.Image(imageFile)
    .filter(function (canvas/**HTMLCanvasElement*/, doneFn/**Function*/){
        // бла-бла-бла
        doneFn(); // вызываем по завершению
    })
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

filter(name:String):FileAPI.Image

Используется CamanJS, подключите его перед библиотекой FileAPI.

  • name — название CamanJS фильтра (произвольный, либо предустановленный)
Caman.Filter.register("my-funky-filter", function () {
    // http://camanjs.com/guides/#Extending
});

FileAPI.Image(imageFile)
    .filter("my-funky-filter") // или .filter("vintage")
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

overlay(images:Array):FileAPI.Image

Добавить наложение, например: водяной знак.

  • images — массив наложений
FileAPI.Image(imageFile)
    .overlay([
        // Левый угл.
        { x: 10, y: 10, w: 100, h: 10, src: '/i/watermark.png' },

        // Правый нижний угл.
        { x: 10, y: 10, src: '/i/watermark.png', rel: FileAPI.Image.RIGHT_BOTTOM }
    ])
    .get(function (err/**String*/, img/**HTMLElement*/){

    })
;

get(fn:Function):FileAPI.Image

Получить итоговое изображение.

  • fn — функция обратного вызова

FileAPI.Camera

Для работы с веб-камерой, обязательно установить параметр FileAPI.media: true.

publish(el:HTMLElement, options:Object, callback:Function):void

Публикация камеры.

  • el — куда публикуем
  • options — { width: 100%, height: 100%, start: true }
  • callback — первый параметр возможная ошибка, второй экземпляр FileAPI.Camera
var el = document.getElementById('cam');
FileAPI.Camera.publish(el, { width: 320, height: 240 }, function (err, cam/**FileAPI.Camera*/){
    if( !err ){
        // Камера готова, можно использовать
    }
});

start(callback:Function):void

Включить камеру

  • callback — будет вызван в момент готовности камеры
var el = document.getElementById('cam');
FileAPI.Camera.publish(el, { start: false }, function (err, cam/**FileAPI.Camera*/){
    if( !err ){
        // Включаем камеру
        cam.start(function (err){
            if( !err ){
                // камера готова к использованию
            }
        });
    }
});

stop():void

Выключить камеру


shot():FileAPI.Image

Сделать снимок с камеры

var el = document.getElementById('cam');
FileAPI.Camera.publish(el, function (err, cam/**FileAPI.Camera*/){
    if( !err ){
        var shot = cam.shot(); // делаем снимок

        // создаем предпросмотр 100x100
        shot.preview(100).get(function (err, img){
            previews.appendChild(img);
        });

        // и/или загружаем
        FileAPI.upload({
            url: '...',
            files: { cam: shot
        });
    }
});

Константы

FileAPI.KB:Number

1024 байт

FileAPI.MB:Number

1048576 байт

FileAPI.GB:Number

1073741824 байт

FileAPI.TB:Number

1.0995116e+12 байт


Utils

FileAPI.each(obj:Object|Array, callback:Function[, thisObject:Mixed]):void

Перебор объект или массив, выполняя функцию для каждого элемента.

  • obj — массив или объект
  • callback — функция, выполняется для каждого элемента.
  • thisObject — объект для использования в качестве this при выполнении callback.

FileAPI.extend(dst:Object, src:Object):Object

Объединить содержимое двух объектов вместе.

  • dst — объект, который получит новые свойства
  • src — объект, содержащий дополнительные свойства для объединения

FileAPI.filter(array:Array, callback:Function[, thisObject:Mixed):Object

Создает новый массив со всеми элементами, которые соответствуют условиям.

  • array — оригинальный массив
  • callback — функция для проверки каждого элемента массива.
  • thisObject — объект для использования в качестве this при выполнении callback.

Support

  • Multiupload: все браузеры поддерживающие HTML5 или Flash
  • Drag'n'Drop загрузка: файлы (HTML5) и директории (Chrome 21+)
  • Загрузка файлов по частям, только HTML5
  • Загрузка одно файла: все браузеры, даже очень старые
  • Работа с изображениями: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+
    • crop, resize, preview & rotate (HTML5 или Flash)
    • авто ориентация на основе EXIF (HTML5, если подключен FileAPI.exif.js или Flash)

FileAPI.support.html5:Boolean

Поддержка HTML5.

FileAPI.support.cors:Boolean

Поддержка кроссдоменных запросов.

FileAPI.support.dnd:Boolean

Поддержка Drag'n'drop событий.

FileAPI.support.flash:Boolean

Наличие Flash плагина.

FileAPI.support.canvas:Boolean

Поддержка canvas.

FileAPI.support.dataURI:Boolean

Поддержка dataURI в качестве src для изображений.

FileAPI.support.chunked:Boolean

Возможность загрузки по частям.


Flash

Флеш очень "глючная" штука :] Поэтому в случае успешной загрузки http status должен быть только 200 OK.

Settings

Настройки для flash части. Желательно, разместить flash на том же сервере, куда будут загружаться файлы.

<script>
    var FileAPI = {
        // @default: "./dist/"
        staticPath: '/js/',

         // @default: FileAPI.staticPath + "FileAPI.flash.swf"
        flashUrl: '/statics/FileAPI.flash.swf',

        // @default: FileAPI.staticPath + "FileAPI.flash.image.swf"
        flashImageUrl: '/statics/FileAPI.flash.image.swf'
    };
</script>
<script src="/js/FileAPI.min.js"></script>

crossdomain.xml

Обязательно создайте этот файл на сервере, куда будут загружаться файлы. Не забудьте заменить youdomain.com на имя вашего домена.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
    <site-control permitted-cross-domain-policies="all"/>
    <allow-access-from domain="youdomain.com" secure="false"/>
    <allow-access-from domain="*.youdomain.com" secure="false"/>
    <allow-http-request-headers-from domain="*" headers="*" secure="false"/>
</cross-domain-policy>

request

Пример запроса, который отправляет flash player.

POST /server/ctrl.php HTTP/1.1
Accept: text/*
Content-Type: multipart/form-data;
boundary=----------Ij5ae0ae0KM7GI3KM7
User-Agent: Shockwave Flash
Host: www.youdomain.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

[[..FILE_DATA_HERE..]]
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Upload"

Submit Query
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--

Security

По умолчанию FileAPI.flash.swf разрешает доступ с любых доменов Security.allowDomain("*"). Это может привести к уязвимости same origin bypass, если flash лежит на том же домене, что и критичные данные. Чтобы этого избежать, нужно разрешить доступ только к своим доменам здесь и пересобрать flash.


Server settings

IFrame/JSONP

<script>
(function (ctx, jsonp){
    'use strict';
    var status = {{httpStatus}}, statusText = "{{httpStatusText}}", response = "{{responseBody}}";
    try {
        ctx[jsonp](status, statusText, response);
    } catch (e){
        var data = "{\"id\":\""+jsonp+"\",\"status\":"+status+",\"statusText\":\""+statusText+"\",\"response\":\""+response.replace(/\"/g, '\\\\\"')+"\"}";
        try {
            ctx.postMessage(data, document.referrer);
        } catch (e){}
    }
})(window.parent, '{{request_param_callback}}');
</script>

<!-- or -->

<?php
    include './FileAPI.class.php';

    if( strtoupper($_SERVER['REQUEST_METHOD']) == 'POST' ){
        // Получим список файлов
        $files  = FileAPI::getFiles();

        // ... ваша логика

        // JSONP callback name
        $jsonp  = isset($_REQUEST['callback']) ? trim($_REQUEST['callback']) : null;

        // Ответ сервера: "HTTP/1.1 200 OK"
        FileAPI::makeResponse(array(
              'status' => FileAPI::OK
            , 'statusText' => 'OK'
            , 'body' => array('count' => sizeof($files))
        ), $jsonp);
        exit;
    }
?>

CORS

Включение CORS.

<?php
    // Permitted types of request
    header('Access-Control-Allow-Methods: POST, OPTIONS');

    // Describe custom headers
    header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type');

    // A comma-separated list of domains
    header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);

    if( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ){
        exit;
    }

    if( $_SERVER['REQUEST_METHOD'] == 'POST' ){
        // ...
    }

Chunked file upload

Всё общение между клиентом и сервером ведётся на уровне HTTP заголовков.
Для передачи отдельного chunk'а клиент устанавливает заголовки:

  • Content-Range: bytes <start-offset>-<end-offset>/<total>
  • Content-Disposition: attachment; filename=<file-name>

Другие заголовки не используются, отслеживание уникальности имени передаваемого файла не реализуется и оставлено на усмотрение разработчика.
В ответ на передаваемый chunk сервер может отвечать следующими кодами:

  • 200, 201 — chunk сохранён успешно
  • 416, 500 — восстановимая ошибка

Остальные коды — фатальная ошибка, требуется вмешательство пользователя.


Buttons examples

Base

Простой input[type="file"]

<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 input {
        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 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 input {
        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 name="photo" type="file" accept="image/*" />
</a>