Wednesday, May 26, 2010

Extentending richFaces component with CDK

Lately, we needed new user interface components, as it common in a new
project. Since we use JSF and richFaces, it was natural to use richFaces
CDK. It is promoted, as a technology that allows to create JSF
components in an easy and simple way and most richFaces components were
created with it. I've read the tutorials and started to develop calendar
component. The development was not so simple as in the tutorial example,
but in comparison with the work that I would have done in order to
develop a JSF component from scratch, it is worthwhile. However, the
fact that all tutorials of CDK give one simple example of InputDate
component and there are no articles about the internal structure of
components, there were sone issues that drove me crazy and created a
high network load to richFaces forum in JBoss community. The reason of
most of my issues was that I created components that were similar to the
existing richFaces components, but added some functionalities, without
understanding how these components work.

The way to create component:

Full component creation proccess using richFiaces CDK is described in their tutorial.
Please readhttp://docs.jboss.org/richfaces/latest_3_3_X/en/cdkguide/html_single
before you start. I will just sum-up the tutorial.


1. Maven2: richFaces CDK is in fact a Maven2 plugin and your project should be defined as a Maven project with Maven-cdk-plugin from the beginning. Create Maven project in your IDE, manually or using the archetype mentioned in tutorial. If you use eclipse you need to install m2eclipse plugin. I've experienced a strange issue with it. In the last step before installation I was asked to agree with the licence. However, approving reading the rules, didn't enable the finish button and I couldn't continue installation. When I tried to download it in second time, I installed plugin without problems. Other members of my team that has tried to install the plugin had experienced the same problem.
2. Create or edit an XML file that defines the component: name of the component, name of Java class for component, name of Java class that renders it, tag name in generated tag library, name of JSP like template and component attributes definitions.
3. Create or edit a component class that presents the component in JSF component tree. It should contain setters and getters methods for each of the component attributes and should inheret or override methods to deal with events . Your class should be abstract to allow CDK to do most of the job by creating real component class. For example, if the setter and the getter just set and get values you can define it abstract and CDK will generate the code for it.
4. Create renderer base class. This class is also abstract and it should include encode/decode methods that link between component at client side and its representation in server-side JSF component tree. For example, most components have this piece of code in their doDecode() method:

ExternalContext external = context.getExternalContext();
Map requestParams = external.getRequestParameterMap();
UIMyComponent component = (UIMyComponent)component;
String clientId = component.getClientId( context );
String submittedValue = (String)requestParams.get( clientId );
if ( submittedValue != null ) {
component.setSubmittedValue( submittedValue );
}
This method is called every time the request submitted by user or by AJAX. The code above takes the value submitted and saves it in component class instance. A doEncode() method should create client side HTML for component, but most of its code is generated by CDK from JSP like jspx file. Other methods that are described in JSF manuals can be created, if you need it.
5. JSPX template. This file aims to be an ultimative advantage of CDK. Instead of implementing encode() method, that adds all client code to StringBuffer line-to-line as in old servlets, you just write JSP like file, that defines how component should look in client side and CDK will do the rest. Component attributes and methods defined in renderer base class can be used. For example, I need to display organizer in my component. I can create getWeekDays() ethod in my renderer class and call it in jspx template to render list of days of the week.

6. Run CDK plugin and enjoy debugging. At this point you can generate the first version of your component with Maven2 and start to debug it. You just include generated jar file into some web project, add taglib to any jsp and use component with the name that you've defined in XML definition, in step 1.


