File uploading with Dojo

Professional’s need professional tools, which is one of the main reasons we started the Dojo project. Dojo’s I/O system is a prime example. From built-in caching (and cache busting) to making requests bookmarkable, the dojo.io.bind() abstraction makes doing Ajax easier than with any other client-side library. As I promised earlier I’m going to cover the memory leak prevention and file uploading support in dojo.io.bind().

As with DOM event handlers, IE gets easily confused about what it can and cannot clean up when you make XMLHTTP requests. The naive way of listening for the “finished” state in an asynchronous request is to attach a function to the XMLHTTP object’s onreadystatechange slot and determine inside the handler if the desired state has been reached. The problem though is the same as with DOM event handlers; since the XMLHTTP object in IE is an ActiveX control, IEs reference counting mechanism goes walkabout when presented with an event handler that is assigned from the JavaScript context. The contexts (closures) referenced by the event handler get leaked. Oops.

After much testing, we figured out a pattern that prevents this kind of leakage. Instead of attaching a method to be called back to, Dojo’s IO system starts a timer to watch for state changes on in-flight network requests. When a change is detected, we dispatch the correct event handler from *outside* the XMLHTTP object’s event system. Since we never set a closure reference between the ActiveX object and the JavaScript context, we never leak it. Of course, once there are no more operations “in the air”, we cancel the timer that watches for state changes to prevent further resource usage.

But wait! there’s more!

Dojo’s IO system not only handles hand-rolled requests, it will also submit entire forms in the background for you. Just pass in a reference to the form’s DOM Node in the formNode parameter and it gets automatically serialized into a request of the specified variety (either POST or GET). There’s no reason that adding Ajax to legacy apps should be harder than doing it in new development.

The big caveat-emptor, however, has always been uploading files. If you pass a form node to the default XMLHTTP back end for dojo.io.bind(), it will refuse to process the request since there’s no portable way to get the contents of a file on disk from inside the browser environment. But forms are different. Browsers let users explicitly specify which file to upload in a form and submitting these forms carries the encoded form content along for the ride. It’d be *really* handy if we could treat these forms the same way in an Ajax application.

As alluded to earlier, dojo.io.bind() supports “pluggable” back-ends. When a request is passed into bind, it asks each of the registered transport systems if they will accepted the request in turn. The first one to say “yes” gets passed the request to handle. In most cases, this is the XMLHTTP transport, but not for forms with file uploads. For these occasions, Dojo provides an “IframeIO” module. Since we can “target” a form to be submitted in whatever frame we please, the IframeIO class lets the browser process the request normally, but still in the background. Using it is as simple as adding the following line to your scripts:

dojo.require("dojo.io.IframeIO");

Now, passing the formNode parameter will work for both upload and non-upload cases. The XMLHTTP transport will still get used for most requests but when presented with a file upload, your code will now handle it gracefully, albeit with that *click* sound in the background when the form is submitted. A small price to pay for being able to add file-upload capability to single-page applications!

Next time: uploading files…without the file?

