Your IP : 216.73.216.41
define([
'dojo/_base/lang',
'dojo/_base/declare',
'dojo/_base/Deferred',
'dojo/_base/array',
'dojo/_base/xhr',
'dojo/dom',
'dojo/dom-construct',
'dojo/dom-class',
'dojo/query',
'dojo/has',
'dojo/on',
'dojo/aspect',
'dijit/registry',
'dijit/Dialog',
'snet/DialogConfirm',
'dijit/ProgressBar',
'snet/fileUploader/ProgressBar'
], function(lang, declare, Deferred, array, xhr, dom, domConstruct, domClass, query, has, on, aspect, registry, Dialog, DialogConfirm, ProgressBar, UploadProgressBar) {
return declare(null, {
maxKBytes: 3000, // in kbytes limited by php.ini directive upload_max_filesize
maxNumFiles: 10, // limited by php.ini directive max_file_uploads
bytesOverall: 0,
barOverall: null,
fileStatus: {
numCompleted: 0, // number of completely uploaded files
numAborted: 0, // number of canceled files
numProgressDone: 0, // number of files where upload progress is 100%
numError: 0 // number of files with error
},
files: [], // files that will be uploaded after checking their length and max allowed number of uploads
progressBars: [], // keeps track of created bars
displayTarget: null, // If null, progress is displayed in a dialog, otherwise provide element id
dropTarget: null,
rememberConfirmDelete: false, // do not ask user again to confirm deleting
callback: false,
/**
* Instantiates the fileUploader.
* Expects object with following properties:
* id: // {String|Object} DomNode or id of element that the progress bars are created in.
* url: // {String} url of php page that handles the upload
* target: // {String|Object} DomNode or id of element where files can be dropped onto
*
* @param {Object} props arguments
*/
constructor: function(props) {
if (!this.hasFeatures()) {
this.confirmHasFeatures();
}
else {
this.append_div = props.append_div;
props.dropTarget = dom.byId(props.dropTarget);
lang.mixin(this, props);
this.maxKBytes *= 1048; // e.g. * (1024 + 24)
// add drag and drop events
on(window, 'dragover', function(evt) {
evt.preventDefault(); // necessary for dnd to work
});
on(window, 'drop', function(evt) {
evt.preventDefault();
});
on(this.dropTarget, 'dragenter', function() {
domClass.add(this, 'targetActive');
});
on(this.dropTarget, 'dragleave', function() {
domClass.remove(this, 'targetActive');
});
on(this.dropTarget, 'mouseout', function() {
domClass.remove(this, 'targetActive');
});
on(this.dropTarget, 'drop', lang.hitch(this, function(evt) {
var files = evt.dataTransfer.files;
this.reset();
this.addFiles(files);
domClass.remove(this.dropTarget, 'targetActive');
}));
}
},
/**
* Add and filter files to upload.
* Add files to internal array and calc total amount of bytes to upload. Also check for size and number of uploads limit.
* @param {Array} files instance of FileList object
*/
addFiles: function(files) {
var dfds = [], idx;
dfds[0] = new Deferred();
dfds[0].resolve(false);
// exclude files that are to large
// and chain deferreds so the get fired one after the other
this.files = array.filter(files, function(file) {
idx = dfds.length - 1;
var self = this;
if (file.size > this.maxKBytes) {
dfds[idx + 1] = dfds[idx].then(function(remember) {
var fileName = file.name || file.fileName;
if (!remember) {
return files.length > 1 ? self.confirmFileSize(fileName) : self.confirmFileSizeSingle(fileName);
}
else {
var dfd = new Deferred();
dfd.resolve(true);
return dfd;
}
});
return false;
}
else {
this.bytesOverall += file.size;
return true;
}
}, this);
// limit number of files you can upload
if (this.files.length > this.maxNumFiles) {
this.files = this.files.slice(0, this.maxNumFiles);
idx = dfds.length - 1;
dfds[idx + 1] = dfds[idx].then(lang.hitch(this, function() {
return this.confirmNumFileLimit(this.maxNumFiles);
}));
}
dfds[dfds.length - 1].then(lang.hitch(this, function() {
this.createBars();
this.uploadFiles();
dfds = null; // free memory
}));
},
/**
* Creates a progress bar for each file and uploads it.
*/
createBars: function() {
var self = this;
var container, div, strTemplate;
var barOverall = this.barOverall;
var i = 0, len = this.files.length;
if (len === 0) {
return false;
}
// create fileUploader container
strTemplate = '<div id="pbwOverallCont">' +
'<div id="pbwBarOverall" class="pbwBar">' +
'<div class="pbwTxt">Progression totale</div>' +
'</div>' +
'</div>' +
'<div id="pbwBarsCont"></div>';
// display inside dialog pane
if (!this.displayTarget) {
container = new Dialog({
id: 'snetUploader',
title: 'Transfert sur le serveur',
content: strTemplate,
duration: 500,
onHide: function() {
window.setTimeout(function() {
container.destroyRecursive();
}, container.get('duration') + 20);
}
});
container.show();
}
// display inside target element
else {
container = domConstruct.create('div', {
id: 'snetUploader',
innerHTML: strTemplate
}, this.displayTarget)
}
// create progress bar for overall progress
div = domConstruct.create('div', null, 'pbwBarOverall');
barOverall = new ProgressBar({
maximum: this.bytesOverall,
value: 0
}, div);
// create containers for individual progress bars
for (; i < len; i++) {
(function(i) {
var bar, file = self.files[i];
div = domConstruct.create('div', null, 'pbwBarsCont');
bar = new UploadProgressBar({
text: file.name + ', ' + self.formatSize(file.size),
bytesTotal: file.size,
maximum: file.size,
value: 0
}, div);
bar.setIcon(file, 64);
bar.watch('value', function(valName, valOld, valNew) {
var stat, inc;
if (valNew != Infinity && valOld != Infinity) {
inc = valNew - valOld + barOverall.get('value');
barOverall.set('value', inc);
}
else if (valNew == Infinity) {
stat = self.fileStatus;
stat.numProgressDone++;
if (stat.numProgressDone + stat.numAborted === len) {
barOverall.set('value', Infinity);
}
}
});
aspect.after(bar, 'onComplete', function() {
// todo: use bar.watch instead as above
var stat = self.fileStatus;
stat.numCompleted++;
if (stat.numCompleted + stat.numAborted === len) {
domClass.add(query('.dijitProgressBarTile', barOverall.domNode)[0], 'pbwBarTileDone');
barOverall.set('value', barOverall.maximum);
}
});
aspect.after(bar, 'onRetry', function() {
self.resume(file, this);
});
aspect.after(bar, 'onResume', function() {
self.resume(file, this);
});
aspect.after(bar, 'onPause', function() {
if (bar.xhr) {
bar.xhr.abort();
}
});
aspect.after(bar, 'onDelete', function() {
var dfd = self.del(file, bar);
dfd.then(function() {
self.progressBars[i] = null;
self.files[i] = null;
self.remove(container);
});
});
aspect.after(bar, 'onAbort', function() {
// we can't just use bar.xhr.abort(); since part of the file will still be written to disk on server
// => init abort on server first
self.abort(file, bar);
self.progressBars[i] = null;
self.files[i] = null;
});
aspect.after(bar, 'onRemove', function() {
self.remove(container);
});
self.progressBars[i] = bar;
})(i);
}
this.barOverall = barOverall;
},
/**
* Shows a dialog to confirm skipping files that are to big.
* @param {String} fileName name of file
* @return {dojo/_base/Deferred}
*/
confirmFileSize: function(fileName) {
var dialog = registry.byId('dialogFileSize') || new DialogConfirm({
id: 'dialogFileSize',
title: 'Confirm',
content: '<p>Maximum file size is limited to ' + this.formatSize(this.maxKBytes) + '.</p>' +
'<p>Press \'OK\' to skip file ' + fileName + '<br/>or press \'Cancel\' to cancel uploading.</p>'
});
return dialog.show();
},
/**
* Shows a dialog to confirm not uploading file that is to big (only one file overall).
* Used only when there is a single file to upload.
* @param {String} fileName name of file
* @return {dojo/_base/Deferred}
*/
confirmFileSizeSingle: function(fileName) {
var dialog = registry.byId('dialogFileSizeSingle') || new DialogConfirm({
id: 'dialogFileSizeSingle',
title: 'Confirm',
content: 'Maximum file size is limited to ' + this.formatSize(this.maxKBytes) + '. Uploading file ' +
fileName + ' will be canceled.</p>',
hasCancelButton: false,
hasSkipCheckBox: false
});
return dialog.show();
},
/**
* Shows a dialog to confirm skipping the remaining files.
* If the limit of number of files that can be uploaded is reached, the user can decide to skip the remaining files.
* @param {number} limit maximum number of files that can be uploaded
* @return {dojo/_base/Deferred}
*/
confirmNumFileLimit: function(limit) {
var dialog = registry.byId('dialogNumFileLimit') || new DialogConfirm({
id: 'dialogNumFileLimit',
title: 'Confirm',
content: '<p>Maximum number of files to upload is limited to ' + limit + '.</p>' +
'<p>Press \'OK\' to upload only the first ' + limit + ' files<br/>or press \'Cancel\' to cancel uploading.</p>',
hasSkipCheckBox: false
});
return dialog.show();
},
/**
* Displays a dialog to confirm deleting a file.
* @param {string} fileName name of file to delete
* @return {dojo/_base/Deferred}
*/
confirmDelete: function(fileName) {
var dialog = registry.byId('dialogDelete') || new DialogConfirm({
id: 'dialogDelete',
title: 'Delete',
content: '<p>Do you want to delete the uploaded file ' + fileName + ' on the remote server?</p>' +
'<p>Press \'OK\' to delete<br/>or press \'Cancel\' to cancel.</p>',
hasSkipCheckBox: true
});
return dialog.show();
},
/**
* Displays a dialog to confirm that current browser is not supported.
* @return {dojo/_base/Deferred}
*/
confirmHasFeatures: function() {
var dialog = registry.byId('dialogHasFeatures') || new DialogConfirm({
id: 'dialogHasFeatures',
title: 'Wrong browser',
hasCancelButton: false,
hasSkipCheckBox: false,
content: '<p>Your browser doesn\'t support HTML5 multiple drag and drop upload. Consider downloading Mozilla Firefox.</p>'
});
return dialog.show();
},
/**
* Removes the overall bar.
*/
remove: function(container) {
var someLeft = array.some(this.files, function(item) {
return item !== null;
});
if (!someLeft) {
window.setTimeout(function() { // TODO: check if this is really necessary
// since we might be in progress of already hiding another dialog
container.hide();
}, container.get('duration') + 20)
}
},
/**
* Upload all dropped files that don't exceed size and number of files limit.
*/
uploadFiles: function() {
var i = 0, len = this.files.length;
for (; i < len; i++) {
// this.saveToDb(this.files[i]);
this.upload(this.files[i], this.progressBars[i]);
}
},
/**
* Upload file via XmlHttpRequest.
* Reads file into binary string and uploads it while displays its progress.
* @param {File} file file to upload
* @param {snet.fileUploader.ProgressBar} bar progress bar
*/
upload: function(file, bar) {
// Use native XMLHttpRequest instead of XhrGet since dojo 1.5 does not allow to send binary data as per docs
var req = bar.xhr = new XMLHttpRequest();
var dfd = this.setReadyStateChangeEvent(req, bar);
this.setProgressEvent(req, bar);
bar.upload();
if(this.url.indexOf("?") != "-1"){
var elem_param="&";
}else{
var elem_param="?";
}
req.open('post', this.url + elem_param + 'fnc=upl', true);
req.setRequestHeader("Cache-Control", "no-cache");
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
req.setRequestHeader("X-File-Name", file.name);
req.setRequestHeader("X-File-Size", file.size);
req.send(file);
return dfd;
},
/**
* Displays upload status and errors.
* @param {XMLHttpRequest} req
* @param {snet.fileUploader.ProgressBar} bar
*/
setReadyStateChangeEvent: function(req, bar) {
var dfd = new Deferred();
on(req, 'readystatechange', lang.hitch(this, function() {
var err = null;
if (req.readyState == 4) {
// upload finished successful
if (req.status == 200 || req.status == 201) {
//window.setTimeout(function() {
bar.complete();
dfd.resolve();
if(this.append_div){
dojo.place(req.responseText,this.append_div);
}
//}, 500);
}
else {
// server error or user aborted (canceled)
if (req.status === 0 && (bar.aborted || bar.paused)) {
// User canceled or paused upload. Not an error.
dfd.resolve();
}
else {
err = {
statusCode: req.status,
statusText: req.statusText,
responseText: req.responseText
};
if (req.statusText == '') {
err.responseText = 'Unknown error.';
}
bar.error(err);
dfd.reject();
this.fileStatus.numError++;
}
}
req = null;
bar.xhr = null;
}
}));
return dfd;
},
/**
* Setup the progress event to display upload progress.
* @param {XMLHttpRequest} req
* @param {snet/fileUploader/ProgressBar} bar
* @param {number} [resumeStart]
*/
setProgressEvent: function(req, bar, resumeStart) {
var cnn = on(req.upload, 'progress', function(evt) {
var loaded = evt.loaded + (resumeStart || 0);
if (evt.lengthComputable) {
//var num = Math.round((evt.total - loaded) / evt.total * 1000);
var num = Math.round(loaded / evt.total * 100); // find better measure, see below
bar.set('value', loaded);
if (num == 100 && (!arguments.callee.done === true)) {
// TODO: find better ways to decide when we switch to indeterminate
// in FF4 never evt.loaded == bar.maximum, but in chrome
// see https://bugzilla.mozilla.org/show_bug.cgi?id=637002
arguments.callee.done = true;
cnn.remove(); // make sure this only gets called once per bar after upload is completed
bar.wait(); // upload is complete but file has not been written to disk, waits for status 200
}
}
});
},
/**
* Sends request to delete file on remote server.
* @param {string} fileName name of file to delete
* @param {snet/fileUploader/ProgressBar} bar
* @return {dojo/_base/Deferred}
*/
deleteFile: function(fileName, bar) {
return xhr.get({
url: this.url,
content: {
fnc: 'del',
fileName: fileName
},
failOk: true,
error: function(err, ioArgs) {
bar.error({
statusCode: ioArgs.xhr.status,
statusText: ioArgs.xhr.statusText,
responseText: err.responseText
});
}
});
},
/**
* Deletes the uploaded file and removes the bar from the list.
* @param {File} file
* @param {snet.fileUploader.ProgressBar} bar
*/
del: function(file, bar) {
var self = this;
var dfd = new Deferred();
var fileName = file.name || file.fileName;
dfd.resolve(self.rememberConfirmDelete);
dfd = dfd.then(
function(remember) {
if (remember === false) {
return self.confirmDelete(fileName);
}
}).then(
function(remember) {
self.rememberConfirmDelete = remember;
return self.deleteFile(fileName, bar);
}).then(function() {
bar.remove();
});
return dfd;
},
/**
* Aborts the upload.
* @param {File} file
* @param {snet.fileUploader.ProgressBar} bar
*/
abort: function(file, bar) {
if (bar.xhr) {
bar.xhr.abort();
}
this.fileStatus.numAborted++;
var inc = bar.maximum - bar.value;
this.barOverall.set('value', this.barOverall.value + inc);
window.setTimeout(lang.hitch(this, function() {
// after aborting some bytes of the file might still be written to the disk on the server,
// Unfortunately the delete request cant be sent right away, since there is some lag on the server
// (file is not written yet) and the delete would create a 404.
// Ugly, but I just do a the best guess of a 2sec delay to send the delete
this.deleteFile(file.name || file.fileName, bar);
bar.xhr = null;
bar = null;
}), 2000);
},
resume: function(file, bar) {
// Since we do not write anything to disk in this demo, we can't check the size of the partially written file
// on the server before continuing, instead we just use the local progress (which of course is not reliable, since
// we do not know how much was still sent and saved on the server -> in a realworld app use the extra xhr to server)
var dfd = null;
if (!this.hasBlobSliceSupport(file)) {
var dialog = registry.byId('dialogHasBlobSliceSupport') || new DialogConfirm({
id: 'dialogHasBlobSliceSupport',
title: 'Wrong browser',
hasCancelButton: false,
hasSkipCheckBox: false,
content: '<p>Your browser does not support resuming uploads.</p>'
});
dialog.show();
}
dom.byId(bar.id + '_msg').innerHTML = 'Resuming...';
// Get number of bytes from server which have already been written to the server
// This is not possible in demo, since nothing has been written to disk, server will just send nada
dfd = xhr.get({
url: this.url,
handleAs: 'json',
content: {
fnc: 'getNumWrittenBytes',
fileName: file.name || file.fileName
},
failOk: true,
error: function(err, ioArgs) {
bar.error({
statusCode: ioArgs.xhr.status,
statusText: ioArgs.xhr.statusText,
responseText: err.responseText
});
}
});
dfd.then(lang.hitch(this, function(result) {
// Blob.slice is temporarily prefixed in Firefox now, because of changed syntax from start, length to start, end
var dfd, chunk;
var start = (result && result.numWritten) || bar.value; // bar.value for demo only
var length = file.size - start;
var req = bar.xhr = new XMLHttpRequest();
if ('mozSlice' in file) {
chunk = file.mozSlice(start, file.size); // new parameters start, end instead of length
}
else {
chunk = file.slice(start, length);
}
dfd = this.setReadyStateChangeEvent(req, bar);
this.setProgressEvent(req, bar, start);
bar.upload();
if(this.url.indexOf("?") != "-1"){
var elem_param="&";
}else{
var elem_param="?";
}
req.open('post', this.url + elem_param + 'fnc=resume', true);
req.setRequestHeader("Cache-Control", "no-cache");
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
req.setRequestHeader("X-File-Name", file.name);
req.setRequestHeader("X-File-Size", file.size);
req.send(chunk);
return dfd;
}));
return dfd;
},
/**
* Resets the fileUploader.
*/
reset: function() {
this.bytesOverall = 0;
this.barOverall = null;
this.fileStatus = {
numCompleted: 0,
numAborted: 0,
numProgressDone: 0,
numError: 0
};
this.files = [];
this.progressBars = [];
},
/**
* Format file size.
* @param {Number} bytes
*/
formatSize: function(bytes) {
var str = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
var num = Math.floor(Math.log(bytes) / Math.log(1024));
bytes = bytes === 0 ? 0 : (bytes / Math.pow(1024, Math.floor(num))).toFixed(1) + ' ' + str[num];
return bytes;
},
/**
* Check if browser supports necessary features for the uploader to work.
*/
hasFeatures: function() {
// test taken from has.js
if (has.add('dnd', function(global, document, anElement) {
return 'draggable' in document.createElement('span');
}, true) && has.add('file-api', function(global, document, anElement) {
return typeof FileReader != 'undefined';
}, true) && has.add('native-xhr-uploadevents', function(global, document, anElement) {
return has("native-xhr") && ("upload" in new XMLHttpRequest);
}, true)) {
return true
}
else {
return false;
}
},
hasBlobSliceSupport: function(file) {
// file.slice is not supported by FF3.6 and is prefixed in FF5 now
return ('slice' in file || 'mozSlice' in file);
}
});
});