Here are the problems that I confronted in a chronological order:
1. Nested loops: The things look fine, in theory. It is not the first time that I use maven, so I came to step 5 quickly. Ooops, Maven reports that generated class has compiler errors. Ok, I'll go to forum. Another oops, there is a forum for developers that use richFaces components, but there is no forum for those that want to create its own components. The post in richFaces forums has no answer for two days and I start to dig into the problem by myself and find something. I use nested forEach tags in jspx to create table with named rows and columns. CDK converts forEach into java for loops, but use same variable name for both loops. The result is compilation error that I've seen. At this moment I finaly get an answer from forum, that nested loops are not supported by CDK, but such support will be added in next version. Thanks, it is very helpful.
I must indicate that in other cases, when I've added JSF and CDK tags to post and defined it as a question, I've been quickly answered by Nick or Ilya, th members of CDK development team, and I appreciate the help I've received.
Link to forum: http://community.jboss.org/en/richfaces?view=discussions&start=0
Finally my problem was solved by using two different tags for nested loops c:forEach and i:repeat tags. Don't try to use version ofc:forEach with 'begin', 'end' and 'step' attributes if you want to put variables in it. As I'll write below, it will not work. Add another method to renderer and use version of c:forEach, that iterates list returned from it.

You can't do this:

<c:forEach var="trName"
items="#{this:getHoursList( component)}">
#{trName}
<c:set var="hourIndex" value="0"/>
<c:forEach var="item"
items="#{this:getItem(
component, dayIndex, hourIndex )}>
#{item}
<c:set var="hourIndex" value="${hourIndex+1}"/>
</c:forEach>
<c:set var="dayIndex" value="${dayIndex+1}"/>
c:forEach>

You can't do this:
<c:set var="dayIndex" value="0"/>
<c:set var="rows" value="#{this:getHoursC0unt()}"/>
<c:forEach var="trName"
items="#{this:getHoursList( component )}">
#{trName}
<c:forEach var="hourIndex" begin="0" end="rows">
#{this:getItem(component,dayIndex, hourIndex )}
</c:forEach>
<c:set var="dayIndex" value="${dayIndex+1}"/>
<c:forEach>

But you can do this:
<c:forEach var="trName"
items="#{this:getHoursList(component)}">
#{trName}
<ui:repeat var="item"
value="#{this:getNextRow( component )}">
#{item}
</ui:repeat>
</c:forEach>

2. JSPX is not JSP: I tried to escape the
nested loops problem by using different tags for different loops I
confronted another problem that is not only CDK developers' problem, but
everyone that develops with it should be aware of it. You can use some
tags that you used to write in JSP pages or facelets, but not all of
them. JSPX template is not JSP page and not facelet, if you use one of
the tags that are not defined in 'Chapter 10' of richFaces CDK
reference, you will not see any error, and it will be rendered as a
simple test. For example if you use common <h:outputtext
value="foo"/> in template the component, client side will not render
just "foo", but the whole <h:outputtext value="foo"/> token.

3. Next problem - Types Conversion: I used c:set
tag in the template and I had a problem with it. When CDK converts
template to doEncodeEnd() method, the object that is declared with this
tag is stored in variables map as Object and you'll get
compilation error if you try to use it as type different from Object or
String in your java scriplet. You can write ${hourIndex+1} for
such variables and it will work, but you'll have the problem with <c:forEach
var="item" start="0" end="#{hoursCount}">.

This code work only if second parameter of getRowForTime() method has java.lang.Object type:
<c:set var="hourIndex" value="0"/>
<c:forEach var="trName"
items="#{this:getHoursList( component )}">
#{trName}
<ui:repeat var="item"
value="#{this:getRowForTime( component, hourIndex )}">
#{item}
</ui:repeat>
<c:set var="hourIndex" value="${hourIndex+1}"/>
</c:forEach>
There is c:object tag, but you can't use it twice for
the same variable. Using c:setvar another time, redefines
variable, using c:object another type declares variable second
time in generated code and you have a compilation error.

Good news: If you know about variables map you
can use it in java scriplets to refer expressions defined in template
tags.

