RIFE logo
This page last changed on Jun 27, 2006 by andresgr.

Creating a more advanced RIFE application

Laying out the structure

Now we've seen the simplest possible application, and should be ready to move on to a more realistic example. In the real world, we are going to need dynamic and generated content, links and an occasional form that needs to be filled out and processed.

We're going to use a little number guessing game as a slightly simplified real world example. The game lets the user guess a number and repeatedly responds "too high" or "too low", until the guess is correct.

The numberguess example is available in the same RIFE examples file that we used in the previous chapter. Repeat the instructions from the Hello World example to setup RIFE and Jetty for the game.

cd ~/tutorial/rife-examples-{version}
mkdir 03_numberguess/WEB-INF/lib
cp ROOT/WEB-INF/lib/rife-{version}.jar 03_numberguess/WEB-INF/lib/
mv 03_numberguess ../jetty-{version}/webapps/

You will need to start Jetty again, you can try the example out at the location {{*http://localhost:8080/03_numberguess/*}} .

The number of files is starting to grow as the application gets more advanced, so let's take a look at the directory structure and files before moving on:

Directory structure for the numberguess example
classes/
    rep/
        participants.xml

    sites/
        numberguess.xml

    elements/
        start.xml
        guess.xml
        success.xml

    templates/
        guess.html
        success.html

    implementations/
        tutorial/
            numberguess/
                Start.java
                Guess.java
                Success.java

src/
    tutorial/
        numberguess/
            backend/
                Contest.java
                Game.java

If you look in the elements directory you see three different XML files. Each of them represents an element in the game. The elements are connected by creating execution flows between them in the site definition. These flows are called "Flow links" and are created with the flowlink tag. If you want to pass data between different elements you use "Data links" which are represented by the datalink tag.

We are going to connect the elements like this:

The numberguess site

Elements must provide one or more "exits" if you want to make it possible to link them to other elements. An exit is the starting point of a flow link and the end point is another element.

In schema above, the data links are represented by blue arrows while the flow links are black. We can also see that START has one exit named "started", GUESS has two exits named "start" and "success" and SUCCESS has one exit called "start". By connecting the exit "started" from START to go to GUESS we get the elements linked together.

Linking elements

Defining exits

As mentioned, the elements have various exits that make it possible to connect the elements to each other. These exits are defined in the element declaration files. For an example, take a look at elements/guess.xml, where two exits, "start" and "success", are defined.

Defining exits for an element
<element implementation="tutorial.numberguess.Guess">
  <exit name="start"/>
  <exit name="success"/>
</element>

Creating the links

The relationships between the three elements, Start, Guess and Success, are defined in the site file, sites/numberguess.xml. It's there that elements are linked together and that the flow of the site is defined. All this is done by the appropriate flowlink tags:

Defining flowlinks
<site>
  <element id="Guess" file="guess.xml" url="/guess">
    <flowlink srcexit="start" destid="Start"/>
    <flowlink srcexit="success" destid="Success"/>
  </element>
</site>

This particular element, GUESS, has one link to the START element and one to the SUCCESS element. A flow link is always connected between an exit of an element and an element, and the exits of each element must be specified in the element's definition file.

The benefit of all this is that it's easy to get a clear overview of the relationships and flow between elements in a site just by looking at the site file. This is a great advantage when developing and maintaining web applications, especially big ones.

Passing data between elements

In addition to setting up flow links, we also need to be able to pass data around between elements. The numberguess game uses a game ID that is used to identify every game that is played. We want to pass this ID around when a game is active. This makes it possible for all the elements to know which game they should work with. Data links are used for this purpose, and their definition is very similar to flow links.

Defining inputs and outputs

In order to be able to pass input variables to and from an element you need to define inputs and outputs for those variables. To do that, you use input and output tags in the element declaration. After adding the input and output tags to the Guess element declaration, the result is:

Defining inputs and outputs
<element implementation="tutorial.numberguess.Guess">
  <input name="gameid"/>

  <exit name="start"/>
  <exit name="success"/>

  <output name="gameid"/>
</element>

Creating the data link

To actually connect the output of one element to the input of another, a data link must be created. An example from the number guess game is the link from the "gameid" output of the Guess element that is connected to the "gameid" input of the Start element:

<datalink srcoutput="gameid" destid="Start" destinput="gameid"/>

After adding datalinks to the example from above, we would get the following:

Adding data links to the guess element
<site>
  <element id="Guess" file="guess.xml" url="guess">
    <flowlink srcexit="start" destid="Start"/>
    <datalink srcoutput="gameid" destid="Start" destinput="gameid"/>

    <flowlink srcexit="success" destid="Success"/>
    <datalink srcoutput="gameid" destid="Success" destinput="gameid"/>
  </element>
</site>

Note that the output name doesn't have to be the same as the input name, since you provide them both to the datalink tag.

Templates

In order to separate the presentation and the data layer, RIFE uses templates. A template is a regular HTML file with specific markup to structure it for web application usage. There's no template scripting language and it's not possible to include any logic at all inside them. They only contain blocks and placeholders for content: values. The logic is placed in the elements where blocks are manipulated and content assigned or constructed in the placeholders. The blocks and value tags are replaced by the generated output before being sent to the browser.

This makes RIFE templates different from other solutions, for example JSP, since the pages can be edited in any regular HTML editor. Since the files are valid HTML, they can also easily be tried out in a browser without having to run the whole application.

In the Hello World example, all the content was generated directly in the Java class implementation, by writing it from the processElement method. It would be pretty cumbersome if all content would need to be generated in this fashion. It is here that templates come in handy.

In the numberguess application, there are two templates: one for the guess page and one for the success page. A quick look at the guess template gives the following snippet:

The template for the guess page
<p>Guess a number from 0 to 100</p>
<!--V 'warning'--><!--/V-->
<!--B 'invalid'-->
<p><font color="#990000">The guess is invalid.</font></p>
<!--/B-->
<p><i><!--V 'indication'--><!--/V--></i></p>
<!--B 'lower'-->The answer is lower.<!--/B-->
<!--B 'higher'-->The answer is higher.<!--/B-->

As you can see, RIFE makes use of ordinary HTML comments. There is also another syntax for template tags, that is a bit less verbose where [! is used instead of <!- and ] instead of ->. You can see the two tags V and B being used, where V is a placeholder for a value and B is the definition of a block. To insert a string into a value tag from the Java element code might look like this:

template.setValue("warning", "The answer is lower.");

In this example though, we use the blocks that are defined with B tags and insert one of them as the content of a value:

template.setBlock("indication", "lower");

This way we don't have to use one single string of HTML inside the Java code, and no logic in the templates. This very clear separation of content and logic is one of RIFE's great strengths.

Reading data from a form

Since we need to handle input from the user we have a form on the web page, with an entry and a submit button. This is reflected in the Guess element definition by the submission tag:

Adding a submission to the guess element
<element implementation="tutorial.numberguess.Guess">
  <input name="gameid"/>

  <submission name="performGuess">
    <param name="guess">
      <default>-1</default>
    </param>
  </submission>

  <exit name="start"/>
  <exit name="success"/>

  <output name="gameid"/>
</element>

Here, we have defined one submission parameter, namely the number that the user guessed. Submissions always refer back to the original element. This centralizes all the handling of a submission in the same element, instead of having one page for showing the form and another one that deals with the submitted data.

Since the form display and data processing is done in the same element, it's easy to just show the form again if some data is missing or invalid, without having to ask the user to go back to the previous page to fill out the rest.

We also need to add a bit of code to the template to handle the submission form:

The guess template with a form
<form action="[!V 'SUBMISSION:FORM:performGuess'/]" method="post">
  <!--V 'SUBMISSION:PARAMS:performGuess'/-->
  <input type="text" name="guess" value="[!V 'PARAM:guess'][!/V]" /><br />
  <input type="submit" value="Guess" /><br />
</form>

The action is set to a special value: SUBMISSION:FORM:performGuess. RIFE replaces this value with the correct URL, in this case the URL that corresponds to the GUESS element. The last part of this special value refers to the name of the submission that you declared earlier in the element XML definition.

The value of the text input HTML tag is set to the PARAM:guess value. This again corresponds to the guess parameter of your submission definition. This value is automatically replaced by the last value of the parameter and allows the user to always see the last guess that was made. Note that we use a a longer value tag here and we'll explain its use in detail later. All you have to know now is that when no guess parameter is available, the value will be replaced with an empty string and thus show up as an empty field to the user.

Warning

You always have to use SUBMISSION:FORM:name together with SUBMISSION:PARAMS:name. RIFE will warn you about this, but it's always good to know beforehand.

Values with a prefix like SUBMISSION: are treated specially by RIFE. Here, for instance, the form action and parameters are set up. There are other prefixes such as EXIT: and CONFIG: as we will see later on. These tags are called filtered value tags since RIFE automatically filters them out and replaces them with special content that's related to the application.

When the page is entered, the element needs to check if a submission exists, and if so, handle it. The check is done through the hasSubmission method and values are fetched with the various methods that start with getParameter. To get an integer value, getParameterInt is used, and so on. The complete list of methods can be found in the API reference documentation, under the class com.uwyn.rife.engine.Element.

Java code for the Guess element
package tutorial.numberguess;

import com.uwyn.rife.engine.Element;
import com.uwyn.rife.template.Template;
import tutorial.numberguess.backend.Contest;
import tutorial.numberguess.backend.Game;

public class Guess extends Element
{
  private Template mTemplate = null;

  private void handleGuess(Game game)
  {
    int guess = getParameterInt("guess", -1);
    if (guess < 0 || guess > 100)
    {
      mTemplate.setBlock("warning", "invalid");
      return;
    }

    game.increaseGuesses();

    if (game.getAnswer() < guess)
    {
      mTemplate.setBlock("indication", "lower");
    }
    else if (game.getAnswer() > guess)
    {
      mTemplate.setBlock("indication", "higher");
    }
    else
    {
      setOutput("gameid", getInput("gameid"));
      exit("success");
    }
  }

  public void processElement()
  {
    Game game = Contest.getGame(getInput("gameid"));
    if (null == game)
    {
      exit("start");
    }

    mTemplate = getHtmlTemplate("guess");

    if (hasSubmission("performGuess"))
    {
      handleGuess(game);
    }

    print(mTemplate);
  }
}

In this Java code we see quite a few interesting parts. Let's start with the call to exit in processElement. This instructs RIFE to activate the flow link that's connected to the start exit. No further processing is done in the element, it will immediately be exited and the logic will proceed with the element that's being pointed to by the flow link. In this particular example, it causes the start page to be shown when no active game could be found.

If a game was successfully retrieved, a check is done to see if the request contains the submission named "perform_guess". If it does, handleGuess is called to see if the user's guess is correct. If it is, the exit named "success" is triggered and the element connected to this exit will handle the output.

If no submission was found, the default template will be displayed, which asks the user to enter a guess.

Specifying the starting point

It would be nice if our users wouldn't have to enter the location http://localhost:8080/03_numberguess/start/ to enter our game, but instead just http://localhost:8080/03_numberguess/.

We can achieve this by specifying an arrival element for the site. An arrival element is the element that is used for the root of the application, and is setup with the arrival tag. After this final addition, the site file will look like the following:

The final number guess site file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE site SYSTEM "/dtd/site.dtd">

<site>
  <arrival destid="Start"/>

  <element id="Start" file="start.xml" url="/start">
    <flowlink srcexit="started" destid="Guess"/>
    <datalink srcoutput="gameid" destid="Guess" destinput="gameid"/>
  </element>

  <element id="Guess" file="guess.xml" url="/guess">
    <flowlink srcexit="start" destid="Start"/>
    <datalink srcoutput="gameid" destid="Start" destinput="gameid"/>

    <flowlink srcexit="success" destid="Success"/>
    <datalink srcoutput="gameid" destid="Success" destinput="gameid"/>
  </element>

  <element id="Success" file="success.xml">
    <flowlink srcexit="start" destid="Start"/>
  </element>
</site>

Wrapping it up

Now we've written a simple web game, and hopefully learnt a lot about RIFE in the process. We've defined flow and data links, used templates to create web pages, and handled user input from forms. We should now be ready to move on to more advanced topics, like writing complex templates.


numberguess_site.png (image/png)
Document generated by Confluence on Oct 19, 2010 14:56