Only modern browsers have the ability to submit binary data over ajax to a server and even then it is a bit flimsy. You can use a method of posting file inputs to hidden iframes that perform the post submission to the server instead. This works correctly in all browsers that support iframes and file inputs. I have packaged this into a friendly Javascript class:
Javascript
Note: requires jQuery
function HiddenForm(args) {
args = args || {};
var defaults = {
method: "post",
enctype: "application/x-www-form-urlencoded",
action: "",
datatype: "html",
data: {},
done: function() {},
error: function() {},
ready: function() {}
};
for (var key in defaults) {
if (!args.hasOwnProperty(key)) {
args[key] = defaults[key];
}
}
this.args = args;
var targetId = "_hidden_iframe_" + HiddenForm.id++;
this.$div = $('<div class="ui_hidden"></div>');
this.$form = $('<form method="' + args.method +'" enctype="' + args.enctype + '" action="' + args.action + '" target="' + targetId +'"></form>');
this.$iframe = $('<iframe class="ui_hidden" id="' + targetId + '" name="' + targetId + '"></iframe>');
this.$div.append(this.$form, this.$iframe);
this.setData(args.data);
var self = this;
$(document.body).append(this.$div);
this.checkTimer;
}
HiddenForm.prototype.set = function(name, val) {
//this assumes that the data already exists in the form and we are just going to go and change the value now
var $elem = this.$form.find('[name="' + name + '"]');
if (HiddenForm.isFormElement(val)) {
$(val).prop("name", name);
$elem.replaceWidth(val);
} else {
$elem.val(val);
}
};
HiddenForm.prototype.setData = function(data) {
//fill the from with the provided data
for (var key in data) {
if (data.hasOwnProperty(key)) {
this.add(key, data[key]);
}
}
};
//allow the sending on the hidden form
HiddenForm.prototype.send = function() {
if (this.checkTimer) {
window.clearInterval(this.checkTimer);
this.checkTimer = null;
}
var self = this;
this.$iframe.one("load", checkDone);
this.$form.trigger("submit");
//this.$form.trigger("submit");
function checkDone() {
if (self.checkTimer) {
clearInterval(self.checkTimer);
self.checkTimer = null;
}
//read the contents of the iframe and return them as data to the user
var iframeDoc = self.$iframe.get(0).contentWindow.document.documentElement;
var returnObj;
switch (self.args.datatype) {
case "text":
returnObj = iframeDoc.textContent || iframeDoc.innerText;
break;
case "html": default:
returnObj = iframeDoc.innerHTML;
break;
case "json":
try {
returnObj = JSON.parse(iframeDoc.textContent || iframeDoc.innerText);
} catch (e) {
returnObj = false;
}
break;
}
self.args.done(returnObj);
}
};
HiddenForm.id = 0;
HiddenForm.formTags = ["input", "textarea", "select", "option"];
HiddenForm.isFormElement = function(val) {
return (typeof val.tagName == "string" && HiddenForm.formTags.indexOf(val.tagName.toLowerCase())>=0);
};
//let the user add data into the form (key/values)
HiddenForm.prototype.add = function(key, val) {
if (val instanceof jQuery) {
val = val.get(0);
}
//do this rather than check instanceof for maximum compatibility
if (!HiddenForm.isFormElement(val)) {
var $newInput = $('<input type="hidden" />');
$newInput.prop("name", key);
$newInput.val(val);
val = $newInput;
}
$(val).prop("name", key);
this.$form.append(val);
};
HiddenForm.prototype.abort = function() {
var iframeWindow = this.$iframe[0].contentWindow;
if (this.checkTimer) {
clearInterval(this.checkTimer);
this.checkTimer = null;
}
if (iframeWindow) {
if (typeof iframeWindow.stop !== 'undefined') {
iframeWindow.stop();
} else {
iframeWindow.document.execCommand("Stop");
}
}
this.args.error();
};
CSS
The following CSS is also required to 'hide' the elements so everything is behind the scene. Note that the hidden css does not use display:none or visibility:hidden. Using these properties will make the code not function.
.ui_hidden {
position: absolute;
top: -1000px;
left: -1000px;
width: 1px;
height: 1px;
opacity: 0.0;
}
Usage
After inserting the javascript class into a file or script tag and placing the css in the head of the document, you can use the following snippet of Javascript to allow the user to submit a file via the hidden iframe:
//create a file input to use here (could use one already on the DOM)
var $fileInput = $('<input name="files[]" type="file" />');
$(document.body).append($fileInput);
//data contains an object or names and inputs to use in the form
var data = { file: $fileInput };
//the url of the server script to use to do the upload
var targetUrl = "server-side upload script goes here";
var hiddenForm = new HiddenForm({
method: "post",
enctype: "multipart/form-data",
action: targetUrl,
datatype: "json",
data: data,
done: function(data) {
alert("Upload complete");
},
error: function() {
alert("Upload failed");
}
});
Make a comment
Contribute to this article and have your say.