This code will not work:
<c:object var="hourIndex" value="0" type="java.lang.Integer"/>
<c:forEach var="trName"
items="#{this:getHoursList( component )}">
<c:object var="hourIndex" type="java.lang.Integer"
value="#{hourIndex+1}"/>
#{trName}
<ui:repeat var="item"
value="#{this:getRowForTime(
component, hourIndex )}">
#{item}
</ui:repeat>
</c:forEach>

This code works:
<c:set var="hourIndex" value="1"/>
<c:forEach var="trName" items="#{this:getHoursList( component )}">
<c:set var="hourIndex" value="${hourIndex+1}"/>
<c:object var="hourIndex" type="java.lang.Object"
value="#{hourIndex}"/>
#{trName}
<ui:repeat var="item"
value="#{this:getRowForTime( component, hourIndex )}">
#{item}
</ui:repeat>
</c:forEach>

4. User defined set or get : If you define get method
for any component's attribute, next code should be added at the
beginning:
//Try to read model as EL-expression
ValueExpression ve = getValueExpression( "attrName" );
if( ve != null ) {
try {
_attr = (AttrType) ve.getValue (getFacesContext().getELContext() );
} catch( ELException e ) {
....
}
}
This code retrieves the value of the attribute at the client
side. The problem is not obvious, because in most cases the setters and
getters are abstract and CDK adds this code in the generated code. In my
case, it created a mysterious issue. I had a dataModel attribute in some
component, that should be received only once from a backing bean or an
empty model will be created if no model was received. When I started to
debug component the model was always empty. Debugging showed, that a
setter method was called for each of the component attributes, except
for the dataModel, while the component was created. The problem
disappeared after I had added the above code in the getter. I think that
getDataModel() was called somewhere by the genereated code and created a
new empty model. After that, when setter methods were called, the model
was not null and values from backing bean were not set, because I've
defined that model will be set only once.

5. RichFaces uses Prototype: It is not shown in CDK
tutorial, but all richfaces components have client-side javascript
prototype classes, and you have to extend both server side code and
JS-Prototype, if you want to extend existing component. See richFaces
source codehttp://anonsvn.jboss.org/repos/richfaces/branches/community/3.3.X/
for examples. You can do it in the way below( in my case I am extending
richCalendar, with new control, creating macro variable for the control
to enable its use in any part of component and define this control as
displayed by default in the header) :
//creates richfaces utility class if it doesn't exists yet.
if (!window.Richfaces) window.Richfaces={};
//Defines calendarTypeControl and overrides default header
//to add it.
Object.extend( CalendarView, {
calendarTypeControl: function( context ) {
if( !context.calendar.params.showTypeButton ) {
return "";
}
var text = context.calendar.params.labels[
context.calendar.TYPE_NAMES[ context.calendar.calendarType ] ];
var onclic =
"Richfaces.getComponent('calendar',this).switchToNextType();" +
"return true;"
var markup = new E(
'div',
{'class': 'rich-calendar-tool-btn',
'onclick': onclick,
'id': context.calendar.id + 'TypeText' },
[ new ET(text) ]
);
return markup;
},
header: [ new E( 'table', {'border': '0', 'cellpadding': '0',
'cellspacing': '0', 'width': '100%'},
[ new E('tbody',{},
[ new E('tr',{}, [
new E('td',{'class': 'rich-calendar-tool'},
[ new ET(function (context) { return Richfaces.evalMacro( "previousYearControl",
context ) } )
]),
new E('td',{'class': 'rich-calendar-tool'},
[ new ET(function (context) {
return Richfaces.evalMacro( "previousMonthControl",
context)})
]),
new E('td',{'class': 'rich-calendar-month'},
[ new ET(function (context) {
return Richfaces.evalMacro( "currentMonthControl",
context)})
]),
new E('td',{'class': 'rich-calendar-tool'},
[ new ET(function (context) { return Richfaces.evalMacro(
"nextMonthControl",
context)})
]),
new E('td',{'class': 'rich-calendar-tool'},
[ new ET(function (context) {
return Richfaces.evalMacro(
"nextYearControl",
context)})
]),
new E('td',{'class': 'rich-calendar-tool'},
[ new ET(function (context) {
return Richfaces.evalMacro( "calendarTypeControl",
context)})
]),
new E( 'td', {'class': 'rich-calendar-tool rich-calendar-tool-close',
'style':function(context){
return ( this.isEmpty ?
'display:none;' : '' ); } },
[ new ET(function (context) {
return Richfaces.evalMacro(
"closeControl",
context)})
])
])
])
]
)]
} );
//Defines macro variable for calendar type control
Object.extend( CalendarContext.prototype, {
calendarTypeControl: CalendarView.calendarTypeControl
} );
//Redefines default values for calendar to replace header markup with
//new one that includes calendarType control
Richfaces.Calendar.defaultOptions = {
showWeekDaysBar: true,
showWeeksBar: true,
datePattern: "MMM d, yyyy",
horizontalOffset: 0,
verticalOffset: 0,
dayListMarkup: CalendarView.dayList,
weekNumberMarkup: CalendarView.weekNumber,
weekDayMarkup: CalendarView.weekDay,
headerMarkup: CalendarView.header,
footerMarkup: CalendarView.footer,
isDayEnabled: function (context) {return true;},
dayStyleClass: function (context) {return "";},
showHeader: true,
showFooter: true,
direction: "bottom-right",
jointPoint: "bottom-left",
popup: true,
boundaryDatesMode: "inactive",
todayControlMode: "select",
style: "",
className: "",
disabled: false,
readonly: false,
enableManualInput: false,
showInput: true,
resetTimeOnDateSelect: false,
style: "z-index: 3;",
showApplyButton: false,
selectedDate: null,
currentDate: null,
defaultTime: {hours:12,minutes:0}
};

