Nombas Logo

  • Return to main SE:WSE page
  • Introduction
  • Installation instructions for ScriptEase:WebServer Edition
  • A description of the Javascript language
  • A tutorial for some ScriptEase:Webserver Edition demos
  • A description of the Clib object
  • A description of the SElib object
  • CGI scripting with Javascript
    This chapter provides a tutorial on CGI scripting with ScriptEase:WebServer Edition. For additional information on CGI, you may purchase one of the many books currently available on CGI programming.

    A CGI script is one that receives and processes data sent with a web page, or creates a web page and sends it back to the client who requested it. Although it is called via the same mechanisms for calling web pages, instead of sending back a static (pre-written) HTML document, it creates the document afresh.

    Activating a script
    On the user's end, web pages and scripts are both accessed in the same way, either by clicking on a link or on a form's submit button moves you to then next pager. On the server's end, however, the two are very different: A web page implies a static document that the server simply sends off; a script generates the page and then sends it out. Both of these methods use a URL to reference the script or document. However, a script's URL must refer to both the script itself and to the ScriptEase processor that runs it: first the URL of the processor, followed by a question mark to separate the two URLs, and finally the URL of the script.

    For example,

         "http://www.nombas.com/cgi-bin/sewse.nlm?C:/foo.jse"
    refers to a script called "foo.exe" residing in the root directory of the server's C: drive.

    The URL is included on a web page as an address given in an anchor tag's HREF attribute (when the script is activated via a link) or with a form's ACTION attribute (when the script is activated with a form's submit button).

    Passing data to a CGI script
    Data is passed to a CGI script in two ways: directly as parameters or as input from an HTML form. Data is always passed and received as strings. These two methods for passing data are not mutually exclusive and may be used together.

    Parameters
    Data may be passed as a parameter to a cgi script by putting the data at the end of the script's URL, after a `+' sign. This method of passing data may only be used if the data is a constant value, i.e., the same data will be passed every time the script is run. Two links accessing the same script may pass different values, but the parameter data associated with a link or form will not change for that link.

    For example, suppose you have a script that is referenced by several pages, but works slightly differently depending on the page that accesses it. In this case you need to pass a parameter to the script telling it which page referenced it. This may be done as follows.

      <A HREF="http://www.foo.com/bin/sewse.nlm?c:/foo.jse+homepage>
      <A HREF="http://www.foo.com/bin/sewse.nlm?c:/foo.jse+newspage>
    In this case, either "homepage", or "newspage" will be passed to foo.jse as the first command line argument, depending on which link was selected. The value will be passed to the script as a string and stored as argv[1] (local to the script's main function), or _argv[1] (a global variable). For example,
         referingPage = _argv[1];
    and
         main(argc,argv)
         {
           referingPage = argv[1];
         }
    will both get the page name (homepage or newspage, depending on which link accesses it) and store it in the variable referingPage. If you need more information on argv, read the Javascript reference chapter.

    Form input
    The URL of a script may be passed as the ACTION attribute of a HTML <FORM> tag. If the user presses the form's submit button, all data entered in the fields of the form will be passed to the script. You may also pass constant parameters to the script as described above.

    The function cgi.getVar() retrieves the form values from the script: pass in the name of the variable you are trying to access, and CGI.getVar will return the value of the variable. (The name of a CGI variable is determined by the NAME attribute in the HTML docutment). For example:

         CGI.getVar("NAME");
    will retrieve the value from the HTML form's name field. If the variable is unable to get the value of the variable, it returns NULL. If you are retrieving data from a text field, the text will be returned. If you are retrieving the value of a checkbox, "ON" will be returned if the box was checked, and NULL will be returned if it was not. If you are retrieving the value of a set of radio buttons, the VALUE attribute of the checked button will be returned, or NULL if none of the buttons is checked.

    cgi.getVar() may also be called with no parameters, in which case it returns an array of all valid variable names.

    Sending Output with CGI.out(htmlString)
    Once you have extrated a variable's values and processed the data, you must make an HTML document and return it to the server. This is done by building an HTML file as a long string (or series of strings) and returning it to the client. The file to be returned is build up out of calls to cgi.out(). The first call to CGI.out() creates a buffer to contain the HTML page and writes string htmlString to it. Each subsequent call to CGI.out appends the contents of htmlString to the CGI output buffer. When the script terminates, the buffer is sent back to the client.

    To clear the CGI output buffer, call cgi.out(null). This will erase the entire contents of the buffer.

    The first portion of the CGI output buffer must contain the content type of the document being returned. For HTML documents, this looks like:

         cgi.out("Content-Type: text/html\r\n\r\n");
    This example generates a simple HTML document:
         cgi.out("Content-Type: text/html\r\n\r\n");
         cgi.out("<HTML>");
         cgi.out("<HEAD>");
         cgi.out("<TITLE>ScriptEase HTML Example</TITLE>");
         cgi.out("</HEAD>");
         cgi.out("<BODY>");
         cgi.out("<H2>Simple Messages are best!");
         cgi.out("</BODY>");
         cgi.out("</HTML>");
    A simple FORM example
    This script processes the data input on the following form:


    A simple form, with fields filled in

    When a person clicks on the "SUBMIT" button, the data entered into the fields is passed to the script as indicated by the ACTION attribute of the FORM. When the page is submitted, an HTML document containing the following text is returned:

    Julio Iglesias, we will be sending information on goats and elephants to the following email address: julio@smarmy.com.
    If the animal trainer button had been checked, there would be an additional sentence:
    I see you are a professional animal trainer. Contact us about employment opportunities.
    Next we will present a script that processes this data into the above format. It is broken down into three parts: a series of statements that retrieve the data from the form, a section that formats the data, and a section that passes the data back to the client as an HTML document. There is one function declared, ErrorMessage, it is called if the script is unable to find one of the form data fields it needs.

    The first block of statements receive the input data from the ScriptEase interpreter using cgi.getVar(). The values are stored in Javascript variables. In some cases if the variable was not found(cgi.getVar returned NULL) default values are set. For checkboxes (pigs, goats and elephants), the corresponding Javascript variables are initialized to empty strings. If neither radio button is checked, this script assumes that the person meant to check NO.

    The next series of statements construct a string from the selected checkbox values. In other words, if both pigs and elephants are checked, the string "pigs and elephants" will be added to the HTML output. It uses two functions, Clib.strcmp, for comparing strings, and Clib.strcat, to add one string on to another. These functions are documented in Chapter 6, the Standard ScriptEase Library.

    The calls to cgi.out return the newly generated HTML document to the client. The first line identifies the document as an HTML document. If it is omitted, the browser may not recognize the document and may be unable to display it. This line is also in the error message function which creates a simple HTML document, containing the error message. The next two lines output the standard tags that begin any HTML document, followed by the name entered into the NAME field and the string "we will be sending you information on," followed by animalstring. The final if statement checks to see if the radio button value is set to "YES", and tells the user to contact us about animal trainer opportunities if it is. The standard HTML closing tags are added at the end. All the calls to cgi.out work together to produce the text of the HTML document being sent. All of the data is sent when this script terminates.

      // Print an error message if there is a problem. 
      // Name and EMAIL are required
      if( !(Nametext = cgi.getVar("NAME")))   ErrorMessage("NAME");
      if( !(Emailtext = cgi.getVar("EMAIL")))   ErrorMessage("EMAIL");
      // If pigs, goats, or elephants aren't found set them to an
      // empty string
      if( !(pigcheck = cgi.getVar("PIGS")))   pigcheck="";
      if( !(goatcheck = cgi.getVar("GOATS")))   goatcheck="";
      if( !(elephantcheck = cgi.getVar("ELEPHANTS"))) elephantcheck="";
      // if they don't select a probutton, assume they meant no
      if( !(Probutton = cgi.getVar("PRO")))   Probutton="NO";
      // Format animalstring 
      // Clib.strcmp returns 0 if strings are identical.
      Clib.strcpy(animalString,""); // Initialize animalString
      // see if pig needs to be added
      if (Clib.strcmp(pigcheck, ""))  animalstring +" pigs";
      // see if goat needs to be added
      if (Clib.strcmp(goatcheck,"")) 
      {
       if (Clib.strcmp(pigcheck, "")) animalstring + " and";
       animalstring + " goats")
      }
      // see if elephant needs to be added
      if (Clib.strcmp(elephantcheck, ""))
      {
        // check to see if and is needed
        if(Clib.strcmp(goatcheck, "") || Clib.strcmp(pigcheck,""))
          animalstring + " and";
        animalstring + " elephants";
      }
      // Send an HTML document back to the client using cgi.out()
      cgi.out("Content-Type: text/html\r\n\r\n");
      cgi.out("<HTML><HEAD><TITLE>Form Return</TITLE></HEAD><BODY>");
      cgi.out(nametext);
      cgi.out(", we will be sending information on");
      cgi.out(animalstring);
      cgi.out(" to the following email address: ")
      cgi.out(emailtext);
      if !Clib.strcmp(Probutton, "YES"){
       cgi.out("<P>You are a professional animal trainer! Contact us");
       cgi.out("about employment opportunities!</P>");
      }
      cgi.out("</BODY></HTML>");
      // Output an error message as an HTML document
      ErrorMessage(ErrorVariable)
      {
       cgi.out("Content-Type: text/html\r\n\r\n");
       cgi.out("<HTML><HEAD><TITLE>Error Message</TITLE>");
       cgi.out("</HEAD><BODY>Error accessing ");
       cgi.out(retPrintf("%s.</BODY></HTML>",ErrorVariable));
       Clib.exit(0);
      }
    In this example the data is stored in strings (created with the Clib.strcat() function) before being output to the client. This is a convenient way to build a complex output string before calling cgi.out.

    A more complex form - Alive in 1880 - a quiz
    This example uses a form to create a quiz. The first part of this script consists of #include statements that allow the script to use tinyhtml.jsh and html.jsh. These libraries contain functions to make cgi.out statements easier, with pre-formatted functions for most of the standard HTML tags.

    The include statements are followed by a list of the people included in the quiz.

      people = {
      {"BARTOK","DEAD", "Bela Bartok, Hungarian composer, 1881-1945"},
      {"CHURCHHILL","ALIVE","Winston Churchhill, Statesman, 1876-1965"},
      {"SANGER","DEAD",
       "Margaret Sanger, birth control pioneer, 1883-1966"},
          ...
      }
    This list is in fact a multidimensional array:
         people[0][0] = "BARTOK";
         people[0][1]="DEAD"; and 
         people[0][2] = "Bela Bartok, Hungarian composer, 1881-1945"
         people[1][0] = "CHURCHHILL";
         people[1][1]= "ALIVE"; and 
         people[1][2] = "Winston Churchhill, Statesman, 1881-1945"
         ...
    The include statements are followed by a list of the people included in the quiz.

    The function main() begins the body of the script. First some variables are initialized as empty strings. This is to prevent error messages when they first get used as parameters to functions that require strings. The variable PeopleInList is then set to the number of people in the list with the SElib.getArrayLength() function.

    Next, the script calls CheckVariables to retrieve the user input. CheckVariables receives PeopleInList as an argument and uses it to determine which variables to check for and get an answer.

    CheckVariables loops through the list of variables, checking each answer using the value returned by cgi.getVar:

      CheckVariables(answer, PeopleInList)
      {
        for (p=0; p < PeopleInList; p++)
        {
          if ( !(answer[p] = cgi.getVar(People[p][0])) )
          {
            // If no answer was supplied, make sure they get it wrong
            if (!Clib.strcmp(People[p][1], "ALIVE"))
              Clib.strcpy(answer[p], "DEAD")
            else
              Clib.strcpy (answer[p], "ALIVE");
          }
        }
      }
    This code uses names in the "people" array as the names of the variables to retrieve using cgi.getVar(). We know that people[0][0] = "BARTOK", so while p=0, the call to cgi.getVar will be interpreted as
         if (!(answer[0] = cgi.getVar("BARTOK")))
    and so on through the list.

    If cgi.getVar successfully returns a variable, the variable will get a value of either "DEAD" or "ALIVE", depending on which button was selected by the user. If the call to cgi.getVar fails, it means that no button was pressed, so the script assigns the wrong value ("DEAD" if the person really was alive, e.g.) to the variable.

    The script then sends the answers retrieved to the function CorrectAnswers(). This function returns a string with all of the answers, plus a green check mark for each correct answer and a red X for each wrong answer. It first compares the answer (answer[x]) with the correct answer (people[x][1]), and adds a string to reference the appropriate gif image, the correct answer, and a <BR> element to the HTML string to be returned from the function.

    The variable score is set to the number of correct answers. This will be presented as a percentage, so it is multiplied by 10. (There are ten questions in the quiz, so each counts for 10%).

    We then begin to construct the HTML document for output back to the client. As always, we begin with a content-type line to let the browser know what kind of document to expect. There is only one other cgi.out() statement. It contains the entire text of the HTML document:

      cgi.out(
        tag("HTML", NULL,
          tag("HEAD", NULL,
            tag("TITLE", NULL, "Alive in 1880? -- Answers")
          ),
          tag("BODY", `BGCOLOR="FF9999"`,
            "You scored ",
            score,
            "\%, (assuming you knew who everyone was).",
            "<BR><BR>",
            CorrectionString,
            "<BR>",
          )
        )
      )
    The cgi.out() function has only one parameter: the first call to the tag() function. The tag function creates an HTML tag out of its first parameter, uses the second parameter for attributes that get passed to the tag, and appends all subsequent parameters between the beginning and end tags. If there is no third parameter, the tag is assumed not to take a closing tag. The tag() function is a part of tinyhtml.jsh, one of the function libraries Nombas provides to simplify creation of web pages.

    The first call to tag() has four parameters: "HTML", NULL, and two more calls to tag(), one for "HEAD" and one for "BODY." The second parameter is NULL because HTML takes no attributes, as is the case with HEAD and TITLE. BODY, however, takes a parameter, which is shown inside back-tick quotes. This BGCOLOR attribute sets the background of the page yellow. The remainder of the page is a series of strings and variables representing strings sent as parameters to the tag() function.

    This can also be done with the BODY() function in html.jsh, another of the function libraries provided with ScriptEase:Webserver Edition. The first parameter to BODY is the attributes for the BODY. Using the BODY() function instead of the tag function, it would look like this:

         BODY(`BGCOLOR="FF9999"`,
         	"You scored ",
         	score,
         	"\%, (assuming you knew who everyone was).",
         	"<BR><BR>",
         	CorrectionString,
         	"<BR>",
         )
    When you use a series of cgi.out calls to create a webpage and then look at the source of that page in your browser, the page appears much as you'd expect it to: each call to cgi.out produces one line of HTML document text. However, if you use tag(), the CGI output of the function will appear as one line. In other words, if you create the document with a call to tag("HTML", "", ...) and then look at the created document's source, it will all be printed as one line. You can fix this problem by adding newline characters ('\n') to these strings in all the places where you want the line to break. Note that this can only be done in strings enclosed by full quotation marks ("), and not in back-ticks(`).

    The FormEase Library
    Formease.jsh is a library of functions that perform common tasks in dealing with form variables automatically - like checking form variables. For example, here is a script that does the exact same thing as the script in the first example of this chapter, with one major difference: if you don't fill out the email address field, the computer will return the form, with the fields that were filled in still filled, and say that it must have an email address in order to send information.

         #include <../htmlcopy.jsh>
         #include <../formease.jsh>
         
         // read the form from the CGI input, and the HTML document
         GetFormInput(form);
         document = ReadFile("pgne.htm");
         SetFormOutput(document, form);
         if (IsBlank(form.name))
         {
         	InsertRetryMessage(document, "Please insert a name.");
         	cgi.out("Content-Type: text/html\n\n");
         	cgi.out(document);
         	Clib.exit(0);
         }
         if (IsBlank(form.email))
         {
         	InsertRetryMessage(document, NULL, "Please include an email 
         					address.");
         	cgi.out("Content-Type: text/html\n\n");
         	cgi.out(document);
         	Clib.exit(0);
         }
         
         cgi.out("Content-Type: text/html\n\n");
         cgi.out("<HTML><HEAD><TITLE>Form Return</TITLE></HEAD><BODY>");
         cgi.out(form.name);
         cgi.out(", we will be sending information on")
         
         if (!IsBlank(form.pigs))
         	cgi.out(" pigs");
         
         if (!IsBlank(form.goats)){
         	if (!IsBlank(form.pigs))
         		cgi.out(" and");
         	cgi.out(" goats")
         }
         if (!IsBlank(form.elephants)){
         	if(!IsBlank(form.pigs) || !IsBlank(form.goats))
         		cgi.out(" and");
         	cgi.out(" elephants")
         }
         cgi.out(" to the following email address: ")
         cgi.out(form.email);
         
         if !Clib.strcmp(Form.PRO, "YES"){
         	cgi.out("<P>I see you are a professional animal trainer. ");
         	cgi.out("Contact us about employment opportunities.</P>");
         }
         
         cgi.out("</BODY></HTML>");
    Script using formease.jsh
    As you can see, this script is considerably shorter than the first script, even though it does more. These #include statements include the ScriptEase libraries htmlcopy.jsh (for the function ReadFile()) and formease.jsh with the script. These libraries contain functions that simplify form processing.

    Formease.jsh contains the function GetFormInput(), which the user input as an object, with one property for each of the form's fields. It takes the place of all the if(...cgi.getVar()) statements in the original script. In our example, form.name returns the string from the NAME field, form.email returns the string from the email field, etc.

    ReadFile() (from the htmlcopy.jsh library) takes an entire file and stores it as a long string. The string can then be sent back to the client with a cgi.out() statement (remember to put a content type header beforehand). This string can be used with InsertRetryMessage() to insert a message into the document and return the message to the user.

    InsertRetryMessage() then adds a message to the beginning of the string supplied as the first parameter. The message string itself is the third parameter. It is a format string as used in the rsprintf() function. The message will be in bold text and set off from the main message by horizontal lines. In this sample, the user will see a message to enter more data followed by the form with all the data they entered saved.

    The second parameter to InsertRetryMessage() is a string representing where in the document the retry message is to be inserted. The retry message will be put at the top of the page. You can indicate a different place in the script by putting a comment ("<!-- insert here -->", e.g.) in the page where you want to insert the comment. Pass the comment (as a string) as the second parameter to InsertRetryMessage() and the message will replace the indicated comment in the script.

    The next few lines determine whether the name and email fields were filled in and send the form back if it wasn't.

    IsBlank(), one of the functions in formease.jsh, checks to see if the field was filled in. If IsBlank(form.email) returns true, then the email field wasn't filled in, and the form is sent back to the client with an error message.

    The only other part of this script that is different from the original is that the Clib.strcmp() functions have been replaced with IsBlank() functions. Since the variable names have all been replaced by structures using GetFormInput(), the strcmp() function can no longer be used to interpret the form data, so we must use IsBlank() instead.

    Hangman.jse - a web game
    This is a ScriptEase:Webserver Edition version of the popular word guessing game.The rules of hangman are very simple. You have eight chances to incorrectly guess a letter in the word. With each wrong guess, part of a body is drawn on a gallows. After eight wrong guesses, the entire body is on the gallows and the game is lost. The game is won by guessing the word. At any time you can guess the entire word by typing it into the textbox that appears above the letters.

    This script uses a form with multiple submit buttons, one for each letter of the alphabet. There is also a text filed. The user guesses a letter by clicking on the appropriate button or by entering the guess in the text field and hitting return. The text field may also be used to guess the entire word at once.

    To identify which button was pressed, the script calls cgi.getVar() with no parameters, which returns an array of all the values passed. These values are parsed to find one in the proper form, i.e. one that is one letter long.
    A game of hangman in progress

    The first part of the script is a list of #include statements and #define statements. They let the computer know where to find the libraries and .gifs used in the game, and define the URLs for links.

    Then comes a list of the words that the computer uses for you to guess from. You can add to this list, because the number of words in the list, stored in the variable WordCount, is regenerated every time the script is run.

      #include "tinyhtml.jsh"
      #define IMAGE_URL "/sewse/hangman/%s"
      #define HANGMAN_URL "/www.nombas.com/us/sewse.nlm?n:/hangman/hangman.jse"
         
      Words={ "OVEN", "JACKKNIFE", "SYRINGE", "ABSORBENT", 
       "FRAGMENT", "TOOTHBRUSH", "BRISTLING", "HOUSEFLY" 
      };
      WordCount = SElib.getArrayLength(Words);
      #define ALLOWED_WRONG_GUESSES 7
      // default message
      Message = "What's your guess?";
    The script uses a hidden variable, "State", to keep track the word and the letters that have been guessed. When the hangman game runs, the first thing the script does is try to read the "State" variable. If there is no "State" variable, it knows that it is starting a new game, and selects a word for you to guess. The word is stored as a number to prevent cheating by looking at the source of the document. If the number is 0, for example, the word selected will be the first word on the list.

    If state exists, the computer parses it into its component parts: the number corresponding to which word is the one being guessed, and a string consisting of all letters that have been guessed. It does this using Clib.sscanf, a function, which parses strings for data using a format string and returns the number of patterns matched. If one pattern is matched, then there are no letters in SelectedLetters, so Selected Letters is set to an empty string; if two patterns are matched, they will be the WordIndex and the Selected Letters string.

      // see if the variable 'state' exists
      if ( !(State = cgi.getVar("state")) )
      {
        // this is a new game, select word and make plain form
        Clib.srand(); // randomize number generator
        WordIndex = Clib.rand() % WordCount;
        SelectedLetters = "";
      } 
      else 
      {
        // Use sscanf to extract WordIndex and Selectedletters
        // Word Index is an integer, and SelectedLetters is a string
        // of letters representing the guesses so far
        if ( 1 == Clib.sscanf(State,"%u %s",WordIndex,SelectedLetters) )
          SelectedLetters = "";
        else
          Clib.assert( 2 == Clib.sscanf(State,"%u %s",WordIndex,
                                        SelectedLetters) );
      }
    The next line Vars = cgi.getVar() checks to see what has been passed to the script. When cgi.getVar() has no parameters passed to it, it returns an array of all the parameters passed to the script. In this script there are two values passed: the hidden string 'state', and a string consisting of the letter pressed (if a letter button was pressed) or a string consisting of the word guessed (if a word was guessed).

    The for loop following the call to cgi.getVar() looks for a string that is only one character long (!Vars[i][1] will return TRUE if there is no character in the second position of the array) and whose only character is a letter. If it finds such a variable, it sets NewLetter to be equal to the letter found.

    If the computer gets through the loop without finding such a variable, it assumes that instead of guessing a single letter, the player guessed the whole word, so it tries to extract the word with another call to cgi.getVar(), looking for the variable "guess". The word guessed is made all upper case with the Clib.strupr() function and stored in the variable NewLetterStr.

         Vars = cgi.getVar();
         for ( i = 0; Vars[i]; i++ ) {
            if ( !Vars[i][1] && Clib.isascii(Vars[i][0]) ) {
               NewLetter = NewLetterStr[0] = Vars[i][0];
               break;
            }
         }
         if ( !Vars[i] ) {
            // didn't press button, so get entire guess
            Clib.assert( NewLetterStr = cgi.getVar("guess") );
            Clib.strupr(NewLetterStr);
            NewLetter = NewLetterStr[0];
         }
    The script then checks to see if NewLetterStr is one letter long or not. This is in case the player entered their guess on the keyboard instead of pressing one of the letter buttons. If not, we check to see if the word they entered matches the word being guessed with a Clib.strcmp() function. If it is, all of the letters in the word are copied into the string of selected letters. If not, the message is set to "Wrong!" and a letter is selected to be used as a guess - in other words, if you guess the word wrong, you are told one letter which isn't in the word.

    If NewLetterStr is only one letter long (this is the else part of the if statement described in the previous paragraph) the script checks to make sure it's a letter, and then checks to see whether it's been guessed or not. If it passes these two tests, it will be added to the list of guessed letters. The length of the target word is then calculated and stored in the variable WordLen.

      // See if the string returned is only one byte long
      if ( 1 != Clib.strlen(NewLetterStr) )
      {
        // See if they guessed the word correctly, if correct
        // they win. else pick a letter to be a wrong choice
        if ( !Clib.strcmp(NewLetterStr,Word = Words[WordIndex]) )
          Clib.strcpy(SelectedLetters,Word);
        else
        {
          // they guessed the word incorrectly
          Message = "Wrong!";
         	
          // Step through the guess until an incorrect letter
          // is found to use as a guess  
          for ( i = 0; i < Clib.strlen(NewLetterStr); i++ )
          {
            if ( !Clib.strchr(Word,NewLetterStr[i]) )
            {
              if ( !Clib.strchr(SelectedLetters,NewLetterStr[i]) )
              {
                NewLetterStr[i+1] = 0;
                Clib.strcat(SelectedLetters,NewLetterStr+i);
              }
              break;
            }
          }
        }
      } 
      else if ( !Clib.isalpha(NewLetter) )
        Message = "Choose a letter between A and Z, inclusive.";
      else if ( Clib.strchr(SelectedLetters,NewLetter) )
        Clib.sprintf(Message,"You already selected %c.",NewLetter);
      else
        Clib.strcat(SelectedLetters,NewLetterStr);
         
      WordLen = Clib.strlen(Word = Words[WordIndex]);
    There are three more sections before we have enough information to begin sending the HTML document back to the server. The first two of these sections are each headed with a comment to tell you what they do: build a partially completed display of the word being guessed and build a list of wrong letters guessed thus far. The word display and list of wrong letters are stored in strings, because they need to be output back to the client as part of the game display. The final section decides whether you have won or lost, or are to keep playing.

    The 'build display of word' section starts by setting Solved = True. If you can make it through this section without changing Solved to false, you win! It then starts creating a variable DisplayWord to hold the word. This starts off with "WORD:" and then tests each letter in turn, seeing if the letter appears in the string of SelectedLetters, and adding it to DisplayWord if it is. If it isn't, it adds a blank instead, and sets Solved=False.

    'Build a list of wrong letters' does much the same thing in reverse. It goes through the list of SelectedLetters and checks to see if they are a part of the Word. If not, they are added to the string WrongChoices. Each time a letter is added to this string, WrongCount is incremented by one.

    WrongCount is the variable used to see whether you've lost the game or not. When it is equal to ALLOWED_WRONG_GUESSES (defined at the beginning of the script), the computer checks whether you've guessed all the letters or not, and sets Message accordingly, from it's default string "What's your guess?" to either "You lose. The word was ..." or "You win."

    The actual construction of the HTML document now begins with the cgi.out statements. There are only two cgi.out tags in this script; the first outputs the content line (to tell the browser that the document is an HTML document), and the second contains the entire contents of the HTML document. This script uses the tag() function in tinyhtml.jsh, a flexible function that makes an HTML tag from whatever its first parameter is. The second parameter of tag consists of the attributes that will be passed to the tag, or NULL if the tag takes no attributes. All following parameters will be enclosed within the beginning and end tags.

    Most of the these parameters are functions in their own right, which return strings. Remember that when a function appears inside a statement, the function can effectively be substituted with what it returns. So if you had a function that returned the complete works of Shakespeare as a string, the line:

         tag("B", NULL, Shakespeare())
    would insert the complete works of Shakespeare into your document, in boldface using the "<B>" tag which requires no attributes.

    HangmanImage() is a function that inserts the hangman image into the document. It determines what state the game is in (won, lost or how many wrong guesses have been made) and inserts the appropriate image name into the variable img. The return statement of this function,

      return tag("IMG",
                 Clib.rsprintf( `SRC= ` IMAGE_URL ` " ALIGN=LEFT 
                                WIDTH=180 HEIGHT=216`, img));
    creates the following string to be inserted into the document:
      <IMG SRC="hangwin.gif" ALIGN=LEFT WIDTH =180 HEIGHT=216>
    Then the variable DisplayWord, which was created earlier in the script, is inserted into the document within a pair of <H3>/</H3> tags. This is done with the tag function again.

    The next two lines are pretty cryptic. They use the conditional operator ?. The first of these two statements says "if WrongCount = 0 (i.e., no wrong letters have been guessed), put an empty string in the document; otherwise put in the string of WrongChoices formatted earlier. The WrongChoices string is formatted inside HTML <P>/</P> tags.

    The second says "if the game is won OR if the game is lost, add the message to the string; otherwise add the string returned by the function HangmanForm()." The message will be either you won or you lost. Hangman form returns both the message string (What's your guess?, the default) and a series of INPUT fields: a text field so you can guess the entire word at once, plus 26 buttons for each of the letters of the alphabet, plus the hidden field, state, which contains the index number of the word being guessed at and a string of the letters that have already been guessed. Note that the function HangmanForm() has only two statements, and sprintf statement and a return. The return statement is so complex that it is several lines long, and contains functions itself (e.g. the Letters() function, which builds up the buttons for the letters of the alphabet).

    When the script ends, the HTML text is sent back to the client. They can then make more letter selections or select a new word if they correctly guessed the word.

    Troubleshooting: frequently encountered problems

    The browser says "Document returns no data" (Netscape)
    Usually when this happens there is no content line put out to the server. A content line looks like this:

         cgi.out("Content-Type: text/html\r\n\r\n");
    and basically tells the browser what kind of document to expect. In most cases, it will be an html document, but it could be a .gif or .jpg image. The content type line should be the first line sent back to the client.

    Some browsers are able to parse out the type of file from its contents, and therefore do not need this line. It's always a good idea to include it though, since you have no idea which browser your clients may be using.

    You can also get this message if you are using the functions in html.jsh or tinyhtml.jsh, and forget to use them inside a call to cgi.out. This means that basically you format the page but never send it back to this client.

    I get an error message stating that the error is in a Nombas-supplied .jsh file
    Usually when this happens the function reporting the error was passed a parameter in the wrong format (e.g., a string instead of an integer, or a misspelled variable name). When the function tries to use the data, it can't, so it reports an error. Make sure all of the variable names are correct, and of the proper type for the function returning the error. The IDE Debugger will help you isolate the error.