A simple Fiz application

This page is a tutorial that walks you through a simple Fiz application illustrating many of the features of Fiz. Here is the Java code for the application (don't worry about understanding it now; we'll go through it piece-by-piece below).

import org.fiz.*

public class DemoInteractor extends Interactor {
    FormSection form = new FormSection(
        new Dataset("postUrl", "post"),
        new EntryFormElement("name", "Name:"),
        new EntryFormElement("street1", "Address:"),
        new EntryFormElement("street2",  ""),
        new EntryFormElement("city", "City:"),
        new SelectFormElement(new Dataset("id", "state",
                                          "label", "State:",
                                          "choiceRequest", "getStates")),
        new EntryFormElement("zip", "Zip code:"));

    public void main(ClientRequest cr) {
        Html html = cr.getHtml();
        html.setTitle("Simple Fiz Form");
        cr.addDataRequest("getStates", new FileDataManager().newReadRequest(
                "demo/states.yaml", "states"));
        Section sections[] = {
            new TemplateSection("<h1>Enter your data below</h1>\n"),
            form,
            new TemplateSection("<div id=postedInfo></div>\n")
        };
        cr.showSections(sections);
    }

    public void post(ClientRequest cr) {
        Dataset data = form.collectFormData(cr);
        if (validate(cr, data)) {
            cr.updateElement("postedInfo", Template.expand(
                    "<p>Posted form data: @name, @street1, {{@street2,}} " +
                    "@city, @state  @zip</p>\n", data));
        }
    }

    protected boolean validate(ClientRequest cr, Dataset data) {
        boolean success = true;
        for (String id : new String[] {"name", "street1", "city",
                "state", "zip"}) {
            if (data.get(id).length() == 0) {
                form.elementError(cr, id,
                        "You must provide a value for this field");
                success = false;
            }
        }
        String zip = data.get("zip");
        if (zip.length() > 0) {
            if (zip.length() != 5) {
                form.elementError(cr, "zip",
                        "Zip code must consist of 5 decimal digits");
                success = false;
            }
            for (int i = 0; i < 5; i++) {
                if (!Character.isDigit(zip.charAt(i))) {
                    form.elementError(cr, "zip",
                            "Zip code must consist of 5 decimal digits");
                    success = false;
                }
            }
        }
        return success;
    }
}

Running the application

The first step is for you to build and deploy the application so you can see how it works:

  • Create a new application using the fiz tool:
    fiz create app demo
    This will create a new subdirectory demo containing the code and the data for the application, plus a copy of Fiz.
  • Copy the code above into a new file demo/src/DemoInteractor.java.
  • Create a new directory demo/web/WEB-INF/demo and make a copy of the attached file states.yaml in that directory (this file contains a list of states that will be displayed by the application).
  • Cd to the demo directory and build the application by typing the following command:
    ant build
  • Deploy the application by copying the contents of the directory demo/out/demo to the directory webapps/ROOT in your Tomcat installation, replacing everything that used to be in that directory.
  • Start Tomcat
  • Type the following URL in your browser: http://localhost:8080/demo/main. (This assumes that Tomcat is using the default port of 8080)

At this point your browser should display a form that looks like the following:

Try filling out the form and submitting it. You should see error messages if you leave certain fields blank or if you type an incorrect Zip code.

How it works: dispatching

The application code consists of three methods: main displays the form page, post handles form submissions, and validate is an internal method used to validate the form data when it is posted. The main method is invoked automatically by Fiz when the URL http://localhost:8080/demo/main is received in an HTTP request. Fiz uses the first field of the URL, demo, to pick the class (DemoInteractor) and the second field, main, to pick a method in that class. See URL Dispatching for more details on the dispatching process.

Sections

In Fiz Web pages are constructed out of Section objects. Each Section is responsible for generating a portion of the Web page. The main method generates its page by creating three sections and then invoking the Fiz method showSections to render them:

    public void main(ClientRequest cr) {
        ...
        Section sections[] = {
            new TemplateSection("<h1>Enter your data below</h1>\n"),
            form,
            new TemplateSection("<div id=postedInfo></div>\n")
        };
        cr.showSections(sections);
    }

The first section displays a heading, the second section generates the form, and the third section creates a <div> at the bottom that is initially empty. Each kind of section is implemented by a particular class; for example, the TemplateSection class implements a simple section that displays a given HTML string (it also provides the option of substituting variable values into the string; we don't use that feature here, but you will examples of it below). See Sections for more information about sections.

Interactors

Top-level methods for handling requests, such as main, are called entry methods, and they must be defined in subclasses of Interactor. Each Interactor class will typically handle several different URLs that have related functionality. In this case, the Interactor handles both the URL to display the form page and also the URL that is invoked when the form is posted. Each URL is mapped to a distinct method in the Interactor. See Interactors for more information about Interactors.

ClientRequests

When an entry method is invoked it receives a ClientRequest object as argument. A ClientRequest holds overall information about a single HTTP request (such as the URL and query values) and its eventual response (such as an HTML page). ClientRequest variables are almost always named cr in Fiz. The main method uses its ClientRequest to retrieve the Html object used to assemble the page, and uses that to set the title that will appear in the browser window:

        Html html = cr.getHtml();
        html.setTitle("Simple Fiz Form");

Fiz is implemented using the Java servlet mechanism, and the ClientRequest provides access to the HttpServletRequest and HttpServletResponse objects from the underlying servlet container; you probably won't need to access these very often. The ClientRequest also provides numerous other methods for purposes such as generating Javascript in the response or processing forms containing uploaded files; see ClientRequests for more information.

Datasets

The form in this example is specified with a FormSection object, defined as a class variable so that it can be shared between the main and post methods:

    FormSection form = new FormSection(
        new Dataset("postUrl", "post"),
        new EntryFormElement("name", "Name:"),
        new EntryFormElement("street1", "Address:"),
        new EntryFormElement("street2",  ""),
        new EntryFormElement("city", "City:"),
        new SelectFormElement(new Dataset("id", "state",
                                          "label", "State:",
                                          "choiceRequest", "getStates")),
        new EntryFormElement("zip", "Zip code:"));

This code illustrates the use of Dataset objects, which serve numerous purposes in Fiz. A Dataset is a hierarchical collection of name-value pairs. For example, the first argument to the FormSection constructor is a Dataset containing one entry with name postUrl and value post, and the first argument to the SelectFormElement constructor is a Dataset with three entries: id, label, and choiceRequest. Names and values can be arbitrary strings; in addition, the value for a dataset entry can be a nested dataset or a list of nested datasets. See Datasets for more information about datasets.

In this example Datasets are passed as the first arguments to constructors as a way of specifying configuration properties for the components. The postUrl configuration property for FormSection specifies the URL that should be invoked when the form is posted; in this case, the URL is post, which gets dispatched to the post method in the DemoInteractor class. Fiz components typically support large numbers of configuration properties, most of which are uninteresting for any given instance, so it would be tedious to require each property to be specified explicitly as a separate argument to the constructor. Datasets allow you to specify just the properties you care about; all the others will be defaulted. For example, FormSection allows you to control the appearance of the submit button with another configuration property. Some components, such as EntryFormElement, provide specialized constructors for common cases where only a few configuration properties are provided. For example,

    new EntryFormElement("name", "Name:")

is equivalent to
    new EntryFormElement(new Dataset("id", "name", "label", "Name:"))

The constructor for FormSection takes one argument containing configuration properties, plus any number of additional arguments that describe the elements of the form. Fiz contains several classes that define various form elements, and you can also define your own custom form elements. This example uses two kinds of form elements:

  • EntryFormElement displays a simple text entry; its id configuration property defines a name for this value (which can be used to retrieve the value in the form is posted) and the label configuration property specifies a descriptive label to appear to the left of the text entry.
  • SelectFormElement allows you to select one of several predefined values using a drop-down menu. In this case the choiceRequest property is used to retrieve the values (a list of all states), using the data request mechanism described below.

Data requests and data managers

Most pages in a Web application need to incorporate data from one or more back-end data sources. The most common data source is likely to be a relational database, but complex applications may use a variety of data sources, such as news feeds, other applications, and disk files. Fiz provides a general-purpose mechanism for interacting with data sources called data requests. Data requests are handled by data managers; each data manager provides access to a particular kind of data, such as a SQL database or an Excel spreadsheet file. In this example application, a data request is used to fetch the names of states to display in the SelectFormElement, using the following code:

    ...
    new SelectFormElement(new Dataset(... "choiceRequest", "getStates"))
    ...
    public void main(ClientRequest cr) {
        ...
        cr.addDataRequest("getStates", new FileDataManager().newReadRequest(
                "demo/states.yaml", "states"));
        ...
    }

The sample application uses the FileDataManager class, which reads disk files containing datasets. The main method creates a FileDataManager and uses it to create a data request that will read a dataset named states from the file demo/states.yaml, which you installed as part of the application (normally you would create each data manager once and keep it as part of global application state, so it doesn't have to be created again and again for each request). The method
cr.addDataRequest stores the data request in the ClientRequest under the name getStates. When the SelectFormElement is created, it uses its choiceRequest configuration property find the data request. The form element will read the values from data returned by this data request and use them to populate the pull-down menu.

See Data Managers for more information on data managers and data requests.

Form posting and page updates

When the user clicks on the Submit button in the form an HTTP POST request is made to the URL http://localhost:80/demo/post (this was determined by the postUrl configuration property for the FormSection. Fiz will dispatch this request to the post method in DemoInteractor:

    public void post(ClientRequest cr) {
        Dataset data = form.collectFormData(cr);
        if (validate(cr, data)) {
            cr.updateElement("postedInfo", Template.expand(
                    "<p>Posted form data: @name, @street1, {{@street2,}} " +
                    "@city, @state  @zip</p>\n", data));
        }
    }

The post method invokes the collectFormData method on the FormSection, which retrieves all of the incoming data from the form and returns it in a dataset. The validate method is then invoked to check for errors in the input data (more on this below). If the data is valid a normal application would use the data to update its internal state, then redirect the browser to some other page. This demo does something different in order to demonstrate some useful features of Fiz.

Dynamic page updates

One of the goals for Fiz is to make it easy for applications to update pages on-the-fly without completely redisplaying them. One aspect of this is Fiz' support for Ajax requests (see Ajax Requests). This application does not use Ajax, but it does use the method cr.updateElement; when the post method invokes cr.updateElement, Fiz communicates with the browser to replace the contents of the HTML element whose id is postedInfo with the HTML provided by the second argument to cr.update. In this example postedInfo is the <div> created by the third section of the page. The result is that the posted form data is displayed at the bottom of the page.

Templates

The new HTML for the element is created with Fiz's template facility, which is widely used for generating low-level chunks of HTML. In this example Template.expand takes two arguments. The first argument contains a template string and the second argument contains a dataset containing values to substitute into the stream. Template.expand returns a copy of its template, replacing @ patterns with substituted values:

  • @city is replaced with the value of the city entry in the dataset, which happens to be the contents of the form entry labeled "City:". Recall that the id configuration property for the form element was used to specify the name of that element.
  • {{@street2,}} causes the value of the street2 entry in the dataset to be substituted, except that if the value doesn't exist or is an empty string then no substitution occurs and everything between the ... is simply ignored. This conditional substitution provides a convenient way of handling optional values.

The Template class offers numerous variants of the expand method and supports several other forms of substitution; see Templates for more information.

Validation

The last method in the application is validate; it is called by post to check for errors in the form data. The first portion of the method verifies that values have been provided for required fields:

        boolean success = true;
        for (String id : new String[] {"name", "street1", "city",
                "state", "zip"}) {
            if (data.get(id).length() == 0) {
                form.elementError(cr, id,
                        "You must provide a value for this field");
                success = false;
            }
        }

If a required value is missing then form.elementError is invoked; it communicates with the browser to display an error message in the form next to a particular form element (identified by its id property). The elementError method also displays a message in the bulletin area of the page.

The second part of validate checks the ZIP code to make sure it contains exactly 5 decimal digits; if not, it also displays an error message next to the form element.

Conclusion

The goal of Fiz is to raise the level of programming for Web applications so that you think about your application in terms of higher-level components and spend less time writing HTML. The components handle a lot of the details for you automatically so you don't have to worry about them. For example, consider the FormSection and FormElement components:

  • All of the HTML for these components was generated automatically; the application just describes the structure of the form at a high level.
  • The FormSection automatically handles the form layout, displaying labels next to each element.
  • It is somewhat complicated to arrange for error messages to appear in-line in a form, but the FormSection takes care of this automatically: all the developer has to do is provide the message and the name of the erroneous elements.
  • The FormSection automatically includes additional information in the form to prevent CSRF (Cross Site Request Forgery) security attacks; as application developer you don't have to worry about this at all.

Another example of a high-level feature is the updateElement method. Fiz implements this in a way that works not just during form posts, but also during Ajax requests and even during normal page creation. All you have to do is provide the id of the element you want to modify and the new HTML contents for the element; Fiz takes care of everything else. Fiz provides a special implementation of form posting in order to make this and other operations behave correctly.

At this point you should be ready to start creating Fiz applications of your own. There are additional pages on this Wiki that provide overviews of the various Fiz features, plus there is complete Javadoc documentation available on the Fiz Web site.