A very typical requirement is setting up a standard set of headers/footers etc. that surround every page in an application. This is done by setting up an outer page template, which in some frameworks is called a layout. If you have some experience of building up Producer networks and writing RSF components, it will be no surprise to find that this is handled in RSF by the familiar means of setting up pure HTML templates with appropriate rsf:ids and arranging injection in Spring of the relevant producers.
In this case, most of the necessary Spring definitions are already set up within RSF, and it just a matter of rewiring them slightly. Let's start at the HTML templating level to get a clear overview of what is going on.
Outer page template example#
This is a sample template, in this case taken from the Hibernate Cookbook sample application, which defines the outer, standard part of the page, together with a branch tag, here named page-replace:, indicating where the page body is to be replaced with the different views for each page on the site:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns:rsf="http://ponder.org.uk/rsf"> <head> <title>Online Cookbook</title> </head> <body> <h1>Online Cookbook</h1> <div rsf:id="page-replace:"> <!-- Page body will go here --> </div> <p> <a href="recipe-edit.html" rsf:id="new-recipe">Create new recipe</a> <a href="recipes.html" rsf:id="recipes-all">Show all recipes</a> <a href="categories.html" rsf:id="categories-all">Show all categories</a> </p> </body> </html>
Note that there is nothing "unusual" about this template - it is in fact indistinguishable from any normal RSF pure HTML template, obeying the same rules for any components peering with tags in it arranged by rsf:id.
NullaryProducer for the outer template#
There is however a slight difference with the producer that is paired up with our page layout template - since this markup is expected to be produced for every view in the application, the associated producer is not a ViewComponentProducer as it is for view templates, but the much more slimline NullaryProducer:public interface NullaryProducer { public void fillComponents(UIContainer tofill); }
NullaryProducer is in fact the simplest possible Producer interface, supplying no other context except the container to be filled. This makes sense, since in this case component production is beginning at the very root of the component tree, with no other context known to the framework.
The implementation of the producer backing the template above looks like this:
public class Layout implements NullaryProducer { private NullaryProducer pageproducer; public void setPageProducer(NullaryProducer pageproducer) { this.pageproducer = pageproducer; } public void fillComponents(UIContainer tofill) { UIInternalLink.make(tofill, "recipe-create", new EntityCentredViewParameters(RecipeEdit.VIEW_ID, new EntityID("Recipe", "new 1"), EntityCentredViewParameters.MODE_NEW)); UIInternalLink.make(tofill, "recipes-all", new SimpleViewParameters( Recipes.VIEW_ID)); UIInternalLink.make(tofill, "categories-all", new SimpleViewParameters( Categories.VIEW_ID)); UIJointContainer page = new UIJointContainer(tofill, "page-replace:", "page:"); // include the components from the page body into tag "page-replace:" pageproducer.fillComponents(page); } }
Again, notice that it functions like any other RSF producer, generating components to match the rsf:ids of the tags in the template. Once it reaches the <div> where the page body is to be inserted, it must defer to the framework to fetch the correct component subtree for this view. It does this in a way extremely similar to that of an RSF Evolver which is the basis of the RSF "widget" system, by creating a UIJointContainer which forces the renderer to take a branch to another template - and then filling the container with the subtree obtained from the framework, via another NullaryProducer that is injected in.
In this case the client ID and joint IDs for the UIJointContainer, page-replace: and page: are at the discretion of the author. The value used for page-replace: must match the ID used in your outer page template, like the one above. The value used for page: must match the one used in your actual view pages (see below).
Spring definitions for outer producer and templates#
Our producer implemention is registered with Spring in the normal way - it needs to acquire an extra dependency from the framework in order to fill its joint when required:
<bean id="pageProducer" class="uk.org.ponder.rsf.cookbook.producers.Layout"> <property name="pageProducer" ref="pageBasicProducer"/> </bean>pageBasicProducer is a standard RSF request-scope bean implementing the NullaryProducer interface, which will deliver all the components fetched from the ViewProducers for the current request, whatever they are. The name pageProducer for this bean is also fixed - giving your bean this name indicates that you are replacing the standard pageProducer which produces the ViewRoot to RSF with your own implementation. pageProducer must be declared at request scope.
Finally you need to register the HTML template file used for the outer layout with RSF, using the standard parent definition rootTemplateContributorParent:
<!-- Register the template "layout.html" as defining root layout for each page in the application (extension is auto-inferred from view type), base directory is defaulted from "defaultTemplatePath" -> content/templates/ --> <bean parent="rootTemplateContributorParent"> <property name="templateNames" value="layout"/> </bean>This is very similar to the templateContributorParent used for standard RSF UI components
Writing page templates in your application#
The ViewProducers in your application are unchanged whether you are using outer page templates/layouts or not. The view templates are changed very slightly in that they must contain a branch tag enclosing the part of their markup which is intended to be included into the layout. For example, we set up our outer producer above to use the joint ID of page:, and this is the branch ID we must use in our view templates.For example, here is the template for one of the views in our CookBook application:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns:rsf="http://ponder.org.uk/rsf"> <head> <title rsf:id="edit-title">Edit category</title> </head> <body> <div rsf:id="page:"> <h1 rsf:id="new-category-heading">New category</h1> <h1 rsf:id="edit-category-heading">Edit category</h1> <!-- Etc., Rest of view template in here --> </div> </body> </html>
We include enough header definitions to make this a valid XHTML document, but note that everything outside the tag <div rsf:id="page:"> will be stripped out on rendering. The UIJointContainer = new UIJointContainer(tofill, "page-replace:", "page:"); we issued in our outer producer will join the tag <div rsf:id="page-replace:"> in the outer template to the tag <div rsf:id="page:"> for whichever is the template for the current view.