These notes are based on [ RSF Simple Tool] and the subsequent Sakai RSF Test Base tools.
Jan. 13, 1007
Most of the action in sakai.rsf.simple1 happens in the MainProducer object. There some things that I don;t understand about it, though. The object implements the DefaultView interface. Presumably, this is where the fillCompnents() method comes from. Not the case, as seen in the DefaultView interface:
public interface DefaultView { }
So lets have a look at the other interface, ViewComponentProducer:
public interface ViewComponentProducer extends ComponentProducer { public String getViewID(); }
No, but this expxlains where the getViewID() method comes from in MainProducer. So let's have a look at the interface it extends, ComponentProducer:
public interface ComponentProducer { public void fillComponents(UIContainer tofill, ViewParameters viewparams, ComponentChecker checker); }
So fillComponents() comes from here. Some notes from this file:
This interface must be implemented by classes representing a view for the ClassViewHandler.
A "static", that is, application scope component producer. The common arguments are supplied to give the producer as good a chance as possible of not requiring any request-scope dependencies.
These comments raise some interesting questionst about the difference between application and reqeust scoping and their dependencies. What is the difference?
There is some doucmentation on compnent producers in the ComponentProducer wiki page.
Reviewed four existing RSF PPT presentations. Looked at ViewTemplate RSF wiki page. The following is interesting:
This shows a variety of HTML and RSF constructs. The most important distinction is between rsf:id values which contain colons, and those which do not. As explained in the section on IDs, an ID value containing a colon will peer with a UIContainer[1] component representing a "branch point" of the IKAT rendering algorithm, whereas an ID with no colon will peer with a non-container UIComponent instance, representing a leaf of the component tree.
I think this is important. I suspect that the tree-nature of the component tree corresponds to the containment nature of XML. Consider:
<html> <body> <a href="foo.htm">Link Text</a> More text here. </body> </html>
Can be expressed as the following tree:
html body a "Link Text" "More Text Here"
The html and body nodes are containes, presumably generated by UIContainer. The body node is a branching container, since it contains two sub-nodes: another container (a), and a static text leaf. Both text strings are leaf nodes in this example. It is less clear to me how the 'href' parameter of the anchor node is represented in the component tree. It could be a sub-node (per XML schema) or it could be a parameter on the "a" node.
I think that it is central to understanding how the component is built from template elements.
I wonder if there is a way to dump in the in-memory component tree before it is rendered.
Time to conduct a few experiments. First, let's clone the sakai.rsf.simple1 tool to something we can play with. We can call it sakaki.rsf.testbase.
These instructions tell how to create a cloned copy of the RSF Simple Tool to create a hello world type development application called sakai.rsf.testbase.
- Copy MjnHelloWorld to RsfTestBase.
- Edit the project.xml file:
- Change artifactId to rsf-testbase
- Name to RsfTestBase
- Change the tool registration
- Rename file to sakai.rsf.testbase
- Edit id internally to sakai.rsf.testbase
- Update the web.xml file:
- Change the resourceurlbase to sakai-ref.
- Update the filter mapping: servlet-name to sakai.rsf.testbase.
- Update the servlet definition: servlet-name to sakai.rsf.testbase.
- Update the servlet mapping: servlet-name to sakai.rsf.testbase.
- Change the content template
- Refer to RSF Test Base.
Builds, deploys, starts without error, runs correctly.
Now have a test base to experiment with RSF. Backedup.
Exp. 1 - Dulicate Components
Edit main to duplicate the rsf element called "messagefield" twice.
Success. The string "This is the Sakai RSF Simple Tool example." is presented twice, in two different
DIV blocks.
Exp. 2 - Adding a Second Message
While a second data bean could be added, I chose to add anothher property in the HelloBean.
Getters and Setters provided for it. In MainProducer, I added the following line:
UIOutput.make(tofill, "messagesecond", null, "#{hellobean.second}");
Which defines a second element with and ID of "messagesecond" and refernences the new
HelloBean property.
I must have missed something because the second component is not being rendered. Rather, it's is
defaulting to the template text:
Hello, this is the Sakai RSF Test Base Tool.
to be rendered also.
Got it to work. Getters and Setters didn't follow bean naming convensions. Now renders as:
Hello, this is the Sakai RSF Test Base Tool.
<i>This is the second message.</i>
Not that the included HTML formatting information is escaped. This is good to know since it
means that templates ALWAYS have to be used to include formatting.
I can't seem to find the component emitters, such as UIOutput.
The nesting of elements within the component tree is handled by crearing a ndoe, then using
that variable as an argumnet to an inner node. The TaskList application shows this clearly
in TaskListProducer.java.
Examples of command links are also given in this appl. Consider:
UICommand.make(newtask, "submit-new-task", "#{taskListBean.processActionAdd}");
In this case, the command element (link) is embeded inside of the newtask form, so its included
as the parent node. This could be the besis for figuring out how to create simple links.
Exp. 3 - Simple Link
Added this to the template:
<a rsf:id="linktest">Link Test</a><br />
The following line of code was added to MainProducer:
UICommand.make (tofill, "linktest", "#{hellobean.processActionLink}");
note that a call to processActionLink() means it needs to be defined in Hellobean:
public String processActionLink() { return "main"; }
As with JSF, the string return is what causes RSF to advance to another view. That
greatly simplies explaining how such transitions work.
Needed to import UICommand.
This didn't quite work as expected. The follwing markup was generated:
<a name="command link parameters" id="linktest">Link Test</a><br />
It may be the UICommand is used for form buttons and that a different component is used fo
generate links. I'll have a look around. They are in RSFUtils under components
Looks like the one I want is:
public static UILink make(UIContainer parent, String ID, String target);
I'll substitute it and see if it works.
Getting warmer. It shows up as a link now, but render still isn't quite right:
<a href="http://localhost:8080/rsf-testbase/content/templates/#{hellobean.processActionLink}" id="linktest">Link Test</a><br />
The javadoc seems to indicate that a subnode is required to get this right. It would help
to have an example.
Jan. 15, 2007
Here is a snip from the AddItem.html template from CRUDPlus-OTP:
<div class="navIntraTool"> <a href="Items.html" rsf:id="list-items">List Items</a> </div>
The div wrapper is likely that to make sure that the embedded link has a style from Sakai. The interesting part is that the href refers to another RSF template file. Could it be that simple? It's worth a try.
Nope. Not that simple. The href parameter was overridden. So lets see if we can track down how this is handled in AddItemProducer.java:
UIInternalLink.make(tofill, "list-items", new SimpleViewParameters( ItemsProducer.VIEW_ID));
So first off we see that UIInternalLink is used instead of UILink. UILink must be for external links. Code in MainProducer.java looks like this:
UIInternalLink.make(tofill, "linktest", new SimpleViewParameters( MainProducer.VIEW_ID));
This is rendered as:
<a href="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/main?panel=Main" id="linktest">Link Test</a><br />
Clicking on the link actually works. So it would appear that RSF has provided a special kind of link component that makes it simple to link from view to view. SimpleViewParameters is overridding the HREF definition here, with a reference to the main.html view. This is something of a coincidence on my part. It's work an experiment to see if a changing it will create a different HREF link. Let's change it to read:
UIInternalLink.make(tofill, "linktest", new SimpleViewParameters("testlink.html"));
This renders as:
<a href="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/linktest.html?panel=Main" id="linktest">Link Test</a><br />
So if I read this correctly, The simple view parameter creates the HREF, and appends a parameter called panel set to "Main". I could believe that this is the view from whence it comes, but the template is called "main.html", not "Main.html". I also note that a ViewParameter object is passed into MainProducer, which is likely how GET parameters are passed into the veiw.
Notes from Antranig indicated that UIInternalLink was the correct way to link to another view page. The "panel=Main" was inserted supposedly to make the Sakai portal happy, but I've never seen anything like that in Sakai before. Perhaps Antranig is being paranoid (it's worth it, sometimes). He also indicated that the <filter-map> section of web.xml should be servlet triggered, rather than via a URL, so a <servlet-name> block should be used instead of a URL pattern (*/faces).
On the question of Sakai Components, they are present in the SakaiRSFSamples. I wrote back saying that they should be broken out into their own module, or at least rename this module so that people don't think it is only about "samples".
Exp. 4 - Back and Forth Links
The idea of this experiment is to set up two view pages each with links to the other. This allows the user to ping-pong back and forth using the links. We already have a view called "main". We'll add another one called "second". Per RSF best practice, we start with the template designs. The main.html page has been edited to be as follows:
<!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>Sakai RSF Test Base</title> </head> <body> <h1>Sakai RSF Test Base</h1> <div rsf:id="messagefield">To be rendered.</div> <a rsf:id="secondlink">Link to Second Page</a><br /> </body> </html>
Note that the rsf:id for the link has been changed to better document its use.
The second page is a bit more simple:
<!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>Sakai RSF Test Base - Page Two</title> </head> <body> <h1>Sakai RSF Test Base - Page Two</h1> <a rsf:id="mainlink">Link to main page.</a><br /> </body> </html>
On the code side, we'll fix the rsf:id change in MainProducer.java first:
public void fillComponents(UIContainer tofill, ViewParameters origviewparams, ComponentChecker checker) { UIOutput.make(tofill, "messagefield", null, "#{hellobean.message}"); UIInternalLink.make(tofill, "secondlink", new SimpleViewParameters("second.html")); }
Changes build, deploy, and run correctly. Main view page is rendered as:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html > <head > <title >Sakai RSF Test Base</title> </head> <body onload="setMainFrameHeight('Main06082518x433cx4007x8023x27cd8ffdecc7');setFocus(focus_path);"> <h1 >Sakai RSF Test Base</h1> <div id="messagefield">Hello, this is the Sakai RSF Test Base Tool.</div> <a href="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/second.html?panel=Main" id="secondlink">Link to Second Page</a><br /> </body> </html>
Now it comes time to support the creation of the second view page. While other models might be possible, I think I favor having a separate component bean per view. There is no data to be displayed on the second page yet, so we don't need to create a separate request bean yet (I think). MainProducer currently handles creating the component tree for the main view. It implements the DefaultView interface. I don't know if the second page needs this or not, but I'm going to leave it out on the theory that the second page is NOT the default page.
So let's create the second component bean and call it "SecondProducer.java":
/* * Created on Jan. 15, 2007 by mjn * This file is (c) 2007 Nolaria Consulting, all rights reserved. */ package org.sakaiproject.tool.simple.components; import uk.org.ponder.rsf.components.UIContainer; import uk.org.ponder.rsf.components.UIInternalLink; import uk.org.ponder.rsf.viewstate.SimpleViewParameters; import uk.org.ponder.rsf.viewstate.ViewParameters; import uk.org.ponder.rsf.view.ViewComponentProducer; import uk.org.ponder.rsf.view.ComponentChecker; public class SecondProducer implements ViewComponentProducer { public static final String VIEW_ID = "main"; public String getViewID() { return VIEW_ID; } public void fillComponents(UIContainer tofill, ViewParameters origviewparams, ComponentChecker checker) { UIInternalLink.make(tofill, "mainlink", new SimpleViewParameters("main.html")); } }
In theory, the only thing else we need is to wire this new component bean into our application via our Spring applicationContext. Here is the main body:
<beans> <!-- Define the component producer for the main view --> <bean class="org.sakaiproject.tool.simple.components.MainProducer"/> <!-- Define the component producer for the second view --> <bean class="org.sakaiproject.tool.simple.components.SecondProducer"/> <!-- Define the bean roots from the request scope file that are accessible via incoming request URL (not strictly necessary for this app) --> <bean parent="requestAddressibleParent"> <property name="value" value="hellobean"/> </bean> </beans>
SecondProducer has been added as managed bean.
Let's give it a whirl. Well, I think it's working, but it's hard to tell. I'll edit the templates a bit to make it more obvious.
No, it's not working. I thought it might be that I didn't change the VIEW_ID of SecondProducer, but that didn't solve the problem. The URL after clicking reads as follows:
http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/main?errortoken=yLPDZIF8tMWzdafZ3rFVeoNH &errorredirect=1&panel=Main&sakai.tool.placement.id=32019bfa-0057-4934-00f6-0415cb8db3ba
The error tokens imply that it wants to tell me something, but what?
I'm getting errors in catalina.out. Perhaps dropping the DefaultView interface wasn't such a good idea. I'll put it back as see if it fixes things.
Well, it compiled and deployed without error. Even started up without errors. However, the second page came up by default. This suggests that my original supposition was correct that this interface indicates the default page to start the app with. I'll remove it from SecondProducer again.
So it built, deployed and started without error. It correctly defaults to the main view page. On startup from Mercury, some information is printed to catalina.out:
2007-01-15 17:08:58,250 INFO (ReasonableSakaiServlet.java:35) - <ReasonableSakaiServlet starting up for context C:\dev\apache-tomcat-5.5.17\webapps\rsf-testbase>
2007-01-15 17:08:58,437 INFO (SakaiHttpServletFactory.java:99) - <Beginning ToolSinkTunnelServlet service with requestURL of http://localhost:8080/mercury/sakai.rsf.testbase/mercury and extra path of >
2007-01-15 17:08:58,437 INFO (ServletEarlyRequestParser.java:40) - <begin parseRequest for /mercury/sakai.rsf.testbase/mercury>
2007-01-15 17:08:58,468 INFO (SakaiRequestParser.java:94) - <Got tool dispatcher id of sakai.rsf.testbase resourceBaseURL http://localhost:8080/rsf-testbase/ baseURL http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/ and Sakai PID 973bd201-a70e-4975-001d-3f6bb3063386>
2007-01-15 17:08:58,531 INFO (RootHandlerBean.java:107) - <Request handled>
This doesn't seem to shed much light on our problem, which seems to happen when the link to the second view page is clicked.
Close examination of the error messages in catalinal.out from RSF gave me the clue I needed. There was some indication that various attempts were being made to find the name of the second view page including: "second.second.html.html" and "second.html.html". Hmm. It looks like the reference to second page shouldn't contain ".html". So I went back and changed the SimpleViewParamters embedded in the UIInternalLink component creation and dropped the ".html" from both. This makes sense beause now the URL refers to a view ID, which are (buy convention are defined as static constants in each view producer. This now looks like this (in SecondProducer):
UIInternalLink.make(tofill, "mainlink", new SimpleViewParameters("main"));
So, to follow the conventions, this should be coded as (in SecondProducer):
UIInternalLink.make(tofill, "mainlink", new SimpleViewParameters(MainProducer.VIEW_ID));
and (in MainProducer):
UIInternalLink.make(tofill, "secondlink", new SimpleViewParameters(SecondProducer.VIEW_ID));
Recomipiled, deployoed, and it worked.
Jan. 16, 2007
Exp. 5 - Simple Table
Based on a chat with Antranig last night, he seem to feel that list generation was very simple. Since form creation involves UICommand components, tables might be the simpler of the two.
In the TaskList example, we have the following template fragement to describe the task list table:
<form rsf:id="delete-task-form"> <!-- This span is needed here to guide the renderer away to the checkboxes --> <span rsf:id="delete-select"/> <table id="tasks:tasklist" class="listHier"> <thead> <tr> <th class="headerAlignment"></th> <th class="headerAlignment">Owner</th> <th class="headerAlignment">Task</th> <th class="headerAlignment">Date Created</th> </tr> </thead> <tbody id="tasks:tasklist:tbody_element"> <tr rsf:id="task-row:deletable"> <td class="firstColumn"> <input rsf:id="task-select" value="true" type="checkbox"/></td> <td class="secondColumn" rsf:id="task-owner">admin</td> <td class="thirdColumn" rsf:id="task-name">Die</td> <td class="fourthColumn" rsf:id="task-date">May 21, 2006 9:02 AM</td> </tr> <tr rsf:id="task-row:nondeletable"> <td class="firstColumn"></td> <td class="secondColumn" rsf:id="task-owner">admin</td> <td class="thirdColumn" rsf:id="task-name">Eat cheese</td> <td class="fourthColumn" rsf:id="task-date">May 21, 2006 9:02 AM</td> </tr> </tbody> </table> <p class="act"><input rsf:id="delete-tasks" value="Delete" type="submit"/></p> </form>
This example is way more complex than I need it to be for this experiment. For starters, lets eliminate the form aspects of the table, and narrow the number of columns to two:
<table id="tasks:tasklist" class="listHier"> <thead> <tr> <th class="headerAlignment"></th> <th class="headerAlignment">Owner</th> </tr> </thead> <tbody id="tasks:tasklist:tbody_element"> <tr rsf:id="task-row:deletable"> <td class="firstColumn"> <td class="secondColumn" rsf:id="task-owner">admin</td> </tr> <tr rsf:id="task-row:nondeletable"> <td class="firstColumn"></td> <td class="secondColumn" rsf:id="task-owner">admin</td> </tr> </tbody> </table>
The TaskListProducer code includes the use of a UIBranchContainer() in the table generation loop, to decide between task-row:deletable, and task-row:nondeletable. We don't need this kind of switching yet, so it can be elinmated from the template:
<table id="tasks:tasklist" class="listHier"> <thead> <tr> <th class="headerAlignment"></th> <th class="headerAlignment">Owner</th> </tr> </thead> <tbody id="tasks:tasklist:tbody_element"> <tr rsf:id="task-row:deletable"> <td class="firstColumn"> <td class="secondColumn" rsf:id="task-owner">admin</td> </tr> </tbody> </table>
Our table will be based on a list of Thing objects accessed via a class called the ThingManager. The table will have two columns: number and name. Each Thing is therefore required to have a name property. Number will be gernated by the iterator offset. With that in mind, we can rename our rsf:id's:
<table id="thinglist" > <thead> <tr> <th>Number</th> <th>Name</th> </tr> </thead> <tbody id="thinglist:tbody_element"> <tr rsf:id="thing-row"> <td rsf:id="thing-number">number</td> <td rsf:id="thing-name">name</td> </tr> </tbody> </table>
I have further eliminated stylesheet references, as they are purely for decoration (though my designer colleagues might take exception to that). The emphasis in on how this works, not what it will look like. As such, the simpler the better. As near as I can tell, the use of the colon ":" in rsf:id's is largely to keep track of the component node heirarchy. Antranig has this to say about it:
An ID with no colon specifies a "leaf" component, such as UIOutput or UILink which peers with a concrete (HTML) tag structure. An ID with a colon designates a container represented by UIBranchContainer, which will peer with some indetermediate tag such as a div, span or td. The ID prefix is used to identify the resolution set that the IKAT function call will use to select amongst multiple implementations of the template matching the same prefix in their rsf:id.
The suffix of an ID as it appears in a template may be empty (i.e. it may finish with a colon so: message-component indicating that (as a tag) it represents a "default implementation" of the template to be used as a "fallback" where no tag is available matching any suffix that was supplied as part of the component. The template matching process is explained in more detail in the description of the IKAT renderer.
Given that, "thing-number" and "thing-name" are rightly identified as leaf nodes in the component tree. Since "thinglist" is a top level component, it isn't in a container (unless it is the top level container). That leaves the "thinglist:tbody_element", which I'm less sure about. Well, that's the nature of experiments, I guess.
We will insert the HTML table fragment into main.html below the previous mark-up.
For the sake of sanity check, let's build and deploy this with just the template change, no code changes.
The results were not quite what I expected, but understandable. The table is rendered as:
<table id="thinglist"> <thead > <tr > <th >Number</th> <th >Name</th> </tr> </thead> <tbody id="thinglist:tbody_element"> </tbody> </table>
The table headers come through, but the tbody is empty, inspite of placeholders included in the template. That actually makes sense, since there is no corresponding component for the tbody as identified, so no further processing is done. I think I'll add a border to make the table aspects a bit more clear.
Now we turn our hand to adding components into MainProducer. The code in TaskListProducer looks like this:
UIForm deleteform = UIForm.make(tofill, "delete-task-form"); // Create a multiple selection control for the tasks to be deleted. // We will fill in the options at the loop end once we have collected them. UISelect deleteselect = UISelect.makeMultiple(deleteform, "delete-select", null, "#{taskListBean.deleteids}", new String[] {}); Map tasks = taskListManager.findAllTasks(siteId); StringList deletable = new StringList(); // JSF DateTimeConverter is a fun piece of kit - now here's a good way to shed 600 lines: DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, localegetter.get()); for (Iterator iter = tasks.values().iterator(); iter.hasNext();) { Task task = (Task) iter.next(); boolean candelete = task.getOwner().equals(currentuserid); UIBranchContainer taskrow = UIBranchContainer.make(deleteform, candelete ? "task-row:deletable" : "task-row:nondeletable"); if (candelete) { UISelectChoice.make(taskrow, "task-select", deleteselect.getFullID(), deletable.size()); deletable.add(task.getId().toString()); } UIOutput.make(taskrow, "task-name", task.getTask()); UIOutput.make(taskrow, "task-owner", task.getOwner()); UIOutput.make(taskrow, "task-date", df.format(task.getCreationDate())); } deleteselect.optionlist.setValue(deletable.toStringArray()); deleteform.parameters.add(new UIELBinding("#{taskListBean.siteID}", siteId)); UICommand.make(deleteform, "delete-tasks", "#{taskListBean.processActionDelete}");
Note that the rsf:id of the table, "tasks:tasklist" is not directly referenced by this code. A branch container is created for each row in the table:
UIBranchContainer taskrow = UIBranchContainer.make(deleteform, candelete ? "task-row:deletable" : "task-row:nondeletable");
Subsequently, taskrow is used as the component node for adding the td elements.
A candidate coding of the thinglist production:
for (int i=0; i<5; i++) { UIBranchContainer thingrow = UIBranchContainer.make(thinglist, "thing-row"); UIOutput.make(thingrow, "thing-number", i); UIOutput.make(thingrow, "thing-name", "Name goes here."); }
That's pretty simple. Wonder if it will work? Let's give it a try.
Looks like thinglist has to refer to a UIContainer. Changed to "tofill", the outer container for the view.
This part of the code now looks like this:
// Generate a table. for (int i=0; i<5; i++) { UIBranchContainer thingrow = UIBranchContainer.make(tofill, "thing-row"); UIOutput.make(thingrow, "thing-number", Integer.toString(i)); UIOutput.make(thingrow, "thing-name", "Name goes here."); }
The tofill container is now in place, and the interation variable is converted to a String.
I got the following error message:
Fatal internal error handling request: Target exception of class org.xmlpull.v1.XmlPullParserException Successive lines until stack trace show causes progressing to exception site: Error setting dependency viewRender of bean RSFRenderHandler --> Error setting dependency viewTemplate of bean viewRender --> Error parsing view template file /content/templates/main.html --> Error parsing template --> attribute value must start with quotation or apostrophe not 1 (position: TEXT seen ...<br />\r\n\t<br />\r\n\t<table id="thinglist" border=1... @12:32) org.xmlpull.v1.XmlPullParserException: attribute value must start with quotation or apostrophe not 1 (position: TEXT seen ...<br />\r\n\t<br />\r\n\t<table id="thinglist" border=1... @12:32)
Looks like my template is not valide XHTML. It's useful to see this kind of error message because it's very likly to come up. This should be noted in the training material.
The problem in this case happened when I added the "border=1" parameter to the table block. More properly, the border width should be surrounded by quotes. Correcting that revealed the following error:
Fatal internal error handling request: Target exception of class java.lang.IllegalArgumentException Successive lines until stack trace show causes progressing to exception site: Branch container ID must contain a colon character : java.lang.IllegalArgumentException: Branch container ID must contain a colon character :
Currently, my BranchContainer constructionlooks like:
UIBranchContainer thingrow = UIBranchContainer.make(tofill, "thing-row");
In TaskListProducer, this entry refers to the kind of row it is. Maybe a simple reference will do it:
UIBranchContainer thingrow = UIBranchContainer.make(tofill, "thing-row:things");
Well, that worked and table was generated. I don't really know WHY it worked, however. Just as a further experiment, I'm going to change this identifier to just "think-row:". RSFWiki notes seem to indicate that this would work, too. After changing the template and corresponding line in the producer, it worked also. That's good. It makes sense. The colon designates that node as a branch container, ie., a node that can contain sub-nodes, like table rows.
To complete this experiment, I will create a very simple data model and reference it's data.
Spring is giving me problems.
The application context is:
<beans> <!-- Define the component producer for the second view --> <bean class="org.sakaiproject.tool.simple.components.SecondProducer"/> <!-- Define the bean roots from the request scope file that are accessible via incoming request URL (not strictly necessary for this app) --> <bean parent="requestAddressibleParent"> <property name="value" value="hellobean"/> </bean> <!-- Define the component producer for the main view --> <bean id="MainProducer" class="org.sakaiproject.tool.simple.components.MainProducer"> <property name="helloBean" ref="hellobean" /> </bean> </beans>
This should inject a bean called "bellowbean" into a property called "helloBean" of MainProducer.
The request context is where this bean is defined:
<beans> <bean id="hellobean" class="org.sakaiproject.tool.simple.beans.HelloBean"/> </beans>
Spring says it can't find a bean named "hellobean".
Jan. 17, 2007
A conversation with Antranig:
mjnsakai: I'm having a problem with setting up beans in my RSF application.
mjnsakai: I don't know as much as I'd like about it.
AntranigBasman: Take a look at this
AntranigBasman: http://www2.caret.cam.ac.uk/rsfwiki/Wiki.jsp?page=RSAC
mjnsakai: Ok, I'll have a look at it.
AntranigBasman: And also this:
AntranigBasman: http://issues.sakaiproject.org/confluence/display/BOOT/RSF+and+Spring+Contexts
mjnsakai: Do my beans always need a parent?
AntranigBasman: No, you don't always need a parent
AntranigBasman: All the core Spring info is in this chapter:
AntranigBasman: http://static.springframework.org/spring/docs/2.0.x/reference/beans.html
AntranigBasman: "Chapter 3 Spring"
AntranigBasman: Basically the bottom line is you can't refer to a bean at a shorter scope from one at a longer scope
AntranigBasman: http://ponder.org.uk/Albums/slides/DSC03148.html
Aaron's discussion at RSF and Spring Contexts is a good one. He lays out a three tier hierarchy of context scopes:
- Application Context
- Session Context
- Request Context
each of these longer than the next. Ie, application lasts longer than sessions last longer than request. As Antranig says, you can refer to a bean of shorter scope from a longer one. That said, how does the application bean get access to a bean in request scope? Presumbably this is what the RSF expression langauge is for.
More from Antranig:
mjnsakai: So if my view producer is declared in application scope, how do I get at a bean that is instantiated in request scope?
mjnsakai: It's seems reasonable.
AntranigBasman: Well, typically you don't
mjnsakai: why not?
AntranigBasman: Well, because of the scoping issue, for starters
AntranigBasman: In practice you would do one of two things:
AntranigBasman: Either i) move your producer into request scope, or ii) declare a proxy for the bean you are trying to access
AntranigBasman: Option i) would be the more typical one, unless you had very many more producers than beans
mjnsakai: I see that. It makes sense.
mjnsakai: See, I only have the examples to really work from.
mjnsakai: I see things written one way, like TaskList, and assume that's the way it works.
AntranigBasman: Aaron and I debated what to "tell people" about which scope to declare producers at.
AntranigBasman: Which is a debate you could probably enter profitably
mjnsakai: yeah, once I really understand it.
AntranigBasman: I started out giving the full version, but Aaron's first take was that this would confuse people, and you should start with the blanket advice "declare producers at request cope"
mjnsakai: I agree with Aaron.
AntranigBasman: It is really just an efficiency thing, and declaring them at app-scope could be considered a kind of "premature optimisation". Although it is also a kind of "documentation"
AntranigBasman: If you can declare a producer at app-scope, then you should
AntranigBasman: But typically in most "modern" apps, you can't
mjnsakai: Ah, this also leads towards statelessness.
AntranigBasman: One of the goals of OTP (One True Path) is to be able to shift stuff back to app-scope again
AntranigBasman: But so far in the initial crop of RSF apps, most people are going with the model of injecting their request beans into their producers
AntranigBasman: Which is "sort of fine" but was not initially the way RSF was intended to be used
AntranigBasman: However it is a model which it is easier to understand and work with, off the bat
mjnsakai: What shift in thinking is needed to move back to app scope?
AntranigBasman: It sort of restores some of the "everything is within arms' reach" feeling that JSF gave people
AntranigBasman: And which if you take it away, can make people feel a bit nervous
AntranigBasman: The "full-scale" way of dealing with RSF was meant to be to make full use of the EL system
AntranigBasman: And not to try to directly manipulate your request model at all
mjnsakai: Similar to JSF, that way, though RSF EL is simpler.
AntranigBasman: If you take that to the 100% extent, then you can return all your producers into App scope
mjnsakai: I think I see that.
mjnsakai: This doesn't seem to be documented in the wiki.
AntranigBasman: If you take that beyond the 100% extent, then you can even start writing your producers in XML
mjnsakai: That's taking it to an extreme.
AntranigBasman: Yeah, we were waiting a sort of "pedagogical model" for this to arrive
mjnsakai: I think I'm going to trod the common path.
mjnsakai: Work through request scope thinking, then move up.
AntranigBasman: I don't think Aaron and I had really settled the argument, so I wasn't comfortable just splurging more confusing stuff onto the wiki
AntranigBasman: You will see that the majority of the samples uses request-scope producers, except for the fully OTP-ised ones
mjnsakai: I think one of the challenges of RSF is that it is deep.
mjnsakai: It's easy to get overwhelmed with the complex things you can do.
mjnsakai: One of the challengers to teachers is to first simplify, then add progressively.
AntranigBasman: Yes... although the "simple" subset of it is meant to be very simple
AntranigBasman: Steve calls it "Fisher Price simple"
mjnsakai: I like that.
mjnsakai: Simple is good.
mjnsakai: but depth is good, too.
Returning to the problem at hand, it seems that I should move my view producer from application scope into request scope. This should make my data bean visible to MainProducer. This should be a matter of moving the declaration of the MainProducer bean from applicationContext to requestContext.
After editing, the applicationContext.xml file looks like this:
<beans> <!-- Define the component producer for the second view --> <bean class="org.sakaiproject.tool.simple.components.SecondProducer"/> <!-- Define the bean roots from the request scope file that are accessible via incoming request URL (not strictly necessary for this app) --> <bean parent="requestAddressibleParent"> <property name="value" value="hellobean"/> </bean> </beans>
and the requestContext.xml file looks like this:
<beans> <bean id="hellobean" class="org.sakaiproject.tool.simple.beans.HelloBean"/> <!-- Define the component producer for the main view --> <bean id="MainProducer" class="org.sakaiproject.tool.simple.components.MainProducer"> <property name="helloBean" ref="hellobean" /> </bean> </beans>
Well, no errors on startup! Antranig was right. It was just a problem with bean scope referencing. Application shows the view correctly:
Jan. 18, 2007
I'll be moving towards the next experiment, which is either a form or GET with parameters. Some interesting notes from Aaron in his Introduction to RSF:
RSf producers implement ViewComponentProducer:
- Define start page by implementing DefaultView
- Implement NavigationCaseReporter to control "submit" navigation
- Implement ViewParamsReporter to receive query parameters from http request
These two "reporter" classes bearn closer inspection.
RSF View Parameters control the passing of data between page views
- Uses query parameters (GET)
- extends SimpleViewParameters
Should be used when data needs to be sent from one view to another
- Works like standard web variables should
- Can be reused on multiple pages
His example of this is:
public class AddItemViewParameters extends SimpleViewParameters { public Long id; // an identifier for an item public AddItemViewParameters() { } public AddItemViewParameters(String viewID, Long id) { this.id = id; this.viewID = viewID; } public String getParseSpec() { // include a comma delimited list of the // public properties in this class return super.getParseSpec() + ",id"; } }
He has a simple example of generating a table using a loop.
Also talkes about contitional rendering. If no UI component is added to the component tree that corresponds to an rsf:id, it will be skipped in rendering.
Exp. 6 - GET Parameters
I'm already using SimpleViewParameters in MainProducer to create the URL for the UIInternalLink. Presumably, I can add another parameter by extending SimpleViewParameters to include another parameter, as in the example above. This is worth a try, especially since it may have bearing on building the Gallery tool.
The exercise will be to add a "Previous" and "Next" links to the main.html view. A state variable called "slide" will keep track of where we are in the progression.
First, we'll add the two links to our template:
<br /> <a rsf:id="prevlink">Previous</a> <a rsf:id="nextlink">Next</a><br /> <br />
Initally, we'll code both of these to be simple loop backs to main.html.
Code looks like this in MainProducer.java:
... UIOutput.make(tofill, "messagefield", null, "#{hellobean.message}"); UIInternalLink.make(tofill, "secondlink", new SimpleViewParameters(SecondProducer.VIEW_ID)); // Links for Previous and Next. UIInternalLink.make(tofill, "prevlink", new SimpleViewParameters(MainProducer.VIEW_ID)); UIInternalLink.make(tofill, "nextlink", new SimpleViewParameters(MainProducer.VIEW_ID)); ...
This builds, deploys, and runs correctly. Previous and Next links are shown and link back to main page properly.
Now we try to add the slide parameter. SimpleViewParameter is extended to create a slide parameter:
public class AddSlideViewParameters extends SimpleViewParameters { public int slide; // the slide number. public AddSlideViewParameters() { } public AddSlideViewParameters(String viewID, int slide) { this.slide = slide; this.viewID = viewID; } public String getParseSpec() { // include a comma delimited list of the // public properties in this class return super.getParseSpec() + ",slide"; } }
Next, the MainProducer components for the internal links are modified to use this new view parameter mechanism:
public class MainProducer implements ViewComponentProducer, DefaultView { public int slide = 1; public static final String VIEW_ID = "main"; public String getViewID() { return VIEW_ID; } public HelloBean helloBean = null; public void setHelloBean (HelloBean hb) { this.helloBean = hb; } public HelloBean getHelloBean () { return this.helloBean; } public void fillComponents(UIContainer tofill, ViewParameters origviewparams, ComponentChecker checker) { UIOutput.make(tofill, "messagefield", null, "#{hellobean.message}"); UIInternalLink.make(tofill, "secondlink", new SimpleViewParameters(SecondProducer.VIEW_ID)); // Links for Previous and Next. UIInternalLink.make(tofill, "prevlink", new AddSlideViewParameters(MainProducer.VIEW_ID), this.slide-1); UIInternalLink.make(tofill, "nextlink", new AddSlideViewParameters(MainProducer.VIEW_ID), this.slide+1); ...
This works. Template is rendered as:
<br /> <a href="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/main?slide=0&panel=Main" id="prevlink">Previous</a> <a href="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/main?slide=2&panel=Main" id="nextlink">Next</a><br /> <br />
The slide numbers are correctly set to one before and one after the current slide number (defaulting to 1).
The next step is to extract the parameter and set it as the current slide number. This is where ViewParamsReporter comes in.
Ok, a brief chat with Antranig seems to be clearing this up. The AddSlideViewParameters object that I created is used to both add parameters and get them out later. When implementing the getVieParameters() method of the ViewParamsReporter interface, I construct an object of the same type and return it. Presumably the slide value number is injected somehow. So the local method is:
public ViewParameters getViewParameters() { ViewParameters slide = new AddSlideViewParameters(); return slide; }
And the extraction of the new slide value is:
public void fillComponents(UIContainer tofill, ViewParameters origviewparams, ComponentChecker checker) { AddSlideViewParameters vp = (AddSlideViewParameters)this.getViewParameters(); if (vp != null) this.slide = vp.slide; ... }
Actually, this turns out to be even simpler than I was trying to make it. The ViewParameter object passed into the ViewProducer can be upcast to AddSlideViewParameters, as follows:
public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) { try { this.slide = ((AddSlideViewParameters) params).slide; } catch (Exception e) { this.slide = 1; } System.out.println ("\n**** MainProducer: slide view parameter is:"+this.slide);
If no parameters are passed (as on the first link, what ever the default value of slide is will be used. Thus, we should intialize the parameters in AddSlideViewParameters:
public class AddSlideViewParameters extends SimpleViewParameters { public int slide = 1; // the slide number, which defaults to one if not supplied. ...
Note that multiple parameters can be encoded using the same, single ViewParameters object, as long as the parse pattern is set appropriately.
Exp. 7 - Conditional Generation of Content
The slide number controls provide a good opportunity to implement conditionalize content since we don't want the current slide to ever go below 1. In theory, there is an upper bound on next as well, but only one example is needed for purposes of experimentation.
RSF allow one component to be substituted for another when the component tree is being created. Thus, we can replace a UIOutput component for the UIInternalLink depending on the current value of slide:
// Links for Previous and Next. if (this.slide > 1) UIInternalLink.make(tofill, "prevlink", new AddSlideViewParameters(MainProducer.VIEW_ID, this.slide-1)); else UIOutput.make(tofill, "prevlink", null, "Previous"); UIInternalLink.make(tofill, "nextlink", new AddSlideViewParameters(MainProducer.VIEW_ID, this.slide+1));
This would be a bit cleaner to extract the "Previous" string from a message bundle, but it illustrates the point. This change worked (first time!).
Jan. 19, 2007
Antranig informs me that catching the cast exception in the producer is not needed. I am always garaunteed to have a valid VeiwProducer object passed into a ViewProducer. He has also updated the ViewProducer page in RSFWiki on the basis of my question, which will help those who follow after me.
Exp. 8 - Data Collection using a Form
One of the last major skills I need to learn is collecting information from a user via a form. Antranig recommended the RSF Logon Example to see how forms work.
As before, we start with the UI design. I'm going to add two new views to RsfTest bed: form page to collect information about the current user, and a page to display the collected information. Later this will lead to persisting the information and building internal models, but I don't need that to understand the RSF mechanisms. KISS!
The form view will be called "getinfo" and the display view will be "showinfo". I was going to do a form with radio buttons, but perhaps it would be best to start with simple text fill in fields. The getinfo.html file looks like this:
<!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>Sakai RSF Test Base - Get User Information</title> </head> <body> <h1>Sakai RSF Test Base - Get User Information</h1> <form method="#" rsf:id="info-form"> Enter your name: <input type="text" name="username" size="40" rsf:id="info-username" /><br /> <br /> Enter pet's name: <input type="text" name="petname" size="40" rsf:id="info-petname" /><br /> <input type="submit" value="Submit" rsf:id="info-submit" /><br /> </form> </body> </html>
Note the XHTML conventions that call for terminating standalone tags. I'm also following the rsf:id naming conventions that I've seen in several examples. It seems useful to use a prefix string like "info-" to identify all of the components having to do with the information collection form.
Next, the showinfo.html page looks like this:
<!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>Sakai RSF Test Base - Show User Information</title> </head> <body> <h1>Sakai RSF Test Base - Show User Information</h1> <div rsf:id="show-user">Info about user goes here.</div> <div rsf:id="show-pet">Info about pet goes here.</div> <br /> <a rsf:id="mainlink">Return to Main Page</a><br /> <a rsf:id="infolink">Get Information Again</a><br /> </body> </html>
Besides displaying the information collected, we'll include internal links back to the main page and the information form. Now we need to add the corresponding view produces.
The example strongly implies that I have to use a backing bean to receive the form data. How would I got about using a GET-based form? Time to call Antranig!
mjnsakai: Do I have to use a data bean to capture my form information? AntranigBasman: Yes mjnsakai: Or can I use a form of ViewParameters? AntranigBasman: And yes AntranigBasman: RSF supports both GET and POST forms mjnsakai: I suspected as much. AntranigBasman: a POST form will apply to a data bean AntranigBasman: Whereas a GET form will be mapped onto a ViewParams mjnsakai: Just what you'd expect. AntranigBasman: Given that ViewParams also have support for nested pure beans, the two approaches can be corresponded relatively closely AntranigBasman: Or at least as closely as you need :P AntranigBasman: Clearly the *root* of ViewParams can't be pure, since it needs to carry along the housekeeping stuff which it needs to fork AntranigBasman: But you can derive your own tree of objects from one of the fields that are RSF-free mjnsakai: So how do I set up a GET form using ViewParameters? mjnsakai: Do I pass a ViewParameter derrivative into the UIInput, etc? AntranigBasman: There are just two key things with GET forms - firstly you create the UIForm with the RENDER_REQUEST argument AntranigBasman: Secondly, you need to make sure that the rsf:id for each of the form elements actually corresponds to the names in the particular ViewParameters you are using AntranigBasman: Everything is constrained here by HTTP so you have no choice AntranigBasman: The GET form *will* submit directly against the URL, there is no flexibility mjnsakai: so the rsf:id would would be the form parameter name? AntranigBasman: Yes AntranigBasman: They all become the same mjnsakai: I see. AntranigBasman: So also there are some restrictions on using Branch containers within GET forms AntranigBasman: Although i could do something about that if anyone complained mjnsakai: As long as the form is simple, there should be no problems. mjnsakai: It would take conditionalize forms to call for a Branch container, eh? AntranigBasman: No, actually I remember that I fixed that already :P AntranigBasman: Actually the whole design of RSF is a testament to my poor memory AntranigBasman: The code has to be a lot clearer when you are goldfish :P mjnsakai: I'm a little unclear on Branch containers, actually. AntranigBasman: They induce branching and looping in the template structure AntranigBasman: And are how we manage to keep the templates clear of any logic AntranigBasman: You just write an element with a colon in the ID mjnsakai: I've seen looping. mjnsakai: When is branching needed? AntranigBasman: Branching for where you have alternative HTML representations of a tree section AntranigBasman: Or, more wide-scale, for "reusable components" AntranigBasman: A branch can either be a branch within the same file, or to a different file mjnsakai: ah, that's coming later in my education. AntranigBasman: Of course, simply missing something out is a kind of branch AntranigBasman: So suppressing or showing a particular segment of the template is the simplest use of a branch container mjnsakai: I need to understand re-usable components a bit further down the line. AntranigBasman: OK
So it looks like this can be done with some careful construction of the form parameters. I'm going to go back and modify the form to support a GET request:
<!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>Sakai RSF Test Base - Get User Information</title> </head> <body> <h1>Sakai RSF Test Base - Get User Information</h1> <form method="#" rsf:id="info-form"> Enter your name: <input type="text" name="username" size="40" rsf:id="username" /><br /> <br /> Enter pet's name: <input type="text" name="petname" size="40" rsf:id="petname" /><br /> <input type="submit" value="Submit" rsf:id="login-login" /><br /> </form> </body> </html>
A new view parameters object is created in GetInfoViewParameters to hold form data:
public class AddSlideViewParameters extends GetInfoViewParameters { public String username = "no name"; public String petname = "no pet"; public AddSlideViewParameters() { } public AddSlideViewParameters(String viewID, String user, String pet) { this.viewID = viewID; this.username = user; this.petname = pet; } public String getParseSpec() { // include a comma delimited list of the // public properties in this class return super.getParseSpec() + ",username,petname"; } }
This should allow me to access the form data sent via GET parameters.
Here is the view producer, GetInfoProducer:
public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) { UIForm form = UIForm.make(tofill, "info-form", new GetInfoViewParameters()); UIInput.make(tofill, "username", null); UIInput.make(tofill, "petname", null); UICommand.make(form, "info-submit"); }
I'm a bit unsure about passiing a null value for the binding, but the Javadoc indicate that this was ok. Since I don't have a bean to refer to, I can't use the expression langauge. Added another link to main.html and a component to generate in MainProducer. Finally, here is the ShowInfoProducer:
public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) { GetInfoViewParameters info = (GetInfoViewParameters) params; UIOutput.make(tofill, "show-user", null, "Hi there, "+info.username+", how are you today?"); UIOutput.make(tofill, "show-pet", null, "I see you have your faithful pet, "+info.petname+"with you."); UIInternalLink.make(tofill, "mainlink", new SimpleViewParameters(MainProducer.VIEW_ID)); UIInternalLink.make(tofill, "infolink", new SimpleViewParameters(GetInfoProducer.VIEW_ID)); }
All that remains is to add the new producers as managed beans, and we should be ready to test our form. These could be added to either context, but I added them to the applicationContext.xml file:
<beans> <!-- Define the component producer for the second view --> <bean class="org.sakaiproject.tool.simple.components.SecondProducer"/> <!-- Define the component producer for the get information form view --> <bean class="org.sakaiproject.tool.simple.components.GetInfoProducer"/> <!-- Define the component producer for the show information view --> <bean class="org.sakaiproject.tool.simple.components.ShowInfoProducer"/> <!-- Define the bean roots from the request scope file that are accessible via incoming request URL (not strictly necessary for this app) --> <bean parent="requestAddressibleParent"> <property name="value" value="hellobean"/> </bean> </beans>
Some success. The link to the information form on the main page led to the Info form. The form was rendered with two text input fields, but no submit button! The rsf:id in the template was incorrect ("login-login"), a holdever from the original login application it was copied form. I changed it to "info-submit".
Now I get an erro when I submit the form:
Submitting control value info-submit>
2007-01-19 13:45:39,432 INFO (SakaiRequestParser.java:94) - <Got tool dispatcher
id of sakai.rsf.testbase resourceBaseURL http://localhost:8080/rsf-testbase/ ba
seURL http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/ and Sakai
PID 296254f1-b9e2-495c-00b7-10da95e9b58b>
2007-01-19 13:45:39,448 WARN (RenderHandlerBracketer.java:88) - <Exception rende
ring view: >
Target exception of class java.lang.IllegalArgumentException
Successive lines until stack trace show causes progressing to exception site:
Error setting dependency viewRender of bean RSFRenderHandler
--> Error setting dependency viewTemplate of bean viewRender
--> No TemplateResolverStrategy which was marked as a root resolver (rootPriorit
y > 0) returned a template: tried paths (expected) /content/templates/sakai-null
.html, /content/templates/null.html, classpath:uk/org/ponder/rsf/builtin/templat
es/sakai-null.html, classpath:uk/org/ponder/rsf/builtin/templates/null.html,
java.lang.IllegalArgumentException
at uk.org.ponder.rsf.templateresolver.BasicTemplateResolver.locateTempla
te(BasicTemplateResolver.java:136)
at uk.org.ponder.rsf.templateresolver.TemplateLoaderBean.getObject(Templ
ateLoaderBean.java:31)
It looks like RSF doesn't know which view to transition to after the submit. Normally, this would be hanlded by an action handler. Maybe that's the simplest way to deal with the error, by adding a handler.
I'm not sure if action handlers have a parameter. Currently it's coded as:
public String processActionSubmit () { return ShowInfoProducer.VIEW_ID; }
and referred to using RSF-EL in the GetInfoProducer:
UICommand.make(form, "info-submit", "#{ShowInfoProducer.processActionSubmit}");
Verified that this is likely correct by looking at the TaskList example, which has an action handler.
No help. Fails with the same error message.
Jan 20, 2007
Another short conversation with Antranig:
AntranigBasman: No, actually null as 2nd argument is mandatory for UIInputs that are in a GET form AntranigBasman: Since they could not bind to the model mjnsakai: Ok, thank you for verifying the nulls in UIInputs. However, I have a problem on submitting the form. It fails to go to the view that will process the form. Instead, it returns to the default view. How do I specify the view to process the form using a GET based form. AntranigBasman: You need to use the other constructor for the UIForm AntranigBasman: The one that accepts a ViewParameters mjnsakai: And the view to transfer to is encoded in the ViewParameters that get passed? mjnsakai: Should I pass the VP that encodes the form parameters? AntranigBasman: But I suppose that if someone is perverse enough to ask for this, they should be allowed.... AntranigBasman: Well, there will be a "base VP" AntranigBasman: Against which the form parameters submit mjnsakai: So I don't have to worry about the parameters? AntranigBasman: In general it will just preset the View id and leave the submission stuff empty AntranigBasman: Not quite sure what you mean by "worrying about them" :P mjnsakai: I think I see what i need to do. mjnsakai: I've created an extension of SimpleViewParameters that includes my form parameters. AntranigBasman: yes, that's right. mjnsakai: I need to use this one in the UIForm. AntranigBasman: That's right
Thinking about it, this makes sense. The ViewParameters encodes more than just the data passed in the URL, it encodes the URL itself, including the view to transition to.
The following changes were made. First, I created a new consructor in GetInfoViewParameters:
public GetInfoViewParameters(String viewID) { super (viewID); }
This allows the target view to be specied. I then used this new constructor in the GetInfoProducer:
public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) { UIForm form = UIForm.make(tofill, "info-form", new GetInfoViewParameters(ShowInfoProducer.VIEW_ID)); UIInput.make(tofill, "username", null); UIInput.make(tofill, "petname", null); UICommand.make(form, "info-submit", "#{ShowInfoProducer.processActionSubmit}"); }
Build, deploy, and run. Now able to submit form to show info view. However, the display text doesn't seem to show correctly. I have to have a closer look at the view producer for show info.
The parameters are being passed as the defaults, for some reason. This is the get info form as rendered:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html > <head > <title >Sakai RSF Test Base - Get User Information</title> </head> <body onload="setMainFrameHeight('Main9cecd062xa0aax4f1cx8098xd9fdcfaee0c0');setFocus(focus_path);"> <h1 >Sakai RSF Test Base - Get User Information</h1> <form action="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/showinfo" id="info-form" method="#"> <input type="hidden" name="petname" value="no pet" /> <input type="hidden" name="username" value="no name" /> <input type="hidden" name="panel" value="Main" /> Enter your name: <input value="" type="text" size="40" name="username" id="username"/><br /> <br /> Enter pet's name: <input value="" type="text" size="40" name="petname" id="petname"/><br /> <input value="Submit" type="submit" name="command link parameters&Submitting+control=info-submit" id="info-submit"/><br /> </form> </body> </html>
Note that two hidden fields are created that seem to override the text input fields. They have the same names. My guess is that this is being created by UIForm component. Ah, I think I should be adding the UIInput components to the form component! Let's try that.
GetInfoProducer now looks like this:
public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) { UIForm form = UIForm.make(tofill, "info-form", new GetInfoViewParameters(ShowInfoProducer.VIEW_ID)); UIInput.make(form, "username", null); UIInput.make(form, "petname", null); UICommand.make(form, "info-submit"); }
This is the right track. After all, the input elements are contained in the form element, so they'd need to be represented in the component tree that way, as well. Get info form now renders as:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html > <head > <title >Sakai RSF Test Base - Get User Information</title> </head> <body onload="setMainFrameHeight('Mainb7bac7aex0e06x45ddx80e3x633d61f923ac');setFocus(focus_path);"> <h1 >Sakai RSF Test Base - Get User Information</h1> <form action="http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/showinfo" id="info-form" method="#"> <input type="hidden" name="panel" value="Main" /> Enter your name: <input value="" type="text" size="40" name="username" id="username"/><br /> <br /> Enter pet's name: <input value="" type="text" size="40" name="petname" id="petname"/><br /> <input value="Submit" type="submit" name="command link parameters&Submitting+control=info-submit" id="info-submit"/><br /> </form> </body> </html>
Note that the duplicate hidden fields are now gone. Pressing the submit button still doesn't cause the show info view to render correctly, though. The URL for the show view is:
http://localhost:8080/mercury/sakai.rsf.testbase/mercury/faces/showinfo?
panel=Main&username=Mark+Norton&petname=Rufus&
command+link+parameters%26Submitting%2Bcontrol%3Dinfo-submit=Submit
So you can see that the filled in form values are being passed as GET parameters. Now I need to figure out why they are not being injected into my view parameter (GetInfoViewParameters).
It turns out that I was using UIOput incorrectly. There are two forms of the make method, a four parameter version used when referencing beans (forth param is binding). The three parameter version is what I really needed. This uses the third param, initvalue, as the string to be emmitted. ShowInfoProducer now looks like this:
public void fillComponents(UIContainer tofill, ViewParameters params, ComponentChecker checker) { GetInfoViewParameters info = (GetInfoViewParameters) params; UIOutput.make(tofill, "showuser", "Hi there, "+info.username+", how are you today?"); UIOutput.make(tofill, "showpet", "I see you have your faithful pet, "+info.petname+"with you."); UIInternalLink.make(tofill, "mainlink", new SimpleViewParameters(MainProducer.VIEW_ID)); UIInternalLink.make(tofill, "infolink", new SimpleViewParameters(GetInfoProducer.VIEW_ID)); }
Note that the nulls have been eliminated from UIOutput.make(). This renders and works correctly.
Jan. 22, 2007
Created the RsfExamples package. Started work on example 1 on content. All went well until I tried to access content from a message bundle. After working with Antranig, the problem was eventually solved:
mjnsakai: I have a question concerning getting at Message bundles.
mjnsakai: I declared it in my requestScope, but I don't know how to refer to it using EL.
AntranigBasman: If you use MessageLocator, you can just use the "short style"
AntranigBasman: #{messageLocator.message-key}
mjnsakai: hmm. how do I set it up to get such a thing?
AntranigBasman: Otherwise you just inject the bean and use its methods to deliver a String
AntranigBasman: You would do that in more complex cases
mjnsakai: currently using:
mjnsakai: <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/messages "/>
<property name="cacheSeconds" value="10"/>
</bean>
mjnsakai: what class is the message locator?
mjnsakai: given the bean definition above, can I reference it as messageSource?
mjnsakai: I want to say, "#{messageSource.mymsg}"
AntranigBasman: Oh yeah, I see what you mean now
AntranigBasman: Aaron was also a bit unhappy with this
mjnsakai: couldn't get it to wrok?
AntranigBasman: The point is that "messageSource" is the raw materials for what becomes "messageLocator" automatically
mjnsakai: So I have to declare another bean?
AntranigBasman: I.e. the standard RSF semantics are that you give it a standard Spring bundle called "messageSource", and it gives you an RSF locator automatically called "messageLocator"
AntranigBasman: Aaron was keener on a model where messageLocator was declared explicitly
AntranigBasman: Although it's only 2 lines
mjnsakai: I don't see it.
mjnsakai: It's not in the context file.
AntranigBasman: Which don't you see?
mjnsakai: were the message locator is declare or injected.
mjnsakai: So I can use, "#{messageLocator.mymsg}: ?
AntranigBasman: Yes
AntranigBasman: https://saffron.caret.cam.ac.uk/svn/projects/SakaiRSFSamples/trunk/TaskListRSF-OTP/tool/src/java/org/sakaiproject/tool/tasklist/rsf/TaskListProducer.java
AntranigBasman: Take a look at this one
AntranigBasman: From TaskList-OTP
AntranigBasman: This shows the "short style"
AntranigBasman: So, have you declared "messageSource" properly
AntranigBasman: For example here is the one in taskList-OTP
AntranigBasman: <!-- Spring messageSource replaces JSF message-bundle from faces-config.xml -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/sakaiproject/tool/tasklist/bundle/Messages"/>
<property name="cacheSeconds" value="10" />
</bean>
mjnsakai: I tried two different locations: in a bundle/.... and in WEB-INF/messages.
AntranigBasman: Note that this must be at app-scope
mjnsakai: ah, I have it in request scope. Easy change.
AntranigBasman: Yeah, it has no modifiable content, so would naturally stay out of request-sope
AntranigBasman: So, the non-OTP version shows the "long style" of using MessageLocator
mjnsakai: hmm, musta missed that.
AntranigBasman: https://saffron.caret.cam.ac.uk/svn/projects/SakaiRSFSamples/trunk/TaskListRSF/tool/src/java/org/sakaiproject/tool/tasklist/rsf/TaskListProducer.java
AntranigBasman: You would use this if you had more complex formatting to do
AntranigBasman: UIOutput.make(tofill, "task-list-title", messageLocator
.getMessage("task_list_title"));
mjnsakai: Do I always have to inject this, or can I get at it via EL?
AntranigBasman: Together with injecting the locator directly
AntranigBasman: public void setMessageLocator(MessageLocator messageLocator) {
this.messageLocator = messageLocator;
}
AntranigBasman: So you can just use the form like this:
AntranigBasman: UIOutput.make(tofill, "task-list-title", null, "#{messageLocator.task_list_title}");
AntranigBasman: Which doesn't need anything injected
AntranigBasman: I should really cut off the {# from this example too...
mjnsakai: Ultimately, the problem with not finding the messages turned out to be a maven problem. The resource bundle needs to be included in the build section.
So the lesson learned is that you gotta include your resources in Maven. If it seems like RSF (etc) isn't finding a file, then likely it is not in the classpath.
Jan. 25, 2007
I would like to use components described by the Sakai Style Guide in the Gallery Tool that I'm writing. So, I had a look at the Samkai compoments included in SakaiSamples. Unfortunately, I only see examples of how to include FCKEdit there. A chat with Antranig followed:
mjnsakai: I was reading up on RSF Sakai Components
mjnsakai: Is it the case that only edtors are availbe?
AntranigBasman: No, that's just the only component that needed to be Sakai-specific
AntranigBasman: The other components should work in any environment
mjnsakai: I understand that.. However, the Sakai Sytle guide defines many other components
AntranigBasman: Yeah, so it does
mjnsakai: Many of these are also Sakai-specific. That was kinda the point.
AntranigBasman: Well, as soon as anyone actually wants any of them, it would take no time to port
mjnsakai: I am interested in exporing that.
AntranigBasman: I chose Gonzalo's "double select" one as being probably the most complicated, and it only took an afternoon
mjnsakai: at least some of them.
mjnsakai: where is that code?
AntranigBasman: But other than that I thought it would be a bit rash to just convert everything he had on his site without some evidence of priority or demand
AntranigBasman: This is in plain RSFComponents
mjnsakai: Do you have documentation on how to create a re-usable component?
AntranigBasman: There is my powerpoint from Atlanta - I am "in process" of wikifying that
AntranigBasman: Ah wait, I think that might well be the one that I never uploaded because I lost the last part of it
AntranigBasman: Which I have not yet regenerated
AntranigBasman: I'll mail you the trunk of it...
mjnsakai: ok, that would help.
AntranigBasman: It got a bit esoteric at the point where it crashed anyway
AntranigBasman: https://saffron.caret.cam.ac.uk/svn/projects/RSFComponents/trunk/templates/src/webapp/content/templates/double-list-select.html
AntranigBasman: So this is the RSF template for the double-select, which is near 100%-identical to Gonzalo's markup
AntranigBasman: And this is the producer for it
AntranigBasman: https://saffron.caret.cam.ac.uk/svn/projects/RSFComponents/trunk/evolvers/src/java/uk/ac/cam/caret/rsf/evolverimpl/StandardDoubleSelectEvolver.java
mjnsakai: one more question.
mjnsakai: Is it possible to set the parameters associated with a template tag in the ViewProducer?
mjnsakai: Like, a style, for example.
AntranigBasman: Yes. This is all done through "Decorators"
AntranigBasman: There are standard, portable ones, and you can drop down into the raw XML if you really need to
AntranigBasman: http://www2.caret.cam.ac.uk/rsfwiki/Wiki.jsp?page=Decorators
mjnsakai: So I could add DHTML stuff this way?
AntranigBasman: You could, but you shouldn't
mjnsakai: onMouseOver, etc.
AntranigBasman: Please don't
AntranigBasman: Anyway, take a look at the "double-select" component
AntranigBasman: This shows the style of Javascript etc. that I think should be recommended
AntranigBasman: The whole point is that the template should be self-contained and previewable
AntranigBasman: Not only in style, but in its intrinsic behaviour
AntranigBasman: So the only thing that should be being injected from the server should be the bare minimum of info needed for configuration
AntranigBasman: So if you load that template in the filesystem, you will see it actually WORKS
AntranigBasman: When you click on the buttons and controls, the items really do move from one side to the other
AntranigBasman: Also rendering an "onMouseOver" by hand violates separation of logic and view
AntranigBasman: Not to say making an unholy unportable mess that will confuse the hell out of the designers...
mjnsakai: I was trying to think of an example of a tag parameter that wasn't decoration oriented.
AntranigBasman: Well, in a perfect world, there ain't no such thing
AntranigBasman: But actually I was using the term "Decorator" in the GOF-patterns sense...
mjnsakai: like computing a URL and stuffing it into a UIExternalLink.
AntranigBasman: As in "thing conforming to a standard interface that can be added in a composite way to another thing orthogonally"
mjnsakai: I'm familiar with the pattern.
AntranigBasman: Yeah, so for that "bare minimum" info, what I recommend is the "render single Javascript call" approach
AntranigBasman: If you look in the double-select producer, you will see this line:
AntranigBasman: String initdate = HTMLUtil.emitJavascriptCall(JSInitName, new String[] {togo.getFullID()});
AntranigBasman: Which is sort of the "minimum-impact" parachuting into the client-side
AntranigBasman: The date widget has a slightly bigger one, but it is still the same idea
mjnsakai: I think i see
mjnsakai: Was the date widget converted, too?
AntranigBasman: Well, I wrote that basically from the requirements on Confluence
AntranigBasman: Since there wasn't really a working example to start from
AntranigBasman: String initdate =
HTMLUtil.emitJavascriptCall(JSInitName, new String[] {togo.getFullID(), title.get(), ttb, vsh.getFullURL(uvbparams)});
AntranigBasman: So in this case as well as the "ID location" of the component, there is some server binding info and an AJAX URL
mjnsakai: These all need to be collected into a set of standard Sakai components.
mjnsakai: Otherwise, people will re-write them.
mjnsakai: differently.
AntranigBasman: My component is prominently advertised on the Confluence page
AntranigBasman: Kathy is evaluating it now...
mjnsakai: which confluence page?
AntranigBasman: http://bugs.sakaiproject.org/confluence/display/RES/Date+widget+improvements
According to the recent RSF Roadmap, multi-file templates (MFT's) were added in RSF 0.7 along the the concept of evolvers. Evolvers are considered to be mini-producers, that allow dynamically interchangeable components to be injected into the production hierarchy, typically using an existing concrete RSF component as a seed in order to reuse its binding functionality.
Drilling down into the RsfCompoments examples, I find the following evolver classes:
- BrokenDateInputEvolver (which presumbably doesn't work)
- DoubleSelectEvolver
- FieldDateInputEvolver
- SandardDoubleSelectEvolver
The double select control allows selections to be moved back and forth between two controls, typically used to add and remove things from a remote list, etc.
The DoubleSelectEvolver is an interface defined as:
/** A selection control implemented using two labelled selections */ public interface DoubleSelectEvolver extends SelectEvolver { public void setDestPickText(String destPickText); public void setSourcePickText(String sourcePickText); }
It extends SelectEvolver to have two selections: source and dest. SelectEvolver is defined to be:
import uk.org.ponder.rsf.components.UIJointContainer; import uk.org.ponder.rsf.components.UISelect; public interface SelectEvolver { public UIJointContainer evolveSelect(UISelect toevolve); }
The StandardDoubleSelectEvolver is implemented as: