I am building an asset manager using jQuery file upload and KnockoutJS. The trouble is that when I upload a set of several files, at seemingly random times I receive the exception
Uncaught Error: cannot call methods on fileupload prior to initialization; attempted to call method 'process'
in the middle of the add
callback for one of the files in the set.
Every time a file is added to the upload queue, I perform an AJAX request first in order to 'pre-register' the new asset on the server. Afterwards, I call the jquery.fileupload-process
plugin in order to perform client-side file validation. Simplified:
function add(ev, data) {
// perform an AJAX request through the Knockout view model
viewModel.addAsset().done(function(asset) {
data.asset = asset;
}).done(function() {
data.process(function () {
return $element.fileupload('process', data); // EXCEPTION OCCURS HERE
}).done(function() {
data.submit();
});
});
};
The issue seems to be more related to timing than anything else. With the same set of 20 files being uploaded, sometimes the error occurs on the fifth, seventh, fifteenth file — or not at all.
Here is the full code of the binding:
ko.bindingHandlers.uploader = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element),
options = ko.unwrap(valueAccessor()),
fileUploadConfig,
fileUpload,
doneHandler;
fileUploadConfig = {
dataType: 'json',
type: 'PUT',
url: '/assets', // will be overriden individually after processing
autoUpload: true,
sequentialUploads: true,
dropZone: options.dropzone,
acceptFileTypes: /(\.|\/)(jpe?g|png|mp3|aac|mp4)$/i,
maxFileSize: 250000000
};
fileUploadConfig.add = function add(ev, data) {
if (DEBUG) { console.assert(data.files.length === 1, "expected the add handler to be called with a single file, got " + data.files.length); }
var kind = data.files[0].type.split('/')[0],
file_name = data.files[0].name;
console.log("adding %s", file_name);
viewModel.addAsset(kind).done(function(asset) {
console.log("AJAX call complete, Knockout data model created for file %s", file_name);
data.asset = asset;
data.asset.upload_file_name(file_name);
data.asset.upload_status('waiting');
data.url = asset.resource_path;
}).done(function() {
data.process(function () {
console.log("calling processing plugin for file %s", file_name);
// EXCEPTION OCCURS HERE
return $element.fileupload('process', data);
}).done(function() {
console.log('processing done, submitting %s', file_name);
data.submit();
});
});
};
fileUploadConfig.done = function done(e, data) {
app.services.Asset.refresh(data.asset)
.done(function(asset) {
asset.upload_status('done');
});
};
fileUploadConfig.send = function send(e, data) {
console.log("sending file %s", data.files[0].name);
data.asset.upload_progress(0);
data.asset.upload_status('uploading');
};
fileUploadConfig.progress = function progress(e, data) {
if (data.asset.upload_status() !== 'uploading') {
return;
}
var percentage = parseInt(data.loaded / data.total * 100, 10);
if (percentage >= 100) {
data.asset.upload_status('processing');
data.asset.upload_progress(null);
} else {
data.asset.upload_progress(percentage);
}
};
fileUploadConfig.fail = function fail(e, data) {
console.log("failure file %s", data.files[0].name);
data.asset.upload_status('failed');
app.services.Base._defaultFailureHandler(data.jqXHR);
app.services.Base.failure422Handler(data.jqXHR);
};
console.log("initializing jQ fileUpload");
fileUpload = $element.fileupload(fileUploadConfig);
}
};
An example output of the console debug statements looks like this:
It makes absolutely no sense to me that jQuery file upload should suddenly be not initialized, after already having successfully processed files. This is my call stack at the time of the exception:
If I pause on exception, go to the frame were I am at return $element.fileupload('process', data);
and check on $element.data()
in the console, I receive
Object {bind: "uploader: {dropzone: $('.upload-manager-overlay')}"}
— jQuery file upload is of course still present.
Sorry for not providing a working fiddle, but I cannot provide the upload server endpoint required for this to work.
I would be grateful for any hints or further debugging recommendations on this.
Turns out I did something stupid: the <input type=file>
jQuery file upload was attached to was part of a Knockout-rendered info panel. This info panel was removed as soon as the first file was successfully transmitted.
jQuery file upload not surprisingly does not operate well bound to a DOM element that has been detached from DOM.