Skip to content

Instantly share code, notes, and snippets.

@wegorich
Last active June 2, 2016 15:36
Show Gist options
  • Save wegorich/1681b7ff17054ebf8873794ef4aec5ed to your computer and use it in GitHub Desktop.
Save wegorich/1681b7ff17054ebf8873794ef4aec5ed to your computer and use it in GitHub Desktop.
samples form file uploader speach
$.ajax({
xhr: ()=> {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", (e)=> console.log(e.loaded / e.total) //progress
, false);
return xhr;
},
processData: false
});
.box__dragndrop,
.box__uploading,
.box__success,
.box__error {
display: none;
}
<form class="box" method="post" action="" enctype="multipart/form-data">
<div class="box__input">
<input class="box__file" type="file" name="files[]" id="file" data-multiple-caption="{count} files selected" multiple />
<label for="file"><strong>Choose a file</strong><span class="box__dragndrop"> or drag it here</span>.</label>
<button class="box__button" type="submit">Upload</button>
</div>
<div class="box__uploading">Uploading&hellip;</div>
<div class="box__success">Done!</div>
<div class="box__error">Error! <span></span>.</div>
</form>
'use strict';
;( function ( document, window, index )
{
// feature detection for drag&drop upload
var isAdvancedUpload = function()
{
var div = document.createElement( 'div' );
return ( ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ) ) && 'FormData' in window && 'FileReader' in window;
}();
// applying the effect for every form
var forms = document.querySelectorAll( '.box' );
Array.prototype.forEach.call( forms, function( form )
{
var input = form.querySelector( 'input[type="file"]' ),
label = form.querySelector( 'label' ),
errorMsg = form.querySelector( '.box__error span' ),
restart = form.querySelectorAll( '.box__restart' ),
droppedFiles = false,
showFiles = function( files )
{
label.textContent = files.length > 1 ? ( input.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', files.length ) : files[ 0 ].name;
},
triggerFormSubmit = function()
{
var event = document.createEvent( 'HTMLEvents' );
event.initEvent( 'submit', true, false );
form.dispatchEvent( event );
};
// letting the server side to know we are going to make an Ajax request
var ajaxFlag = document.createElement( 'input' );
ajaxFlag.setAttribute( 'type', 'hidden' );
ajaxFlag.setAttribute( 'name', 'ajax' );
ajaxFlag.setAttribute( 'value', 1 );
form.appendChild( ajaxFlag );
// automatically submit the form on file select
input.addEventListener( 'change', function( e )
{
showFiles( e.target.files );
triggerFormSubmit();
});
// drag&drop files if the feature is available
if( isAdvancedUpload )
{
form.classList.add( 'has-advanced-upload' ); // letting the CSS part to know drag&drop is supported by the browser
[ 'drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop' ].forEach( function( event )
{
form.addEventListener( event, function( e )
{
// preventing the unwanted behaviours
e.preventDefault();
e.stopPropagation();
});
});
[ 'dragover', 'dragenter' ].forEach( function( event )
{
form.addEventListener( event, function()
{
form.classList.add( 'is-dragover' );
});
});
[ 'dragleave', 'dragend', 'drop' ].forEach( function( event )
{
form.addEventListener( event, function()
{
form.classList.remove( 'is-dragover' );
});
});
form.addEventListener( 'drop', function( e )
{
droppedFiles = e.dataTransfer.files; // the files that were dropped
showFiles( droppedFiles );
triggerFormSubmit();
});
}
// if the form was submitted
form.addEventListener( 'submit', function( e )
{
// preventing the duplicate submissions if the current one is in progress
if( form.classList.contains( 'is-uploading' ) ) return false;
form.classList.add( 'is-uploading' );
form.classList.remove( 'is-error' );
if( isAdvancedUpload ) // ajax file upload for modern browsers
{
e.preventDefault();
// gathering the form data
var ajaxData = new FormData( form );
if( droppedFiles )
{
Array.prototype.forEach.call( droppedFiles, function( file )
{
ajaxData.append( input.getAttribute( 'name' ), file );
});
}
// ajax request
var ajax = new XMLHttpRequest();
ajax.open( form.getAttribute( 'method' ), form.getAttribute( 'action' ), true );
ajax.onload = function()
{
form.classList.remove( 'is-uploading' );
if( ajax.status >= 200 && ajax.status < 400 )
{
var data = JSON.parse( ajax.responseText );
form.classList.add( data.success == true ? 'is-success' : 'is-error' );
if( !data.success ) errorMsg.textContent = data.error;
}
else alert( 'Error. Please, contact the webmaster!' );
};
ajax.onerror = function()
{
form.classList.remove( 'is-uploading' );
alert( 'Error. Please, try again!' );
};
ajax.send( ajaxData );
}
else // fallback Ajax solution upload for older browsers
{
var iframeName = 'uploadiframe' + new Date().getTime(),
iframe = document.createElement( 'iframe' );
$iframe = $( '<iframe name="' + iframeName + '" style="display: none;"></iframe>' );
iframe.setAttribute( 'name', iframeName );
iframe.style.display = 'none';
document.body.appendChild( iframe );
form.setAttribute( 'target', iframeName );
iframe.addEventListener( 'load', function()
{
var data = JSON.parse( iframe.contentDocument.body.innerHTML );
form.classList.remove( 'is-uploading' )
form.classList.add( data.success == true ? 'is-success' : 'is-error' )
form.removeAttribute( 'target' );
if( !data.success ) errorMsg.textContent = data.error;
iframe.parentNode.removeChild( iframe );
});
}
});
// restart the form if has a state of error/success
Array.prototype.forEach.call( restart, function( entry )
{
entry.addEventListener( 'click', function( e )
{
e.preventDefault();
form.classList.remove( 'is-error', 'is-success' );
input.click();
});
});
// Firefox focus bug fix for file input
input.addEventListener( 'focus', function(){ input.classList.add( 'has-focus' ); });
input.addEventListener( 'blur', function(){ input.classList.remove( 'has-focus' ); });
});
}( document, window, 0 ));
import helpers from '../upload-helpers';
export const STATE_IN_PROGRESS = "In Progress";
export const STATE_PENDING = "Pending";
export const STATE_COMPLETED = "Completed";
export class FileDTO {
constructor(file, entryDTO: EntryDTO, done) {
this.file = file;
this.entryDTO = entryDTO;
this.folderDTO = entryDTO.folderDTO;
this.bites = entryDTO.folderDTO.bites;
this.strategy = this.folderDTO.strategy;
this.done = done;
}
static of(file, entryDTO, done) {
return new FileDTO(file, entryDTO, done);
}
getPath() {
return this.strategy.getPath(helpers.getNameSalted(this.file.name, this.entryDTO.times));
}
getName() {
return this.strategy.getName(this.file, helpers.getNameSaltedAndExt(this.file.name, this.entryDTO.times))
}
getType(){
return this.strategy.getType(this.file, this.isXLS);
}
}
export class EntryDTO {
constructor(data) {
Object.assign(this, data);
this.uploaded = 0;
this.errors = 0;
this.warnings = 0;
this.resumables = [];
this.progress = {
timestamp: new Date().getTime(),
timePerFile: 0,
timeLeft: 0
}
// var getDirName = (this.item.fullPath || this.item.webkitRelativePath).split('/');
// getDirName.splice(getDirName.length - 1, 1);
// this.dirName = getDirName.join('/');
}
static of(data) {
return new EntryDTO(data);
}
}
export class FolderDTO {
constructor() {
this.files = [];
this.folders = [];
this.totalFiles = 0;
this.foldersCount = 0;
this.resumables = [];
this.bites = { total: 0, perFile: 0 };
}
static of() {
return new FolderDTO();
}
}
import async from 'async';
import { EntryDTO } from './dto';
import moment from 'moment';
export class FolderFiles {
typeName = "";
regex = null;
//it's for just file picker upload, without drag and drop
addItem(item, metadata, obj) {
let name = item.relativePath || item.webkitRelativePath || item.name;
return EntryDTO.of({
item: item,
count: 1,
metadata: metadata,
ext: name.substr(name.lastIndexOf('.') + 1),
itemName: name,
folderDTO: obj
});
}
entriesToArray(list) {
return Array.prototype.slice.call(list || [], 0);
}
filterFiles(obj, folderDTO) {
var items = [];
for (let o in obj) {
o = obj[o];
items.push(this.addItem(o, null, folderDTO));
}
return items.filter((i) => i.item.size);
}
filterFile(entry, metadata) {
return (metadata ? metadata.size : entry.size) && (!this.regex || this.regex && this.regex.test(entry.relativePath || entry.webkitRelativePath || entry.name));
}
filterEntries(entries, obj = { foldersCount: 0, files: [], folders: [] }) {
entries.forEach((entry) => {
if (!entry) return;
if (entry.isFile) {
entry.getMetadata((metadata) => {
if (this.filterFile(entry, metadata)) {
obj.files.push(this.addItem(entry, metadata, obj));
}
});
}
if (entry.isDirectory) {
obj.foldersCount += 1;
obj.folders.push(entry.createReader());
}
});
return obj;
}
scanFolder(reader, entries) {
entries = entries || [];
reader.readEntries((results) => {
// This recursion, it works cause we receive just an links array
// We upload files one by one
// The uploaded one removed from memory
if (results.length) //read next 100 files
setTimeout(() => this.scanFolder(reader, entries.concat(this.entriesToArray(results))));
else {
this.filterEntries(entries, this.scanned);
this.scanNextFolder();
}
//fail callback
}, this.scanNextFolder.bind(this));
}
scanEntries(reader) { //fail callback
reader.readEntries(this.scanFolder.bind(this, reader), this.scanNextFolder.bind(this));
}
scanNextFolder() {
if (this.scanned.folders.length) {
this.scanEntries(this.scanned.folders.pop());
} else {
this.scanned.endCb(this.scanned);
}
}
scanEntriesSync(entries, folderDTO, endCb) {
this.scanned = this.filterEntries(entries, Object.assign(folderDTO, {
foldersCount: 0,
totalFiles: 0,
files: [],
folders: [],
endCb: endCb
}));
this.scanNextFolder();
}
scan(e, folderDTO) {
folderDTO = folderDTO || {};
return new Promise((resolve, reject) => {
if (e.target && e.target.files) {
resolve(Object.assign(folderDTO, {
files: this.filterFiles(e.target.files, folderDTO),
totalFiles: e.target.files.length,
foldersCount: 1
}));
}
if (e.dataTransfer && e.dataTransfer.items) {
//e.dataTransfer.items isn't enumerable
this.scanEntriesSync($.map(e.dataTransfer.items, (value, index) => [value]).map(e => e.webkitGetAsEntry()), folderDTO, (data) => {
delete folderDTO.folders;
delete folderDTO.endCb;
resolve(Object.assign(folderDTO, {
files: data.files,
totalFiles: data.files.length,
foldersCount: data.foldersCount
}));
});
}
});
}
getPath(fileName, folderPath) {
return this.path || `${folderPath}/${moment().format("YYYY/MM/DD")}`;
}
getName(file, saltedName) {
return saltedName;
}
getType(file) {
return file.type;
}
}
<form id="uploadForm"
enctype="multipart/form-data"
action="/api/potato"
method="post">
<input type="file" name="directory[]" webkitdirectory />
<input type="submit" value="Upload" />
</form>
<form id="uploadForm"
enctype="multipart/form-data"
action="/api/potato"
method="post">
<input type="file" name="potato-head" multiple/>
<input type="file" name="potato-head" />
<input type="submit" value="Upload">
</form>
throttle(callback) {
this.bitesPerSecond();
if (this.selectedFolder.bites.perSeconds <= parseInt(this.speedLimit))
callback();
else
setTimeout(callback, this.selectedFolder.bites.perSeconds / parseInt(this.speedLimit) * 1000);
}
uploadFileItem(entryDTO, file, callback) {
this.throttle(() => {
this.uploadFile.upload(FileDTO.of(file, entryDTO, () => callback()));
});
}
export class UploadFile {
constructor() {
this.baseUrl = './';
}
cleunUpResumble(resumable) {
resumable.cancel();
resumable.events.length = 0;
}
processFile(id, file, entryDTO, bites, callback) {
let folderDTO = entryDTO.folderDTO;
var resumable = new Resumable({
target: `${this.baseUrl}upload/files/${id}/chunks`,
forceChunkSize: true,
permanentErrors: [400, 404, 415],
simultaneousUploads: 1,
testChunks: false,
chunkSize: 7000000 //7 mb
});
resumable.on('fileSuccess', () => {
entryDTO.uploaded += 1;
this.helpers.updateFileETA(entryDTO);
bites.total += bites.perFile;
bites.perFile = 0;
this.cleunUpResumble(resumable);
callback && callback({ result: { id: id } });
});
resumable.on('fileProgress', (file) => {
this.helpers.calcETA(entryDTO, bites, {
lengthComputable: true,
loaded: resumable.getSize() * resumable.progress(),
total: file.size
})
if (entryDTO.metadata) {
this.helpers.updateFileETA(entryDTO);
bites.perFile = entryDTO.metadata.uploaded = resumable.getSize() * resumable.progress();
}
});
resumable.on('fileError', (file, message) => {
entryDTO.errors += 1;
entryDTO.retries = entryDTO.retries || 0;
this.helpers.updateFileETA(entryDTO);
bites.total += bites.perFile;
bites.perFile = 0;
entryDTO.timeout = 0;
this.cleunUpResumble(resumable);
callback && callback();
});
resumable.assignDrop({
addEventListener: function(t, l, u) {
l.call(this, {
stopPropagation: function() {},
preventDefault: function() {},
dataTransfer: {
files: [file]
}
});
}
});
folderDTO.resumables = folderDTO.resumables || [];
folderDTO.resumables.push(resumable);
setTimeout(() => resumable.upload(), 0);
}
}
let params = {
"meta": {
"fileName": file.name,
<-- some data //-->
"userId": this.user.id
},
"name": folder.path + file.name,
"type": file.type
};
$.ajax({
url: `/upload/files`,
type: 'POST',
data: JSON.stringify(params),
contentType: 'application/blabla',
dataType: 'json'
}).done(data => {
<-- Start chank uploading //-->
}).fail((jqXHR, textStatus, err) => {
<-- Make retry or log error logic //-->});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment