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:This will create a new subdirectoryfiz create app demo
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 directorywebapps/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:
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 thelabel
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 thecity
entry in the dataset, which happens to be the contents of the form entry labeled "City:". Recall that theid
configuration property for the form element was used to specify the name of that element.- {{
@street2,
}} causes the value of thestreet2
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.