32 Comments

  1. lefakir
    Posted January 2, 2006 at 10:46 am | Permalink

    I don’t know if I am at the right place to expose my problem.

    With your technique, my file is correctly uploaded to the server. However I am unable to handle the data received by my “load” method.
    A simple load (t, d, e) {alert(d);} returns me an “undefined” value.

    The same form in the classic submit way returns me the correct response.

    The same form with dojo transport forced to xmlhhtp doesn’t send the file (this is correct) but the load method alerts my correct customized error message.

    Dojo is really powerfull but I wonder what’s wrong with my script :(

    Thanks for your good work and sorry for my poor english!

  2. Posted January 2, 2006 at 1:04 pm | Permalink

    So one of the changes that’s required to accommodate the iframe-based nature of the IframeIO system is that the return for the transport needs to be an HTML document. The contents of the first textarea element encountered in that document will be passed in as the data to your load() function.

    Hopefully that should get your app working the way you expect.

    Regards

  3. lefakir
    Posted January 3, 2006 at 2:56 am | Permalink

    Thank you.

    The load() function persists in containing “undefined” with your correction.
    I checked with the DOM inspector and the iframe has a node text with my server response. I think that I will recover (directly with DOM manipulation) the contents of this node to solve my problem.

  4. Posted January 3, 2006 at 7:29 am | Permalink

    that it is a text node and not an HTML document containing a element as I specified would indicate the root of the problem.

    Please see the test files at:

    http://download.dojotoolkit.org/release-0.2.1/dojo-0.2.1-ajax/tests/io/

    Regards

  5. Posted January 21, 2006 at 12:25 am | Permalink

    Yes, I had done the dom manuplation and get back the result on load like this:

    load: function(type, data, evt){
    // handle successful response here
    res = dojo.byId(“dojoIoIframe”).contentWindow.document.getElementById(“output”).innerHTML;
    alert(res);

    }

    On script i am outputting:

    echo ‘Date came from script…’;

    May be helpfull to someone who is new to dojo.

  6. Mark
    Posted February 13, 2006 at 3:52 pm | Permalink

    Hi Alex,

    The formNode with the Iframe works great for sending forms asynchronously.

    I would like to set request headers for the form I am sending so I don’t have to parse the entire output to get the information. The standard bind allows headers to be set by using and associative array for the “headers” property.

    How can I use this same functionality with IframeIO?

    Thanks in advance for any help you can give – Mark

    function submitForm(formx, retMimeType, retFunction) {

    var headersx=new Array();
    headersx[“Mark1″]=”TEST1”;
    headersx[“Mark2″]=”TEST2”;
    headersx[“Mark3″]=”TEST3”;
    headersx[“Mark4″]=”TEST4”;

    var bindArgs = {
    url: “ProcessFileUpload”,
    mimetype: retMimeType,
    formNode: formx,
    headers: headersx,
    method: “post”,
    handle: eval(retFunction)
    };

    // dispatch the request
    req=dojo.io.bind(bindArgs);
    return false;
    }

  7. Posted February 13, 2006 at 3:55 pm | Permalink

    Hey Mark,

    I’m not sure that it’s going to be possible to affect the headers when using IframeIO. = (

    Everything else should work as noted above.

    Regards

  8. Animal
    Posted February 14, 2006 at 8:02 am | Permalink

    Will all this work in an https page?

  9. Mark
    Posted March 2, 2006 at 8:21 pm | Permalink

    Hi Alex,

    I was able to get around my problem by using javascript to set a cookie. So when the request is sent by the Dojo iframe, the cookie goes with it.

    Just wanted to communicate the workaround.

    Thanks – Mark

  10. Anish Devasia
    Posted March 10, 2006 at 8:35 am | Permalink

    Alex,

    I am able to upload the file using iframe.That’s great and thanks.

    But after uploading file i am not able to handle the response ‘data’ some times it si giving undefined.But when i am using mimetype=”application/xml” it is giving ‘object’ but i am not able to manipulate this object using DOM.
    for mimetype=”plain/text” it is udefined.
    please suggest a solution.

  11. Jack
    Posted March 11, 2006 at 3:25 pm | Permalink

    Anish, take a look at comments #2 & #3. You will see that you cannot use an XML Response with file uploads. Rather you should generate an HTML response as follows:
    Your response here…
    Quoting alex:
    > The contents of the first textarea element encountered in that document will be passed in as the data to your load() function.

  12. Jonathan Gamba
    Posted March 25, 2006 at 9:53 pm | Permalink

    Hello, I am from Costa Rica, please excuse my bad English, I am trying to passing a form with files (Upload files) but I cant get any response, I saw the the last answers and I try the html response, but doesnt work, I really dont know why, I am using struts:

    function sendForm(){
    document.forms[‘mappedForm’].target = “dojoIoIframe”;
    var bindArgs = {
    url: “composeMail.do?method=doUpload”,
    formNode: document.forms[‘mappedForm’],
    mimetype: “text/html”,
    method: “post”,
    handler: function(type, data, evt) {
    alert(“handler: ” + data);
    },
    load: function(type, data, evt) {
    alert(“load: ” + data);
    },
    error: function(type, evt) {
    alert(‘error: ‘ + evt.message);
    }
    };

    var request = dojo.io.bind(bindArgs);
    }

    **The method in my struts action is call, that is fine but I can get any response :'(
    this is my struts response

    StringBuffer infoHtml = new StringBuffer();
    infoHtml.append(“\n” +
    “\t\n” +
    “\t\n” +
    “\t\n” +
    “\t Please work!!.\n” +
    “\t\t\n” +
    “\t Please…\n” +
    “\t\n” +
    “”);

    //Prepare the response
    byte [] data = infoHtml.toString().getBytes();

    response.setHeader(“Pragma”, “no-cache”);
    response.setHeader(“Cache-Control”, “no-cache”);
    response.setContentType(“text/html”);

    ServletOutputStream sos = response.getOutputStream();
    sos.write(data);
    sos.flush();
    sos.close();

    response.flushBuffer();

    **What is wrong with my code, please help me.

  13. Jonathan Gamba
    Posted March 25, 2006 at 9:57 pm | Permalink

    Me again in the strust response, I send two textArea fields, I guess the validator of the page remove that code. but I am sending them :).

  14. Jonathan Gamba
    Posted March 25, 2006 at 10:03 pm | Permalink

    Another thing :P , when I remove the formNode I received the responses perfectly, I have the problem only when try to pass the form with files.
    thanks

  15. Posted May 5, 2006 at 10:49 pm | Permalink

    Does it work with Safari? I tried the demo at http://download.dojotoolkit.org/release-0.2.2/dojo-0.2.2-ajax/tests/io/test_IframeIO.html and when I press Send It I get no response back (unlike IE and Firefox).

    I hope it does work on Safari :-)

  16. Posted May 9, 2006 at 4:47 am | Permalink

    Hi all,

    Samething here, upload works but the response is undefined.

    My upload page:

    header(“Content-Type: text/html”);

    if(move_uploaded_file($_FILES[‘test’][‘tmp_name’], $_SERVER[‘DOCUMENT_ROOT’].’/’.$_FILES[‘test’][‘name’]))
    {
    $retval = ‘ok’;
    }
    else
    {
    $retval = ‘ops’;
    }

    echo ”

    $retval

    “;

    Thanks for any help

  17. Posted May 9, 2006 at 4:51 am | Permalink

    html was removed from the post, but It’s the samething of the upload.cgi example

    $retval

  18. Posted May 9, 2006 at 5:21 am | Permalink

    If I set dojo mimetype with text/plain it shows:
    DEBUG: Type: load
    DEBUG: Response: undefined

    with text/javascript:
    DEBUG: Type: error
    DEBUG: Response: [object Object]

    and in this case I don’t know what to do with the object

  19. Posted May 13, 2006 at 11:06 pm | Permalink

    Phillip – There’s a bug with text/plain (causing it to return undefined) but you can work around it by extracting the IFrame contents manually – http://trac.dojotoolkit.org/ticket/674

  20. Posted June 12, 2006 at 9:23 am | Permalink

    Hi,

    I am trying to upload files … although I can easily get the response but the file upload does not work.

    server side code (in PHP) is as folllows

    header(‘Content-type: text/html’);

    $uploadDir = ‘./’;
    $uploadFile = $uploadDir.basename($_FILES[‘file_name’][‘name’]);

    if ( move_uploaded_file( $_FILES[‘file_name’][‘tmp_name’], $uploadFile) )
    {
    echo “”;
    echo “”.$_FILES[‘file_name’][‘name’].” uploaded successfully….”;
    echo “”;
    }
    else
    {
    echo “”;
    echo “”.$_FILES[‘file_name’][‘name’].” could not be uploaded successfully….”;
    echo “”;
    }

    client side is as follows

    dojo.require(“dojo.io.*”);
    dojo.require(“dojo.io.IframeIO”);

    var ctr = 0;
    var foo;

    function sendIt(){
    var bindArgs = {
    formNode: document.getElementById(“uploadForm”),
    mimetype: “text/html”,
    content: {
    increment: ctr++,
    fileFields: “ul1”
    },

    handler: function(type, data, evt){
    // handle successful response here
    if(type == “error”)
    {
    alert(“Unknown error occurred.”);
    }
    else
    {
    res = dojo.byId(“dojoIoIframe”).contentWindow.document.getElementById(“output”).innerHTML;
    alert(res);
    }
    // alert(data);
    }
    };
    var request = dojo.io.bind(bindArgs);
    }

    When I run this code I get “[filename] could not be uploaded successfully….” error.

    Please help me with this
    thanks in advance.

  21. jonathan
    Posted August 6, 2006 at 9:26 pm | Permalink

    Hi, I tried to upload a multipart form with file and got an IFrameTransport error, however the file is uploaded correctly onto the server. any idea why?
    Here’s my code:
    [code]
    function upload_file(formNode){

    var bindArgs = {
    url: “foo.php”,
    mimetype: “text/javascript”,
    error: function(type, errObj){
    // handle error here
    alert(“error “+errObj.message);
    },
    load: function(type, data, evt){
    // handle successful response here
    alert(“done”);

    },
    formNode: formNode
    };

    // dispatch the request
    var requestObj = dojo.io.bind(bindArgs);
    }
    [/code]

  22. Cakriwut
    Posted August 16, 2006 at 2:27 am | Permalink

    How do you partially upload a large file? Suppose you have 50 MB file on dialup link. Can we do partiall upload? ADODB.Stream is not the answer, because it open IE security hole. 

  23. john
    Posted August 19, 2006 at 4:53 pm | Permalink

    Can dojo check file size before uploading it?
    Yes, it is always possbile to check on server side, but bandwidth
    has been wasted.

  24. Posted August 23, 2006 at 2:30 am | Permalink

    Like many posting here, I had a great deal of trouble working out how to upload files. The documentation is not explicit enough. I’ve finally worked something out, and I have put the files I used on my website (though they don’t actually run from there – you’ll have to download them and install them on your own system). There is an HTML file and a PHP file.

    I found that to get the upload working requires several things:

    The form must contain these attributes: method=”post” enctype=”multipart/form-data”The script on the server must report that it is returning HTML – with php this is done as follows: header(‘Content-type: text/html’);The script then must return an HTML document, which contains a textarea. Place the actual message that you want delivered within the textarea.
    If you don’t do this you’ll get an error of some sort, such as an “IFrameTransportError”, or error messages from dojo.

    Gentle request to Dojo documenters – more live working examples please?

  25. Posted August 23, 2006 at 9:47 am | Permalink

    I don’t think dojo can check file size. But, we can use small activeX instead.

  26. Posted September 20, 2006 at 8:34 am | Permalink

    Great post Alex, thanks! Now I am wondering if anyone can shed some light on a debug error message that shows up.  At first I thought it was a bug with my code but when I try  http://download.dojotoolkit.org/release-0.3.1/dojo-0.3.1-ajax/tests/io/test_IframeIO.html I receive  this:

    DEBUG: Type: errorDEBUG: Response: [object Object]

    Which I can faithfully reproduce with my own code :)

    Oh, the file does get successfully uploaded.

  27. Posted October 31, 2006 at 2:10 am | Permalink

    Hi All,
            In Internet Explorer i could not able get all the style classes and fonts. Every time I login, I am getting errors as “This object cannot be loaded”. In my colleagues system it is showing all the syle classes related to it and fonts clearly. This problem didnt get resolved even if i reinstall my Internet Explorer. Can you please resolve this problem….
    Thanks,
    Ravindranath.

  28. MizardX
    Posted November 13, 2006 at 2:07 am | Permalink

    To retrieve text from an iframe you have to specify minetype: text/plain in the request AND return html with an textarea in the response. Also, IFrameIO seem to ignore the handler-function, so always use load().

    dojo.require(‘dojo.io.IFrameIO’);

    dojo.io.bind({
       formNode: dojo.byId(‘myForm’),
        mimetype: ‘text/plain’,
        load: function(type, msg) {
            window.alert(‘Success: ‘ + msg);
        }
        error: function(type, err) {
            window.alert(‘Failure: ‘ + err.message);
        }
    });

    <form id=”myForm” action=”myscript.php” method=”post”>
    <input type=”submit” />
    </form>

    And then the resonse have to look something like:

    <html>
    <body>
    <textarea>Hello World!</textarea>
    </body>
    </html>

    If you specify mimetype: text/html you will get the document-object itself.

    dojo.io.bind({

        formNode: dojo.byId( ‘myForm’ ),

        mimetype: ‘text/html’,

        load: function(type, doc) {

            window.alert( doc.getElementsByTagName( ‘textarea’ )[0].value );

        }
        error: function(type, err) {

            window.alert(‘Failure: ‘ + err.message);
        }

    });

  29. lasizoillo
    Posted January 18, 2007 at 4:42 am | Permalink

    Ticket info about ‘text/javascript’ bug.

  30. Nathan
    Posted February 27, 2007 at 4:54 pm | Permalink

    Hi Alex,

    I’m having an interesting problem related to IFrameIO. When I have a input of type=”file” and do an upload via IframeIO I get strange rendering problems in Firefox 2. After any IFrameIO action is carried out, switching to other tabs in my tab container causes the entire browser screen to flash white as certain widgets are rendered.

    I’ve narrowed it down to the presence of widgets created through script versus versus widgets defined in HTML markup in my test page. Only script created widgets cause the screen flash and it only happens in Firefox.

    Thank you,
    Nathan

  31. Petruha
    Posted March 16, 2007 at 4:06 am | Permalink

    Concurrent file upload problem!!!

    Hi, everybody!

      I’ve met strange problem using dojo to upload files. I have simple form with an input of type ‘file’, and using dojo.bind() to submit this form. It works perfect if I’m uploading a single file. But if I submit this form once, and right after that (don’t wait until the file is uploaded) select another file and submit the form again, I have next result: the first file is in fact uploaded, but second is not, and if after that I try to upload another file it fails as well without any notifications/errors.

      I tried to investigate this issue using sniffer and found that browser stucks and does not send any data in such situation.

  32. Dirk
    Posted January 18, 2009 at 4:33 pm | Permalink

    Note: This article is not valid for the current version of dojo. If you are getting an error like

    Could not load ‘dojo.io.*’; last tried ‘.io/*.js’
    or
    Could not load ‘dojo.io.IframeIO’; last tried ‘.io/IframeIO.js’

    it’s because you’re using a later version of dojo.

    For io with these versions, see this link:
    http://www.dojotoolkit.org/book/dojo-porting-guide-0-4-x-0-9/io-transports-ajax
    or
    http://www.dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/ajax-transports/alternate-transports

2 Trackbacks

  1. […] In a previous post I outlined how easy it is to upload files using Dojo’s pluggable I/O system. By including the IframeIO package, Dojo makes sending forms containing file upload elements is just as easy as making any other Ajax request. […]

  2. […] Alex has written up file uploading with Dojo, where he shows us that we can simply add dojo.require(“dojo.io.IframeIO”); to use the iframe IO transport, which is able to upload files. […]