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.
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.
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; } });
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
.
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 |
---|---|---|
|
| The HTTP method to use. Valid values include GET and POST. |
|
| The format for the
response data to be provided to the load or handle callback.
Valid values include |
|
| If |
Tip
As of version 1.2, XML is also handled by the IFRAME transport.
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.
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.
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.