Pages

Thursday, November 19, 2009

Sinek, spreadsheet component, for JSF 2

Here are some notes on developing a spreadsheet component for JSF. Here is how it looks like :Cells contents are being updated with ajax, user is able to add rows/columns by clicking the buttons on the bottom-right corner. I am calling this component 'sinek'. Probably I will rename it later. To check the source code click here.
Here are the parts that make up the component :
  • org.mca.sinek.jsf
This package has 3 files that make up the main component. UISinek, SinekRenderer and WorkSheet.
  • WorkSheet is the datamodel. Its basically a two dimensioned array of Strings:
 public class WorkSheet {
List<List<String>> sheet;
public Integer getNumOfColumns() {
...
public int getNumOfRows() {
...
public List<String> addEmptyRow() {
...
public void addEmptyCol() {
...
}
It also has methods that return the number of rows, add rows/cols.
  • UISinek is the component that holds the state values. After its added to view it adds the sheet cells as its children. Cells are made up of inputtext components :
 @FacesComponent(value = "sinek")
@ListenerFor(systemEventClass = PostAddToViewEvent.class)
@ResourceDependency(name = "jsf.js", library = "javax.faces", target = "body")
public class UISinek extends UIInput {
...
@Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
Map<String, String> requestParameterMap = getFacesContext().getExternalContext()
.getRequestParameterMap();
String exec = requestParameterMap.get("javax.faces.source");
if((getClientId()+ "_addr").equals(exec)) {
getWorkSheet().addEmptyRow();
} else if((getClientId()+ "_addc").equals(exec)) {
getWorkSheet().addEmptyCol();
}
int count = getChildCount();
createRows();
}
...
private void createRow(List<String> row, int rindex) {
ValueExpression ve = this.getValueExpression("value");
String exp = ve.getExpressionString();
exp = exp.substring(0, exp.length() - 1);
int cl = 0;
for (String cell : row) {
String id = getId() + "_" + rindex + "_" + cl;
if(findComponent(id) != null) {
continue;
}
HtmlInputText in = new HtmlInputText();
ValueExpression el = ELUtils.createValueExpression(exp + ".sheet[" + rindex + "][" + cl + "]}");
in.setId(id);
in.setValueExpression("value", el);
in.setLocalValueSet(true);
in.setOnkeyup("jsf.ajax.request(this,event);");
getChildren().add(in);
cl++;
}
}
}
We are annotating it with the @FaceComponent annotation. It listens for the PostAddToViewEvent with the @ListenFor annotation. When the event fires processEvent method will be fired. Process events creates HtmlInputText components whose values are backed up with the datamodel. Also sets up the ajax call which will update the component values. Because we will be using ajax we have to define it with the @ResourceDependency annotation. The "javax.faces.source" will be used to identify the add col/row events.
  • SinekRenderer is the renderer. It renders our cells into the cells of a table:
 @FacesRenderer(rendererType = "sinek", componentFamily = "javax.faces.Input")
@ResourceDependencies({
@ResourceDependency(name = "sinek.css", library = "org.mca", target = "head"),
@ResourceDependency(name = "jsf.js", library = "javax.faces", target = "body")})
public class SinekRenderer extends Renderer {
@Override
public void encodeBegin(FacesContext facesContext, UIComponent comp) throws IOException {
UISinek sinek = (UISinek) comp;
ResponseWriter writer = facesContext.getResponseWriter();
writer.write("<table id=\"" +sinek.getClientId()+"\" class=\"mctable\">");
int R = sinek.getWorkSheet().getNumOfColumns();
renderFooter(writer, R, sinek);
renderColumnHeaders(writer, R);
renderCells(facesContext, writer, sinek);
writer.write("</table>");
}
private void renderCells(FacesContext facesContext, ResponseWriter writer, UISinek sinek) throws IOException {
int i = 0;
int rno = 1;
int r = sinek.getWorkSheet().getNumOfColumns();
List<UIComponent> rows = sinek.getChildren();
for (UIComponent c : rows) {
if (i % r == 0) {
if (i % 2 == 1) {
writer.write("<tr class=\"altrow\">");
} else {
writer.write("<tr>");
}
rno = renderRowHeader(writer, rno);
}
writer.write("<td>");
c.encodeAll(facesContext);
c.setRendered(true);
writer.write("</td>");
if (i % r == (r - 1)) {
writer.write("</tr>");
}
i++;
}
}
@Override
public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
}
@Override
public boolean getRendersChildren() {
return true;
}
}
Renderers are defined with the @FacesRenderer annotation and again @ResourceDependecy annotation is used for the css and ajax scripts. It renderers the children (cells) so we need to override the encodeChildren and getRendersChildren methods.
  • mca.taglib.xml : Defines the namespace and components.
  • sinek.css : Style sheet for our project.
My example also contains Hello class which is a example bean used on page index.xhtml.
Thats all for now drop me a line if you need more info and be sure to check-out the source.

Tuesday, November 10, 2009

JSF2 Notes

These are the notes I derived from blogs on JSF 2 :
  • Composite Components
Ed Burn's have a number of articles on developing 'composite components'. CC's are for creating true components that we can attach converters, listeners etc. Previously you could do the similar things with the help of the facelets, which was somewhat limited. Rick Hightower has an excelent series of articles about this, called 'Facelets fits JSF like glove'. Here is what a CC looks like in JSF2 :
 <cc:interface
name="inputtext"
displayName="label plus text"
expert="true"
hidden="false"
preferred="true">
<cc:attribute name="label" required="true" />
<cc:attribute name="id" required="true" />
<cc:attribute name="value" required="true" />
</cc:interface>
<cc:implementation>
<h:outputLabel id="lbl_#{cc.attrs.id}" for="#{cc.attrs.id}"
value="#{cc.attrs.label}" />
<h:inputText id="#{cc.attrs.id}" value="#{cc.attrs.value}" />
</cc:implementation>
This forms a common element with an inputtext and its label. Apparently there are many tricks to learn here. Its formed of two parts, first is the interface. As far as I can tell this is the UIComponent part. We define valueholders, attributes, actionsources that our CC will respond to. Second part is the implementation. This is like the renderer part. We glue the components together here.
One thing that I didn't like while trying out these was, with the facelets way I could use the CC with panelGrid component and the components forming the CC would be on different grids. This way I could easily align the components. Now on jsf2, CC truely acts like one component and all the sub components fit into the same grid.
  • We can define exception handlers on faces-config.
From Ryan Lubke's blog, http://blogs.sun.com/rlubke/
  • There is a javax.faces.PROJECT_STAGE context parameter we can play with which will make our application behave differently in different stages. Like on 'development' stage certain components will be added to the view root automatically like perhaps facestrace. We are able to access it through Application.getProjectStage()
  • There is a new Resource handling architecture. By default resources are searched under (webapp_root)/resource or (webapp_root)/META-INF/resources and there is a EL support to load them from the pages. #{resource['lion.jpg']} will generate a url to a image under these locations I mentioned. Spec also enables that different versions of the same resource be available to application.
  • Components can be annotated with @ResourceDependency annotation and provide the list of resources (css, js, images...) they need inorder to work. These resources might be rendered on different parts of the page with the help of h:body and h:head tags.
  • There is 'View Scope' which as long as the view is not changed. It's accessible using UIViewRoot.getViewMap and #{viewScope} through EL.
  • There is a system event listener api where you could listen to events like 'configuration complete'.
  • Now we have control over request parameters. We can validate convert them if needed.

Looks like JSF2 has more nice stuff in it like the 'View Declaration Language'. Spec it self seems to be the best resource at the moment though.