The Fiz Framework
Lecture Notes for CS349W
Fall Quarter 2008
John Ousterhout
- Goals:
- Create high-level reusable building blocks for Web applications.
- Provide a framework that will encourage the development of additional
building blocks.
- History:
- ElectricCommander: multiple iterations of Web front-end in PHP.
- Realized there is a framework there.
- Fiz: restarted from scratch using Java servlets.
- Worried about PHP/Ruby performance for highly-layered applications.
- Java risks: too static, clumsy?
- Current state: an early work in progress (not much "highly interactive"
stuff there yet)
- Expect several major revisions/rewrites over the next few years.
- Examples:
- Simple table
- Simple form
- Show code: it's mostly declarative, fairly minimal.
- Based on asking "what's the least possible information required
for these pages?"
- TODO: create Fiz examples
- Overall structure of a Fiz application:
- DataManagers:
- Manage the application's back-end data (e.g. handle communication
with database).
- Roughly analogous to models in Rails (though no ORM yet).
- Permit a variety of back-ends (not just RDBMS's).
- Interactors:
- Manage interactions with the browser (both HTML and AJAX requests).
- Combines Rails controllers and views
- No templates in the Rails sense
- DataRequests:
- Used for communication between Interactors and DataManagers.
- Basic building block for Web pages: Section
- Collects data
- Generates HTML in a particular style:
- A table
- A form
- A standard frame
- A simple HTML template
- Different from Rails:
- Rails separates data collection (in the controller) from HTML
generation (in the view).
- A Section combines both: makes Sections more independent.
- Sections are typically parameterized:
- Where does the data come from?
- How is the HTML rendered?
- The lifecycle of an HTTP request:
- URL structure:
http://www.mycompany.com/fiz/class/method?arg1=x&arg2=y
- Fiz dispatcher invokes method in class ClassInteractor
to handle the request.
- ClassInteractor must be a subclass of Interactor,
method must be a method in the class with a particular
signature.
- The Interactor method defines one or more Sections that represent
the page, then calls showSections method in Fiz.
- Page is rendered in 3 phases:
- First, each Section's registerRequests method is invoked.
The section takes this opportunity to tell Fiz about the data that
it needs (via DataRequest objects).
- Second, showSections initiates processing on all of
the DataRequests at once:
- Allows them to be processed in parallel
- Allows them to be shared (don't repeat the same request)
- Third, each Section's html method is invoked to generate HTML
for the section.
- Benefits of 3-phase approach:
- DataRequests can be processed in parallel (e.g., Amazon makes
hundreds of requests per page).
- Requests for the same manager can be batched.
- Identical requests can be merged.
- Example #1: TableSection:
- Defined by a request, multiple Column objects.
- The TableSection renders the overall table structure, calls out
to Columns for the contents of each <td>.
- TableSection automatically provides odd/even/header
classes for rows, first/last classes for columns.
- Additional constructor properties for empty tables, errors, etc.
- A Column does 2 things:
- Provide HTML for the column header.
- Pull data from a record to generate HTML for that column in each row
of the table.
- Standard column types:
- Simple text
- Link
- Formatted value (e.g., %6.2f)
- Currency
- Date/time
- Select one of several images based on data value
- Checkbox (checked or not based on data value)
- Can define new column types.
- Example #2: FormSection:
- Defined by overall form properties, plus a collection of
FormElement objects.
- Form properties include:
- Initial values for the form fields (either a data request or
literal values)
- URL for posting form.
- Layout style: side-by-side or vertical.
- Error handling information.
- Style for the submit button.
- FormElement: handles an individual control in the form. Different
subclasses for different kinds of controls:
- One-line entry
- Password
- Multi-line text
- Check button
- Radio button
- Menu/list to select one/many predefined values
- Lifecycle of a form:
- FormSection gets called to register data requests:
- Register overall request for the form, if any.
- Call each FormElement to give it a chance to register additional
requests for that element (e.g., choices for menu).
- FormSection gets called to generate HTML:
- Output <form> element, create <table> to hold the form.
- Generate one row for each control in the form:
- Call FormElement to generate the label.
- Call FormElement to generate the control(s), passing it the
initial value data for the overall form
- The FormElement selects one of more values from the initial
value passed in by the form, generates HTML for the control.
- FormSection automatically generates tooltip help text for each
control.
- FormSection generates the submit button.
- When the form is posted, the URL specified by the form is invoked
(via AJAX; more on that later).
- The Interactor invokes the post method on the same FormSection
object, passing it a data request to invoke with the form's data
(e.g., a database update request).
- FormSection calls each FormElement to "collect" the data for
that control.
- In the simple case, the FormElement just copies a value from
the incoming posted data to the collection dataset.
- In more complex cases, the FormElement may need to transform
or combine values (examples: checkbox, time).
- FormSection invokes the data request.
- If the data request completes successfully, FormSection is done;
the Interactor then redirects the browser to a new page.
- If there is an error (e.g., user entered bogus data), FormSection
generates error information to display in the form.
- In some cases, the error from the data request will indicate a
specific value that was in error:
- FormSection asks each FormElement if it was responsible for
the error.
- If a FormElement takes responsibility, then FormSection arranges
for the error message to appear next to the element; otherwise
the message appears in an overall "bulletin" at the top of the
page.
- Summary of FormElement responsibilities:
- Register FormElement-specific data request(s).
- Generate HTML for label.
- Generate HTML for the control (including initial value from the
overall form's data).
- Collect/transform data submitted from the browser.
- Indicate which data values came from this element.
- Provide miscellaneous other properties to FormSection, such as
help text.
- A very challenging set of interfaces to design:
- Keep all views as simple as possible:
- "What's the least amount of information to specify a form?"
- "What's the least amount of code to implement a new FormElement"
- Encapsulate the complexities of handling errors, generating
tooltip help text, etc.
- Allow extensibility in many dimensions (e.g. new FormElements,
different ways of displaying errors, etc.)
- FormSection doesn't encapsulate a data structure, but rather a
set of data flows.
- Datasets and Templates
- Issue: how to tie all the pieces together a simple yet flexible
fashion?
- Dataset: flexible storage container for strings.
- Hierarchical collection of name-value pairs (subset of YAML).
last: Anderson
first: Alice
gpa: 3.3
course:
- name: CS349W
grade: A-
- name: CS240
grade: B+
- Can be stored in files as XML, YAML, etc.
- Stored in memory using hierarchical hash tables.
- Examples:
Dataset d = Dataset.newFileInstance("requests.yaml");
String name = d.get("name");
String street = d.getPath("address.street");
- Useful in many different situations:
- When defining Sections, FormElements, etc.: keyword-style
arguments, omit defaultable properties.
- Defining DataRequests: request name, parameters for the request.
- Results of DataRequests: each DataRequest returns a Dataset
containing results.
- In complex cases like tables, the result contains one child
Dataset for each row.
- If an error occurs, a dataset provides a variety of information
about the error (e.g., message, culprit, ...).
- Incoming query values from URL.
- Overall configuration information, such as predefined
requests, information about data managers, etc. (stored in files).
- Advantage of this representation:
- Can generate a Dataset in one place, pass it through multiple
intermediate layers that know nothing about the contents of
the Dataset, to a final destination that does understand the
contents (Example: row data in TableSection).
- Supports optional values: specify only what's interesting,
let the rest default.
- Templates:
- A stripped-down implementation, intended for microscopic use.
Substitutions only, with some conditionals; can't include
arbitrary code.
- Basic substitution and conditional substitution:
Template.expand(
"<input type=\"text\" name=\"@id\" {{class=\"@class\"}} />",
dataset);
- Default values:
<form id="@id" class="@class?{FormSection}">
- A few other forms (choice, @@, etc.)
- Data values come from a Dataset ("@foo") or from additional
arguments to the expand method ("@1").
- Values are checked for special characters, automatically escaped.
- Different styles of quoting: HTML, URL, Javascript string, none
- Templates and datasets are symbiotic:
- Datasets provide values for template expansion.
- Templates are often stored in datasets.
- Benefits:
- Would prefer to allow small snippets of Java in templates for
additional customizability, but Java doesn't support this (requires
eval).
- Javascript support:
- AJAX support: