Core IO

This section explains the dojo.io facilities that are provided by Core. Injecting dynamic SCRIPT tags to retrieve padded JSON and hacking IFRAME s into a viable transport layer are the central topics of discussion.

Using JSONP with Dojo

You know enough about Dojo by this point that you won't be surprised to know that it streamlines the work involved in implementing JSONP. To accomplish the same functionality as what was described in the primer, you could use dojo.io.script.get, which takes most of the same parameters as the various XHR methods. Notable caveats are that handleAs really isn't applicable for JSONP, and callbackParamName is needed so that Dojo can set up and manage a callback function to be executed on your behalf.

Here's an example of how it's done:

//dojo.io.script is not part of Base, so remember to require it into the page
dojo.require("dojo.io.script");

dojo.io.script.get({
  callbackParamName : "c", //provided by the jsonp service
  url: "http://example.com?id=23",
  load : function(response, ioArgs) {
    console.log(response);
    return response;
  },
  error : function(response, ioArgs) {
    console.log(response);
    return response;
  }
});

To clarify, the callbackParamName specifies the name of the query string parameter that is established by example.com. It is not the name of a function you've defined to act as a callback yourself. Behind the scenes, Dojo manages the callback by creating a temporary function and channeling the response into the load function, following the same conventions as the other XHR functions. So, just allow Dojo to remove that padding for you, and then use the result in the load function and be on your merry way.

Warning

If callbackParamName was not specified at all or was incorrectly specified, you'd get a JavaScript error along the lines of "<some callback function> does not exist" because the result of the dynamic SCRIPT tag would be trying to execute a function that doesn't exist.

Connecting to a Flickr data source

The following example illustrates making a JSONP call to a Flickr data source. Try running it in Firebug to see what happens. It is also worthwhile and highly instructive to examine the error that occurs if you don't provide callbackParamName (or misspell it):

dojo.require("dojo.io.script");
dojo.io.script.get({
   callbackParamName : "jsoncallback", //provided by Flickr
   url: "http://www.flickr.com/services/feeds/photos_public.gne",
   content : {format : "json"},
   load : function(response, ioArgs) {
    console.log(response);
    return response;
  },
  error : function(response, ioArgs) {
    console.log("error");
    console.log(response);
    return response;
  }
});

Getting back JavaScript from a JSONP call

