Thursday, October 8, 2009

Ajax File Upload Utility

This is the code release of the utility I have written to upload files to API through hidden iframes.
This is a mimic of Ajax style upload, but in reality it is a good old form submit through an iframe.

I have created a Demo for the same and you can download it from the link given below

Demo for Ajax File Upload

I create a new instance of the File Uploader Utility as

var configInstance = {
keepAlive: false,
        responseType: "json",
    }
    var uploader = new fileUploader(configInstance);

The config has values keepAlive which when set true will enable you to download content from the API where you submit the form with the new file content. This can be achieved by appropriate response type of the API.
Also note in any scenario the script will return the content of the response. The responseType is by default set to text or innerHtml you can set it to json or xml depending on the API.

You have following properties available to configure an instance
1. keepAlive : Useful when you want to start a download
2. responseType : default value is 'false' , returns innerHtml. Can be set to json or xml also.
3. formatTag : Allows you to specify the tag from which you want read the content in the response.Sometimes the json might be set inside a <pre> tag in the response html.

To start an upload you need to pass a second config object and the form id .In this example the form id is 'demoFile'.

var config = {


        onComplete: function(data, returnMeData){
            //do something here
        },
        onFailure: function(){
            //do something here
        },
        returnMe: {
            "message": "I am returnMeData"
        }
    }
    uploader.startUpload('demoFile', config);



The onComplete and onFailure callback methods are self explainatory.
The returnMe configuration can be used to pass an object to the utility. The utility will return the same as a parameter to the callback for onComplete as shown above.

All comments and feedback are welcome. Pasting the utility code below also. Cheers!!! :)

The code for the utility is as follows


/* Utility Code Start*/
function fileUploader(config){
    /*
     * Default value is false.
     * Set value as  "json" to get json response.
     * Set value as "xml" to get XML response
     * By default returns response as HTML of the screen
     */
    this.responseType = (config.responseType) ? config.responseType : false;
    /*
     * if the response is enclosed in a specific tag
     */
    this.formatTag = (config.formatTag) ? config.formatTag : "";
    /*
     * by default the value is set to true to keep the iframe alive after the operation is finished
     * comes in handy if you have to start a file download from the respone of the upload
     */
    this.keepAlive = (config.keepAlive) ? true : false;
    /*
     * @description Method to create the dynamic iframe
     * @scope Private to each instance
     *
     */
    function createFrame(c){
        //generate a random value
        var n = 'f' + Math.floor(Math.random() * 99999);
        //javascript:false; prevents the IE alert in secure connections
        var iframe = toElement('<iframe  src="javascript:false;" id="' + n + '" name="' + n + '"></iframe>');
        document.body.appendChild(iframe);
        iframe.style.display = 'none';
        if (c && typeof(c.onComplete) == 'function') {
            iframe.onComplete = c.onComplete;
        }
        if (c && typeof(c.onFailure) == 'function') {
            iframe.onFailure = c.onFailure;
        }
        if (c.returnMe != undefined) {
            iframe.returnBack = c.returnMe;
        }
        iframe.delNode = n;
        iframe.fired = false;
iframe.toDeleteFlag = false;
        return iframe;
    };
    /*
     * Helper Methods Start
     * @scope All are private to each instance created
     */
    function setTarget(f, name){
        f.setAttribute('target', name);
    };
    function toElement(html){
        var div = document.createElement('div');
        div.innerHTML = html;
        var el = div.childNodes[0];
        div.removeChild(el);
        return el;
    };
    function addEvent(el, type, fn){
        if (window.addEventListener) {
            el.addEventListener(type, fn, false);
        }
        else
            if (window.attachEvent) {
                var f = function(){
                    fn.call(el, window.event);
                };
                el.attachEvent('on' + type, f)
            }
    };
    /*Helper Methods Ends*/
    var other = this;
    /*
     * @scope Public
     * @description
     * @parameters f : id of form to be submitted for file Upload
     *   c : the configuration passed with the onComplete, onFailure and returnMe data
     * Sample of this c property
     * c = {
     * onComplete : function(){},
     * onFailure : function(){}, you will recieve the error object in the parameter here
     * returnMe : {prop1:"data1",prop2:"data2"}
     * }
     */
    this.startUpload = function(fID, c){
        try {
            var f = document.getElementById(fID);
            if (!f) {
                var e = new Error();
                e.description = "Form element not found";
                throw e;
            }
            var iframe = createFrame(c);
            var that = other;

            addEvent(iframe, "load", function(e){
                //function for capturing the load
                var loaded = function(){
                    try {
                        var response;
                        if (doc.XMLDocument) {
                            response = doc.XMLDocument;
                        }
                        else
                            if (doc.body) {
                                //convert result text to JSON format
                                if (that.responseType == 'json') {
                                    if (that.formatTag != "") {
                                        response = eval("result=" + doc.body.getElementsByTagName(that.formatTag)[0].innerHTML);
                                    }
                                    else {
                                        response = eval("result=" + doc.body.innerHTML);
                                    }
                                }
                                else
                                    if (!that.responseType) {
                                        response = doc.body.innerHTML;
                                    }
                            }
                            else {
                                // response is a xml document
                                response = doc;
                            }
                      
                        if (typeof(iframe.onComplete) == 'function') {
                            if (iframe.returnBack != undefined || iframe.returnBack != null) {
                                iframe.onComplete(response, iframe.returnBack);
                            }
                            else
                                iframe.onComplete(response);
                            // Reload blank page, so that reloading main page
                            // does not re-submit the post.
                            // delete the frame
                            iframe.toDeleteFlag = true;
                            //load event fired reset the iframe
//but we will not reset the iframe url to consider
//the fact that a file can be downloaded if keepAlive is true
//Please note the file download happens only if the response type of the
//url to which the form is submitted is correct
                            if (!that.keepAlive) {
                                iframe.src = "about:blank";
                            }
                        }
                    }
                    catch (e) {
                        //suppress
                    }
                };
                if (iframe.src == "about:blank") {
                    //dont delete on first read
                    if (iframe.toDeleteFlag) {
                        // Fix busy state in FF3
                        setTimeout(function(){
                            //keepAlive is true if its a file download
                            if (!that.keepAlive) {
                                document.body.removeChild(iframe);
                            }
                        }, 0);
                    }
                    return;
                }
                //Fix Opera multiple event firing
                if (iframe.fired) {
                    return;
                }
                var doc = null
                if (iframe.contentDocument) {
                    doc = iframe.contentDocument;
                }
                else
                    if (iframe.contentWindow) {
                        doc = iframe.contentWindow.document;
                    }
                    else {
                        doc = window.frames[iframe.id].document;
                    }
                //Opera fires onload twice.
                // Once when the content of the html is 'false'
                if (window.opera) {
                    if (doc.body && doc.body.innerHTML != "false") {
                        iframe.fired = true;
                        loaded();
                    }
                }
                else {
                    if (doc.body && doc.body.innerHTML != "false") {
                        iframe.fired = true;
                        loaded();
                    }
                }
            });
            //set the target of iframe and submit
            setTarget(f, iframe.id);
            f.submit();
        }
        catch (e) {
            //suppress all errors
            //fire onFailure method with the error object as param
            if (typeof(c.onFailure) == 'function') {
                c.onFailure(e);
                if (iframe) {
                    iframe.src = "about:blank";
                }
            }
        }
    };
};


/* Utility Code Ends*/

No comments: