Generating JavaScript from Perl

Dr. Dobb's Journal May 2002

Simple run-time code generation improves web-based UIs

By Stephen B. Jenkins

Stephen is the senior programmer/analyst at the Aerodynamics Laboratory of the Institute for Aerospace Research, National Research Council of Canada. He can be contacted at http://www.nrc.ca/~jenkins/.

As the senior programmer/analyst at the Canadian Aerodynamics Laboratory of the Institute for Aerospace Research, I decided several years ago to move away from proprietary, homegrown user interfaces, towards web-based systems whenever it seemed sensible to do so (see "Using Perl to Create Web-Based Software Tools for Wind Tunnel Testing," by S.B. Jenkins, American Institute of Aeronautics and Astronautics 2001-905, 39th Aerospace Sciences Meeting & Exhibit, January 2001). The main reason for this was to provide clients and staff of the 2mx3m wind tunnel with a more consistent and robust method of interacting with their experiments. Although most of the code that allows Web access is written in Perl, I opted to use JavaScript in places to increase responsiveness and convenience of the UI. Because JavaScript runs within a user's browser, it can have a significantly shorter response time than a CGI program running on a remote web server. Since web pages that contain this JavaScript code are dynamically generated by Perl programs, and since the contents of these pages are often based on information available only at run time, it follows that some of the JavaScript that manipulates these contents must also be created at run time.

In this article, I'll present three examples of run-time JavaScript generation, each taken from programs developed at the Laboratory, and each chosen to highlight a different application of the basic technique.

In the first example, I've used a few code snippets to show the dynamic generation of JavaScript function calls. I've included a demo program for the second example: the run-time generation of JavaScript functions that modify groups of HTML checkbox elements. In the third example, I'll discuss form validation in the browser, complete with pop-up alert boxes and dynamic images to prompt the user.

In addition to the primary goal of providing a better UI, a beneficial effect is the reduction of the load on web servers due to the reduced number of requests per user session. As well, the first example shows how the load on the client computer can be reduced through the reuse, rather than reload, of a complex web page.

Computers in the 2mx3m wind tunnel operate within the confines of an intranet model — outside access is severely restricted, and allowable hardware/software are tightly controlled. The techniques presented here assume this model, and I caution you to examine the suitability of these methods for your environments before writing any code. While nothing here is OS dependent, there are issues involving web browsers and JavaScript.

JavaScript Function Calls

The first example of code generation is the dynamic creation of JavaScript function calls. Figure 1 is a portion of a web page produced by a Perl program that I wrote to let users view — with a single button click — the contents of any of the data and configuration files for a given wind tunnel test. Because our tests routinely generate more than 4000 files, the time required to create, transmit, and render these pages is not insignificant (on the order of 10-15 seconds). This rather lengthy process would need to be repeated for each displayed file if the HTML buttons had been created to use the default submit action. Instead, I created them so that when pressed, each button initiates the execution of a JavaScript function through its onClick event handler. This function causes a new browser window to spring into existence, and it then requests the desired data file from a separate Perl CGI program (displayfile.pl) written to format and display the file's contents. This technique lets the web page, with the long list of buttons, remain intact in the original window, removing the need to recreate, retransmit, and rerender it.

Listing One is based on the Perl program that produces the buttons just described. To keep the example uncluttered, I've omitted incidental issues such as file paths, HTML table formatting, and so on. Since the list of data and configuration files was a snapshot of the filesystem taken when the user requested the web page, the buttons and their associated onClick function calls had to be generated at run time. Although this is the simplest of the examples I present, it clearly demonstrates all three benefits of run-time JavaScript generation — a more responsive UI, a reduction of the load on the web server, and a reduction of the load on the client computer.

Buttons that Manipulate Groups of Checkboxes

The web page in Figure 2 was generated by a program I wrote to let users initiate a search of data-system event logs (see "A System for Recording and Viewing Events in a Distributed Data Acquisition Environment," S.B. Jenkins, yapc99 (Yet Another Perl Conference) Proceedings, 1999). The name of the machine reporting the event is one of the search criteria that can be specified. Although I provide a checkbox to let all machine names be included in the search, it is common practice to require all but one or two names from the list. To make this process as convenient as possible, I decided to include a button that checks all boxes, letting users deselect individual machines. Similarly, a single button click is all that is required to clear all checkboxes. As in the previous example, I created these buttons to use the onClick event handler to call JavaScript functions, rather than cause a submit action. Because these checkboxes are generated at run time from the list of computer names in the "hosts" file on the web server, it follows that the code to manipulate them must also be generated at run time.

The Perl program in Listing Two demonstrates the generation of a group of checkboxes just described. To keep the example simple, I provide the list of checkbox names in an array (@machinenames), and perform only elementary formatting. I deliberately omit the process of extracting the names from a "hosts" file and modifying them for use in HTML elements, as it is not germane to this discussion. Although this case does not result in any significant reduction of the load on either the web server or client, it does provide a good example of the increase in user convenience, through the use of dynamically generated code.

Form Validation