As it turns out, you could also use dojo.io.script.get to interact with a server method that returns pure JavaScript. In this case, you'd perform the request in the same manner, except instead of providing a callbackParamName, you'd provide a checkString value. The "check string" value is a mechanism that allows for checking an in-flight response to see if it has completed. Basically, if running the typeof operator on the check string value does not return undefined, the assumption is that the JavaScript has completed loading. (In other words, it's a hack.) Assuming that you had CherryPy set up with the following simple script, you would use a checkString value of o to indicate that the script has successfully loaded, as o is the variable that you're expecting to get back via the JSONP call (and when typeof(o) != undefined, you can assume your call is complete).

First, the CherryPy script that serves up the JavaScript:

import cherrypy

class Content:
    @cherrypy.expose
    def index(self):
        return "var o = {a : 1, b:2}"

cherrypy.quickstart(Content(  ))

Assuming you have CherryPy running on port 8080, here's the corresponding Dojo to fetch the JavaScript:

dojo.require("dojo.io.script");
dojo.io.script.get({
  checkString : "o",
  timeout : 2000,
  url : "http://localhost:8080",
  load : function(response, ioArgs) {
    console.log(o);
    console.log(response)
  },
  error : function(response, ioArgs) {
    console.log("error", response, ioArgs);
    return response;
  }
});

Tip

Note that dojo.io.script.get introspects and determines if you're loading JavaScript or JSON based on the presence of either checkString or callbackParamName.

IFRAME Transports

Core provides an IFRAME transport that is handy for accomplishing tasks behind the scenes that would normally require the page to refresh. While XHR methods allow you to fetch data behind the scenes, they don't lend themselves to some tasks very well; form submissions, uploading files, and initiating file downloads are two common examples of when IFRAME transports come in handy.

Following the same pattern that the rest of the IO system has established, using an IFRAME transport requires passing an object containing keyword arguments, and returns a Deferred. IFRAME transports allow using either GET or POST as your HTTP method and a variety of handleAs parameters. In fact, you can provide any of the arguments with the following caveats/additions from Table 4-4.

Table 4-4. IFRAME transport keyword arguments

Name

Type (default)

Comment

method

String ("POST")

The HTTP method to use. Valid values include GET and POST.

handleAs

String ("text")

The format for the response data to be provided to the load or handle callback. Valid values include "text", "html", "javascript", and "json". For any value except "html", the server response should be an HTML file with a textarea element that contains the response.

content

Object

If form is another argument, then the content object produce the same result as if they had been hidden form elements. If there is no form property, the content object is converted to a query string via dojo.objectToQuery( ).

Tip

As of version 1.2, XML is also handled by the IFRAME transport.

File downloads with IFRAMEs

Because triggering a file download via an IFRAME is a common operation, let's try it out. Here's a CherryPy file that serves up a local file when you navigate to http://localhost:8080/. We'll use this URL in our dojo.io.frame.send call to the server:

import cherrypy
from cherrypy.lib.static import serve_file
import os

# update this path to an absolute path on your machine
local_file_path="/tmp/foo.html"

class Content:

    #serve up a file...
    @cherrypy.expose
    def download(self):
        return serve_file(local_file_path, "application/x-download", "attachment")

# start up the web server and have it listen on 8080
cherrypy.quickstart(Content(  ), '/')

Here's the HTML file that utilizes the IFRAME. You should be able to load it up, and, assuming you've updated the path in the CherryPy script to point to it, you'll get a download dialog when you click on the button.

Tip

The first time a call to dojo.io.iframe.send happens, you may momentarily see the IFRAME get created and then disappear. A common way to work around this problem is to create the IFRAME by sending off an empty request when the page loads, which is generally undetectable. Then, when your application needs to do a send, you won't see the side effect.

<html>
    <head>
        <title>Fun with IFRAME Transports!</title>

        <script type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.io.iframe");

            dojo.addOnLoad(function() {
                download = function(  ) {
                    dojo.io.iframe.send({
                        url : "http://localhost:8080/download/"
                    });
                };
            });
        </script>
    </head>
    <body>
        <button onclick="javascript:download(  )">Download!</button>
    </body>
</html>

Warning

In order to use the "Download!" button multiple times, you may need to supply a timeout value for the dojo.io.iframe.send function so that it can eventually time out and make itself available to service another request.

Form submissions with IFRAMEs

Another common use case for IFRAME s is submitting a form behind the scenes—maybe even a form that involves a file upload, which would normally switch out the page. Here's a CherryPy script that handles a file upload:

import cherrypy

# set this to wherever you want to place the uploaded file
local_file_path="/tmp/uploaded_file"

class Content:

    #serve up a file...
    @cherrypy.expose
    def upload(self, inbound):
          outfile = open(local_file_path, 'wb')
          inbound.file.seek(0)
          while True:
              data = inbound.file.read(8192)
              if not data:
                  break
              outfile.write(data)
          outfile.close(  )

          # return a simple HTML file as the response
          return "<html><head></head><body>Thanks!</body></html>"
# start up the web server and have it listen on 8080
cherrypy.quickstart(Content(  ), '/')

And here's the HTML page that performs the upload. If you run the code, any file you upload gets sent in behind the scenes without the page changing, whereas using the form's own submit button POSTs the data and switches out the page. An important thing to note about the example is that the handleAs parameter calls for an HTML response.

<html>
    <head>
        <title>Fun with IFRAME Transports!</title>

        <script type="text/javascript"
          src="http://o.aolcdn.com/dojo/1.1/dojo.dojo.xd.js"
          djConfig="isDebug:true,dojoBlankHtmlUrl:'/path/to/blank.html'">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.io.iframe");

            dojo.addOnLoad(function() {
                upload = function(  ) {
                dojo.io.iframe.send({
                    form : "foo",
                    handleAs : "html", //response type from the server
                    url : "http://localhost:8080/upload/",
                    load : function(response, ioArgs) {
                        console.log(response, ioArgs);
                        return response;
                    },
                    error : function(response, ioArgs) {
                        console.log("error");
                        console.log(response, ioArgs);
                        return response;
                    }
                });
            };
            });
        </script>
    </head>
    <body>
        <form id="foo" action="http://localhost:8080/upload/" method="post"
        enctype="multipart/form-data">
            <label for="file">Filename:</label>
            <input type="file" name="inbound">
            <br />
            <input type="submit" value="Submit Via The Form">
        </form>

        <button onclick="javascript:upload(  );">Submit Via the IFRAME Transport
        </button>
    </body>
</html>

The next section illustrates a caveat that involves getting back a response type that's something other than HTML.

Non-HTML response types

The previous example's server response returned an HTML document that could have been picked out of the response and manipulated. For non-HTML response types, however, there's a special condition that you must fulfill, which involves wrapping the response in a textarea tag. As it turns out, using an HTML document is the only reliable, cross-browser way that this transport could know when a response is loaded, and a textarea is a natural vehicle for transporting text-based content. Internally, of course, Dojo extracts this content and sets it as the response. The following example illustrates the changes to the previous example that would allow the response type to be plain text as opposed to HTML.

Tip

Note that while the previous examples for uploading and downloading files did not require the local HTML file to be served up by CherryPy, the following example does. The difference is that the IFRAME transport has to access the DOM of the page to extract the content, which qualifies as cross-site scripting (whereas the previous examples didn't involve any DOM manipulation at all).

The CherryPy script requires only that a configuration be added to serve up the foo.html file and that the final response be changed to wrap the content inside of a textarea like so:

import cherrypy
import os

# a foo.html file will contain our Dojo code performing the XHR request
# and that's all the following config directive is doing

current_dir = os.getcwd()
config = {'/foo.html' :
    {
    'tools.staticfile.on' : True,
    'tools.staticfile.filename' : os.path.join(current_dir, 'foo.html')
    }
}

local_file_path="/tmp/uploaded_file"

class Content:

    #serve up a file...
    @cherrypy.expose
    def upload(self, inbound):
          outfile = open(local_file_path, 'wb')
          inbound.file.seek(0)
          while True:
              data = inbound.file.read(8192)
              if not data:
                  break
              outfile.write(data)
          outfile.close(  )
          return
"<html><head></head><body><textarea>Thanks!</textarea></body></html>"

The only notable change to the request itself is that the handleAs type is different:

dojo.io.iframe.send({
    form : dojo.byId("foo"),
    handleAs : "text", //response type from the server
    url : "http://localhost:8080/upload/",
    load : function(response, ioArgs) {
        console.log(response, ioArgs); //response is "Thanks!"
        return response;
    },
    error : function(response, ioArgs) {
        console.log("error");
        console.log(response, ioArgs);
        return response;
    }
});

Manually creating a hidden IFRAME

As a final consideration, there may be times when you need to create a hidden IFRAME in the page to load in some content and want to be notified when the content finishes loading. Unlike the dojo.io.iframe.send function, which creates an IFRAME and immediately sends some content, the dojo.io.iframe.create function creates an IFRAME and allows you to pass a piece of JavaScript that will be executed when the IFRAME constructs itself. Here's the API:

dojo.io.iframe.create(/*String*/frameName, /*String*onLoadString, /*String?*/url)
//Returns DOMNode

Basically, you provide a name for the frame, a String value that gets evaluated as a callback, and an optional URL, which can load the frame. Here's an example that loads a URL into a hidden IFRAME on the page and executes a callback when it's ready:

<html>
    <head>
        <title>Fun with IFRAME Transports!</title>

        <script type="text/javascript"
            src="http://o.aolcdn.com/dojo/1./dojo/dojo.xd.js"
             djConfig="isDebug:true,dojoBlankHtmlUrl:'/path/to/blank.html'"
        </script>

        <script type="text/javascript">
            dojo.require("dojo.io.iframe");

            function customCallback(  ) {
                console.log("callback!");

                //could refer to iframe content via dojo.byId("fooFrame")...
            }

            create = function(  ) {
                dojo.io.iframe.create("fooFrame", "customCallback(  )",
                  "http://www.exmaple.com");
            }
        </script>
    </head>
    <body>
        <button onclick="javascript:create(  );">Create</button>
    </body>
</html>

Warning

Be advised that some pages have JavaScript functions in them that break them out of frames—which renders the previous usage of the transport ineffective.

Although you'll often immediately load something into an IFRAME, there may also be times when you need to create an empty frame. If you are using a locally installed toolkit, just omit the third parameter to dojo.io.iframe.create, and you'll get an empty one. If you are XDomain-loading, however, you'll need to point to a local template that supplies its content. There is a template located in your toolkit's directory at dojo/resources/blank.html that you can copy over to a convenient location. You also need to add an extra configuration parameter to djConfig before you try to create the IFRAME as shown in examples in this section.

Tip

In addition to the IO facilities provided by Core, DojoX also provides IO facilities through the dojox.io module. Among other things, you'll find utilities for XHR multipart requests and helpers for proxying.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.