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.
, 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>
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.
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(){ $data .= $_;}
close DAT;
unlink($tmpInputFile, $tmpOutputFile);
print $q->textarea(-name=>'data', -default=>"$data");
print $q->end_html();
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");
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;
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 traffic
my $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.
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:
So now we need to tell dojo how to handle this form request. To do this, we will use dojo.io.bind():
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.
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
even an arbitrary number of) data sets, or to save data across sessions. For this we need dojo.storage