Dojo Perl Tutorial
<html> <head> <title>Creating Asynchronous Web Interfaces for Command Line Tools Using Perl and Dojo</title> </head> <body>
Contents
Creating Asynchronous Web Interfaces for Command Line Tools Using Perl and Dojo
Introduction
Introducing Dojo
Creating a Wrapper with CGI.pm
Building an Asynchronous Interface with Dojo
Handling and Displaying Data
Introduction
</a>This tutorial will walk the developer through the process of creating an asynchronous web interface for a Unix command line tool using the dojo AJAX toolkit and perl. It assumes a working knowledge of perl and familiarity with basic web development techniques. Familiarity with dojo and CGI.pm is not assumed. Please send any comments, corrections or citicisms to the author.
This tutorial was written as part of the Google Summer of Code, 2007 under the mentorship of Ian Holmes with the support of NESCent. This work is licensed under a Creative Commons Attribution-Share Alike 3.0 License.
<a name="dojo">Introducing Dojo
</a>What is Dojo? From [www.dojotoolkit.com/about the Dojo website]:
<p>"Dojo allows you to easily build dynamic capabilities into web pages and any other environment that supports JavaScript sanely. You can use the components that Dojo provides to make your web sites more usable, responsive, and functional. With Dojo you can build degradable user interfaces more easily, prototype interactive widgets quickly, and animate transitions. You can use the lower-level APIs and compatibility layers from Dojo to write portable JavaScript and simplify complex scripts. Dojo's event system, I/O APIs, and generic language enhancement form the basis of a powerful programming environment."</p>
Essentially, dojo take the muss and fuss out of creating interactive web content. It provides the developer with a library of browser-agnostic functions addressing common problems, such as dynamically changing web content or communicating with the server asynchronously. While extremely powerful, the documentation can be a bit spotty. I highly recommend their <a href="http://dojo.jot.com/WikiHome/HelloWorld">Hello World tutorial<a>, which covers
this introductory material in more detail.For the purposes of this tutorial we will be using dojo 4.x.
I've found that the easiest way to use the toolkit is to install it under your webroot directory,
then create symlinks to the js directory under your various project directories, so that it's easily accessible. To
include dojo simply add the line <script type="text/javascript" src="js/dojo/dojo.js"></script>
in the header of your html document.
Now to create our first page using dojo. First we need to inlude the dojo modules we'll be needing. In the header include:
<script type="text/javascript"> dojo.require("dojo.widget.*"); dojo.require("dojo.event.*"); dojo.require("dojo.widget.Button"): dojo.require("dojo.widget.ContentPane"); </script>
The first two requires load the dojo functions related to widgets and events respectively. Notice however that we had to load each widget object individually, in this case Button and ContentPane. Button is exactly what it sounds like, a souped up button which can be handled easily with dojo. ContentPane is an object interface for handling divs with dynamic content, which will become useful for both displaying and holding data.
Writing HTML which incorporates dojo hooks is quite easy. To create our button and ContentPane we simply include the following in the body of our document:
<div widgetId="cpane" dojoType="contentPane">default content</div><br> <button dojoType="Button" widgetId="submitButton">submit</button><p>Next we need to write scripts to define the behavior of our button. Within the script tags in header include:
function init(){ var submitButton = dojo.widget.byId('submitButton'); //create object for submitButton dojo.event.connect(submitButton, 'onClick', 'pressed'); //set onClick event behavior } function pressed(){ var cpane = dojo.widget.byId('cpane'); //create object for cpane cpane.setContent("new content"); //set content to "new content" } dojo.addOnLoad("init");
This will create a page which will update itself automatically with "new content" whenever the submit button is pressed.
A few important notes: dojo.addOnLoad("init")
tell dojo to run the init function AFTER
the HTML has loaded correctly, so that the tags for our Button and ContentPane exist. Secondly,
dojo.event.connect(submitButton, 'onClick', 'pressed')
tells dojo what to do during submitButton's
onClick event, ie run function pressed
. This event.connect
statement is the "glue" which holds
our HTML and our functions together. Finally, variables are scoped within the function they are created in,
so you could not declare cpane
in init()
and access it in pressed()
.
So, this is neat, but not particularly useful at the moment. For dojo to really shine, we'll need something on the server with which we wish to communicate.
<a name="wrapper">Creating a Wrapper with CGI.pm
With CGI.pm, creating a wrapper for a command line tool is extremely easy. Since we will be using form
data in our HTML document to communcate with our server, we can access everything through the CGI
param()
function. A simple skeleton for a wrapper would be:
#!/usr/bin/perl use CGI; my $q = new CGI::; print $q->header(); print $q->start_html(); my $tempno = time; my $options; if ($q->param("param1")){$options .= " -a";} if ($q->param("param2")){$options .= " -b";} if ($q->param("param3")){$options .= " -c";} my $filehandle = $q->param("inputFile"); my $tmpInputFile = "InputTmp".$tempno; my $tmpOutputFile = "OutputTmp".$tempno; open DAT, ">$tmpInputFile"; while(<$filehandle>){ print DAT $_;} close $filehandle; close DAT; my $systemCall = "myApp $options $tmpInputFile $tmpOutputFile"; system($systemCall) or die; my $data; open DAT, "<$tmpOutputFile"; while(<DAT>){ $data .= $_;} close DAT; unlink($tmpInputFile, $tmpOutputFile); print $q->textarea(-name=>'data', -default=>"$data"); print $q->end_html();<p>Let's look at each part of this individually. Setting command-line options:
if ($q->param("param1")){$options .= " -a";} if ($q->param("param2")){$options .= " -b";} if ($q->param("param3")){$options .= " -c";}
On the HTML side we would want to represent these as checkboxes or radio buttons named param1, etc., allowing the user
to choose which options would be used. Note that param()
defaults to false, so that in
building a form you are free to ignore options which are irrelevant. param()
calls can
also be used to transport any other data your form might return, such as a file:
my $filehandle = $q->param("inputFile");
<p>param()
will behave differently depending on context here.
In a scalar context, $q->param("inputFile")
returns the name of the file sent by the client,
while in a filehandle context it will behave as a filehandle. In this particular instance we are interested
only in the filehandle context, though the scalar context may be useful if, for instance, we were
allowing the client to store data on the server. More information on the param()
function
as well as CGI.pm in general can be found in the excellent documentation
available through CPAN.
my $tmpInputFile = "InputTmp".$tempno; my $tmpOutputFile = "OutputTmp".$tempno;
<p>Here we are declaring our temporary filenames to store the data coming for the server and our system
call. Each has a timestamp attached to prevent collisions in the case of heavy trafficmy $systemCall = "myApp $options $tmpInputFile $tmpOutputFile"; system($systemCall) or die;
Constructing the system call for "myApp" and making it. "myApp" has to either be within $PATH as defined in /path/to/apache/bin/envvars, along with any ancilliatory Remember that whenever a system call is made in a CGI script we are giving the client access to our shell environment. We want this access to be as controlled as possible. In particular, avoid putting text submitted by the user directly on the command line, as this could potentially allow for an exploit. In general a call such as this one, processing a user-submitted file, is of little danger. However, if there is any doubt about the security of the application being called, or the call itself, discuss it with your systems administrator or someone else familiar with UNIX security before the potential for exploitation exists.
Finally, perhaps the most important part of this script, transmitting the information back to the client:
print $q->textarea(-name=>'data', -default=>"$data");
The iframe transport we will be using in the next section expects information from the server to be in the first text area of the returned HTML document, and will not function properly otherwise.
<a name="asynch">Building an Asynchronous Interface with Dojo
</a>Now, how do we modify our interface to interact with the wrapper we've created? Dojo makes this quite easy. The first step to is to create a form for our input:
<form id='input' action='POST' enctype='multipart/form-data'> <input type='checkbox' name='param1'>Option -a<br> <input type='checkbox' name='param2'>Option -b<br> <input type='checkbox' name='param3'>Option -c<br> inputfile:<input type="file" name="inputFile"><br> </form>
Next, we need to include the dojo modules for AJAX transport:
dojo.require("dojo.io.*"); dojo.require("dojo.io.IframeIO");
By default dojo does uses XMLHTTP transport, which can not handle file uploads like ours. So passing
files to the server will not fuction properly unless dojo.io.IframeIO
is explicitly required.
However, once it is required, dojo is smart enough to figure out the correct transport for any particular
request.</p>
So now we need to tell dojo how to handle this form request. To do this, we will use dojo.io.bind()
:
function pressed(){ var cpane = dojo.widget.byId('cpane'); //create object for cpane dojo.io.bind({ url: 'http://path/to/myWrapper.pl', formNode: dojo.byId("input"), method: "POST", load: function(type, data, evt) { cpane.setContent(data); }, error: function(type, error) { alert("Error: " + error); } }); }
<p>dojo.io.bind
takes as an argument a JSON object containing the information dojo needs
to properly handle a request to the server. You will notice in this case the action attribute normally
declared within the HTML form tags has been shifted here - this is entirely optional. I find it easiest
in debugging to have all the information about the request I have specified within the io.bind
call, you are free to keep this information within the HTML if you choose. The salient parts of
dojo.io.bind
's arguments are the load:
and error:
pairs. The
Handling and Displaying Data
</a>I've divided this part of the tutorial into sections, as each technique can be applied individually, and not all of these will be relevant to every project. This section is by no means a complete survey of dojo data-wrangling, but more of a collection of starting points.
Storing Data Client-side
<p>For displaying or temporarily holding small sets of data, ContentPane wil suffice. The can be hidden with the dojo
dojo.html.hide()
function, using the dojo object to be hidden as the argument. However this
technique can quickly become tedious and impractical for pages which need to deal with multiple (or
dojo.storage
more to come!
-- Lars Barquist - 05 Sep 2007 <body> </html>