In template( the code behind ... similar to rich:calendar template ):
...
<f:clientid var="clientId">
<h:scripts>
new org.ajax4jsf.javascript.PrototypeScript(),
new org.ajax4jsf.javascript.AjaxScript(),
/org/richfaces/renderkit/html/scripts/events.js,
/org/richfaces/renderkit/html/scripts/utils.js,
/org/richfaces/renderkit/html/scripts/json/json-dom.js,
/org/richfaces/renderkit/html/scripts/scriptaculous/effects.js,
/org/richfaces/renderkit/html/scripts/jquery/jquery.js,
/org/richfaces/renderkit/html/scripts/JQuerySpinBtn.js,
/org/richfaces/renderkit/html/scripts/calendar.js,
/com/sintecmedia/components/renderkit/html/scripts/sintecCalendar.js
</h:scripts>
...
<c:scriptobject var="options"
base="#{this:getSymbolsMap(context, component)}">
<c:scriptoptionattributes="enableManualInput,disabled,readonly,resetTimeOnDateSelect, showApplyButton, styleClass, minDaysInFirstWeek" />
...
<c:scriptoption name="calendarType"
value="#{this:getCalendarType(component)}"/>
<c:scriptoption name="showTypeButton" value="#{this:isShowTypeButton(component)}" />
<c:scriptoption name="typeNames" value="#{this:getTypeNames(component)}"/>
<c:scriptoption name="firstMonth" value="#{this:getFirstMonth(component)}"/>
...
<script type="text/javascript">
<f:call name="writeDefaultSymbols"/>
new SintecCalendar('#{clientId}',
"#{component.asLocale}",
<c:if test="#{not empty options}">
<f:writeasscript value="#{options}"
</c:if>
<c:if test="#{empty options}">
{}
</c:if>,
<f:call name="writeFacetMarkup"/>
).load(
<jsp:scriptlet>/*<![CDATA[*/
writePreloadBody(context, component);
/*]]>*/</jsp:scriptlet>
);
</script>
...