In his book JavaScript: The Definitive Guide, Third Edition (O'Reilly & Associates, 1998), David Flanagan provides a good introduction to using JavaScript for carrying out form validation. Although Flanagan's example assumes traditional static programming, it is easy to adapt it for run-time generation by Perl CGI programs.

Figure 3 is a page generated by the web-based configuration file editor I wrote. The configuration files for the wind tunnel data system are made up of several lines, each with the following information: a parameter name, delimiter, description string, another delimiter, and current value. The description string contains a field type (dropdown or text) and constraints such as minimum value, maximum value, required entry, numeric characters only, and so on. For example, the "Run Number" entry in Figure 3 was constructed from this line in the configuration file:

Run Number; TEXT NUM REQ MIN=1 MAX=999: 255

At the time the web page was created, these constraints were used to generate the JavaScript validation code for each parameter. When the Submit Form button is pressed, that code is executed before actually submitting the form. An error string is built up from any entries that fail the validation process, and the user is notified of all problems in a single pop-up dialog box. To remove any potential ambiguity (or, to put it less delicately: "to make it idiot proof"), I added JavaScript code that uses a standard image-swapping technique to cause flashing red arrows to appear beside each of the offending entries. Once all input data has passed the validation process, the form is submitted to the CGI program. The result is that simple data input errors are caught in the browser, reducing turnaround time and network traffic.

Although form validation in JavaScript is provided as a convenience, the Perl program should revalidate the input data before it is used. This is necessary because malicious users can easily subvert the JavaScript processing, passing invalid data to the CGI program.

Conclusion

The primary benefit to be derived from this type of run-time code generation (RTCG) is an improved UI. At the Aerodynamics Laboratory, we feel that this reason alone justifies the increased complexity of the CGI programs. Still, there seem to be two major disadvantages of these RTCG techniques. The most serious is the increase in the skill set required of the programming staff. The complexity of writing programs that, in the same file, contain three languages (two programming and one markup) may seem daunting, but proper care and the separation of tasks make the process manageable. The second drawback is the increased difficulty in debugging the programs — one language can mask or hide errors from a process in another. The best way to handle this problem seems to be to make small, incremental changes during development, thus minimizing the scope of the search for erroneous code. As programmers gain experience with these methods, both become far less problematic. Overall, these dynamic code-generation techniques have played an increasingly important role in the development of new software at the Aerodynamics Laboratory, and I expect this trend to continue. Within the context of the data processing environment that exists at the Laboratory, the benefits of run-time code generation far outweigh the disadvantages.

DDJ

Listing One

# create a static JavaScript function that opens the new browser window
my $JSmakepage = <<EOF;
    function makepage(filename) {
        newwin = window.open('displayfile.pl?FILE=' + filename);
        newwin.focus();
    }    
EOF
# ... lots of missing code ...
# create buttons to use JS code, generating passed parameter dynamically
my $button = '<INPUT ' . 
             'TYPE="button" ' .
             'NAME= "' . $buttonname .  '" ' .
             'VALUE="' . $buttonvalue . '" ' .
             'onClick="makepage(\'' . $filename . '\')" ' .
             '>';

Back to Article

Listing Two

#!/usr/bin/perl -wT
use strict;
use CGI qw(:standard);

my $groupname = 'machines';
my @machinenames = qw( This should really be a list of computer names );

# generate the JavaScript and HTML
my $js = &createJSfuncs($groupname, @machinenames);
my $html = &createHTMLcheckboxes($groupname, @machinenames);

# send the JS and HTML to the browser
print header,
      start_html( -title  => 'DDJ checkbox example',
                  -script => {-language=>'JavaScript', -code=>$js} ),
      start_form(), 
      $html, 
      end_form(),
      end_html();

# create the JavaScript 'check all' and 'check none' functions
sub createJSfuncs {
    my $groupname = shift;      # the name of the checkbox group
    my @boxnames =  @_;         # the list of checkbox names
    my $all =  "function ${groupname}_all()  {\n";
    my $none = "function ${groupname}_none() {\n";

    foreach (@boxnames) {
        $all .=  "\tself.document.forms[0].$_.checked = true;\n";
        $none .= "\tself.document.forms[0].$_.checked = false;\n";
    }
    $all .= "}";
    $none .= "}";   
    return("\n$all\n$none\n");
}
# create the HTML for a group of checkboxes with all & none buttons
sub createHTMLcheckboxes {
    my $groupname = shift;      # the name of the checkbox group
    my @boxnames =  @_;         # the list of checkbox names
    my $checkboxgroup = '';

    foreach (@boxnames) {
        $checkboxgroup .= checkbox( -name=> $_ ) . "<BR>\n";
    }    
    $checkboxgroup .= button( -name    => "${groupname}allbutton", 
                              -value   => 'Check All',
                              -onClick => "${groupname}_all()" ) . "\n" .
                      button( -name    => "${groupname}nonebutton", 
                              -value   => 'Clear All',
                              -onClick => "${groupname}_none()" ) . "\n";
    return($checkboxgroup);
}

Back to Article