The code above allows to use calendarTypeControl in newCalendar component in two ways: <my:newcalendar/> will display default header with calendarType control and
<pre name="code" class="Cpp" name="code" class="Cpp">
<my:newcalendar>
<f:facet name="someFacet">
<h:outputtext value="{calendarTypeControl}">
</f:facet>
</my:newcalendar>
</pre>
will display the control in the facet specified.

6. If you want action, you should work hard: richFaces is library
of AJAX components, but richFaces CDK doesn't supports (as of version
3.3.3) AJAX automatically, as it seems. You have JAVA and JS API that do
this work, but you can't just write in a template things like <ajax:request
params="a,b,c" values="1,2,3">. The things are even more complicated. You should create JS event for ajax call and attach it to JS proptotype of component. After that, you should create event observer in doDecode() and getSubmitFunction() and getOnClick() methods in renderer. Finally you should add JS code to create event observer and getOnClick() call where it should be fired. Here is example code that allows to select day of the week:

In renderer:
...
protected void doDecode( FacesContext context, UIComponent component ) {
Map paramMap = getParamMap( context );
String clientId = component.getClientId( context );
//If param is null, then there was no day selected in this
//request and no event should be queued.
String param = (String) paramMap.get( clientId );
if( param == null ) {
return;
}
UIWeekDaysButtons dayButtons = (UIWeekDaysButtons) component;
DaySelectedEvent event =
new DaySelectedEvent( dayButtons, Integer.valueOf( param ) );
event.queue();
}
private Map getParamMap( FacesContext context ) {
return context.getExternalContext().getRequestParameterMap();
}
/**
* @return JS expression what fires day selected event
*/
public String getOnClick(
FacesContext context, UIComponent component, Integer dayIndex ) {
return
"Event.fire(this, 'rich:weekdaysbuttons:ondayselect', {'dayIndex': '"+
dayIndex + "'});changeSelection(this);";
}
/**
* @param context
* @param component
* @return code of Javascript function that submits event through
AJAX request
*/
public String getSubmitFunction(
FacesContext context, UIComponent component ) {
JSFunctionDefinition definition = new JSFunctionDefinition("event");
JSFunction function =
AjaxRendererUtils.buildAjaxFunction( component, context );
Map eventOptions =
AjaxRendererUtils.buildEventOptions( context, component, true );
Map parameters = (Map) eventOptions.get( "parameters" );
Map params = getParameters( context,component );
if(!params.isEmpty()){
parameters.putAll(params);
}
parameters.put(
component.getClientId(context),
new JSLiteral("event.memo.dayIndex") );
function.addParameter(eventOptions);
StringBuffer buffer = new StringBuffer();
function.appendScript(buffer);
buffer.append("; return false;");
definition.addToBody(buffer.toString());
return definition.toScript();
}
//get UIParameter's Map
protected Map getParameters(
FacesContext context, UIComponent component ) {
Map parameters = new HashMap();
if( component instanceof UIWeekDaysButtons ){
UIWeekDaysButtons weekDaysButtons =
(UIWeekDaysButtons)component;
List children = weekDaysButtons.getChildren();
for (Iterator iterator = children.iterator(); iterator.hasNext(); ) {
UIComponent child = (UIComponent) iterator.next();
if(child instanceof UIParameter) {
UIParameter param = (UIParameter)child;
String name = param.getName();
if (name != null) {
parameters.put(name, param.getValue());
}
}
}
}
return parameters;
}
...

In template:
...
<h:scripts>
new org.ajax4jsf.javascript.PrototypeScript(),
new org.ajax4jsf.javascript.AjaxScript(),
/renderkit/html/scripts/weekDaysButtons.js
</h:scripts>
...
<c:forEach var="day" items="#{this:getDaysOfWeek( component )}">
<c:object var="dayName" type="java.lang.Object" value="#{day}"/>
<c:object var="dayIndex" type="java.lang.Integer"
value="#{this:getIndexByDay( component, day )}"/>
<td x:passThruWithExclusions="id,name,type,value,onclick,class"
onclick="#{this:getOnClick( context, component, dayIndex )}"
class="unselected">
#{day}
</td>
<c:forEach/>
...
<script type="text/javascript">
new Richfaces.DaySelector(
'#{clientId}',
#{this:getSubmitFunction(context,component)});
</script>
...

In JS file:
...
if (!window.Richfaces) {
window.Richfaces = {};
}
function changeSelection( element ) {
var selectedDayStyle = /\bselected\b/i;
var unselectedDayStyle = /\bunselected\b/i;
if( element.className.match( selectedDayStyle ) ) {
element.className = element.className.replace(
selectedDayStyle, "unselected" );
} else if( element.className.match( unselectedDayStyle ) ) {
element.className = element.className.replace(
unselectedDayStyle, "selected" );
}
}
Richfaces.DaySelectedEvent = "rich:weekdaysbuttons:ondayselect";
Richfaces.DaySelector = Class.create({
initialize: function(clientId, submitFunction) {
this.element = $(clientId);
this.element.component = this;
this["rich:destructor"] = "destroy";
Event.observe(
this.element,
Richfaces.DaySelectedEvent,
submitFunction );
}
});
...

7. Listeners: another undocumented design pattern. Among rich:calendar
attributes you can see 'dateSelected' and some other attributes that contains
EL-expressions for methods fired, when event occurs. If you want to add listener
attribute to component, simple add of attribute to XML will not work, you need to
define listener element also. Below, I show my version of such attribute:

Component's XML(I don't use facelets so I change taghandler element
to comment):
</component>
...
<property>
<name>daySelectedListener</name>
<classname>javax.el.MethodExpression</classname>
<description>MethodBinding representing an action listener method that will be
notified after day clicked </description>
</property>
</component>

<listener>
<name>daySelectedListener</name>
<listenerclass>com.sintecmedia.components.event.DaySelectedListener</listenerclass>
<componentclass>com.sintecmedia.components.component.UIWeekDaysButtons</componentclass>
<eventclass>com.sintecmedia.components.event.DaySelectedEvent</eventclass>
<!-- taghandler generate="true">
<classname>com.sintecmedia.components.taglib.DaySelectedListenerTagHandler</classname>
</taghandler-->
</listener>

Component's class( broadcast() method ):
if( event instanceof DaySelectedEvent ) {
...

//Invoke listener
MethodExpression methodExpression = getDaySelectedListener();
if( methodExpression != null ) {
FacesContext facesContext = getFacesContext();
methodExpression.invoke(
facesContext.getELContext(), new Object[] { event } );
}
}
8. Default attributes: According to CDK reference the developer can
define common HTML attributes with several predefined entities, as
'html_control_events' or 'ajax_component_attributes'. I've assumed that if I
use it, CDK does rest of the job and adds it to component's encode by himself.
It was mistake. If you want that one of this attributes to be rendered in
browser you shoul add to component's template something like this:
onclick="#{component.attributes['onclick']}"

After I've passed these issues, three components that I've
developed started to work. Two last issues forced me to spend the most
time on it. The things are simple, but not documented, so it requires to
study large amounts of richFaces source code, trying to guess what piece
is related to your issue and asking many questions in forum. I assume
that this article describes common isuues and will be useful to people
that begin to use richFaces CDK. I'm not a CDK guru, so I will be glad
to see any suggestings for better solutions of these issues.

The links you need to develop components:
CDK docs:http://docs.jboss.org/richfaces/latest_3_3_X/en/cdkguide/html_single/
richFaces forum:http://community.jboss.org/en/richfaces?view=discussions&start=0
source code:http://anonsvn.jboss.org/repos/richfaces/branches/community/3.3.X/
Book or reference to JSF.

1 comment:

  1. Thanks for your patience and efforts on this write-up.. It will help the other to avoid the same problems and will help us to re-check usability points again during movement with 4.x ;)

    ReplyDelete