Introduction
While JSF2.0 has simlified the component building a lot thanks to the
composite components, there are still usecases when the classical component
building tasks have to be performed.
We showed in part 1 and part 2 how to build the basic JSF artifacts in Scala we now dive deeper into
JSF by showing how to leverage Scala to build your own jsf custom component.
With this article we combine various techniques of Scala for programming custom components.
The case we are going to investigate is a simple hello world component.
A custom component in Scala
First we have a look at the component itself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | import javax.faces.component.{UINamingContainer, FacesComponent} import java.util.logging.Logger import javax.faces.event._ /** * * @author Werner Punz (latest modification by $Author$) * @version $Revision$ $Date$ */ object HelloWorld { /*attributes*/ val ID = "id" val SAY_HELLO = "sayHello" /*statics*/ val logger = Logger.getLogger("HelloWorld") } /** * helper to avoid a problem with the image button being nested in another component * takes a calendar as value */ @FacesComponent("at.irian.HelloWorld") @serializable @ListenersFor(Array( new ListenerFor(systemEventClass = classOf[PostRestoreStateEvent]), new ListenerFor(systemEventClass = classOf[PostAddToViewEvent]) )) class HelloWorld extends UINamingContainer with StandardComponent { /*lets import the attributes due to not having java structs*/ import HelloWorld._ def setSayHello2(hello: String) { setAttr[String](SAY_HELLO, hello) } def getSayHello2(): String = getAttr[String](SAY_HELLO, "") + " added text" //we now handle the special events override def processEvent(event: ComponentSystemEvent) { event match { case evt: PostAddToViewEvent => logger.info("PostAddToViewEvent called"+getAttr[String](SAY_HELLO,"")) case evt: PostRestoreStateEvent => logger.info("PostRestoreStateEvent called") case _ => {} } super.processEvent(event) } } |
We have a simple component which exposes one additional attribute sayHello2. The component itself is a composite component with an xml template for rendering an performs some listener tasks.
So far so good, however we use several things here which are of interest:
1 2 3 4 5 6 7 8 | object HelloWorld { /*attributes*/ val ID = "id" val SAY_HELLO = "sayHello" /*statics*/ val logger = Logger.getLogger("HelloWorld") } |
- A singleton object for static replacements and struct replacements
1 | @ListenersFor(Array(... |
- A listeners array for combining multiple listeners
1 | with StandardComponent |
- a trait instead of a utils class to combine common functionality of components
1 2 3 4 5 | event match { case evt: PostAddToViewEvent => logger.info("PostAddToViewEvent called"+getAttr[String](SAY_HELLO,"")) case evt: PostRestoreStateEvent => logger.info("PostRestoreStateEvent called") case _ => {} } |
- matches for types to avoid instanceof cascades
1) Object HelloWorld
Scala does not have neither static variables nor structs, instead of that it provides singletons as language construct.
Static variables simply can be simulated by a singleton and a simple in class import:
1 2 3 4 5 6 7 8 | object HelloWorld { /*attributes*/ val ID = "id" val SAY_HELLO = "sayHello" /*statics*/ val logger = Logger.getLogger("HelloWorld") } |
and then
1 | import HelloWorld._ |
Allows you to import the instance variables and methods as semi native members.
1 | logger.info("PostAddToViewEvent called"+getAttr[String](SAY_HELLO,"")) |
2) Annotation Arrays
1 2 3 4 | @ListenersFor(Array( new ListenerFor(systemEventClass = classOf[PostRestoreStateEvent]), new ListenerFor(systemEventClass = classOf[PostAddToViewEvent]) )) |
is simply what would be in Java
1 2 3 4 | @ListenersFor({ @ListenerFor(systemEventClass = classOf[PostRestoreStateEvent]), @ListenerFor(systemEventClass = classOf[PostAddToViewEvent]) }) |
Since this code transition is not quite obvious even within the Scala documentation, it is worth to be noted in this blog.
3) Traits
The most interesting part is the traits part.
First of all, what is a trait. To sum it up, a trait is an abstract class which can be multiply inherited sort of an interface
with code. Now this opens quite a few possibilities.
Common constraints can be isolated and shared among object instances without having to revert to singletons.
Traits can access "this" and can call methods provided by the class as abstract members.
We reuse traits in this case to isolate common component behavior without having to introduce yet another helper class or an
abstract base class. In fact we finally can share this referencing code among components with different base classes without having to introduce our own inheritance hierarchy.
The trait looks like following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | trait StandardComponent { def getViewRoot = FacesContext.getCurrentInstance.getViewRoot def getLocale: Locale = FacesContext.getCurrentInstance.getViewRoot.getLocale def getAttr[T](key: Serializable, default: T): T = { val ret = getAttributes().get(key) if (ret != null) ret.asInstanceOf[T] else default } def setAttr[T](key: String, value: T) { //note the scala compiler compiles anyref to java.lang.Object //in scala itself anyref is one level above Any which is the base //of everything scalawise getAttributes.put(key, value.asInstanceOf[AnyRef]) } def getAttributes(): java.util.Map[String, AnyRef] def reqAttrMap: java.util.Map[String, String] = { FacesContext.getCurrentInstance.getExternalContext.getRequestParameterMap } def getReqAttr(key: String): String = { reqAttrMap.get(key) } } |
We use only a subset of this functionality namely getAttr and setAttr.
1 2 3 4 | def getAttr[T](key: Serializable, default: T): T = { val ret = getAttributes().get(key) if (ret != null) ret.asInstanceOf[T] else default } |
Here we can see clearly the this reference to the underlying component getAttributes with
def getAttributes(): java.util.Map[String, AnyRef]
being defined only as interface, which has to be implemented by the component or one of its parents.4) Match patterns
Now an interesting language part in Scala is the extended matches. Not only you can match in Scala for values, also matches for types and patterns are allowed.
We use the type matches to avoid instanceof if cascades:
1 2 3 4 5 6 7 8 9 10 | //we now handle the special events override def processEvent(event: ComponentSystemEvent) { event match { case evt: PostAddToViewEvent => logger.info("PostAddToViewEvent called"+getAttr[String](SAY_HELLO,"")) case evt: PostRestoreStateEvent => logger.info("PostRestoreStateEvent called") case _ => {} } super.processEvent(event) } |
The cases basically replace if instanceof constructs here
A renderer in Scala
Now what if we want to write the renderer in Scala.
Scala there can support us as well, it has XML support in the language baked in.
Now lets have a look at the renderer (if not done in an xhtml template like it should be)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import javax.faces.render.{Renderer, FacesRenderer} import javax.faces.context.FacesContext import javax.faces.component.UIComponent @FacesRenderer(componentFamily = "javax.faces.Output", rendererType = "at.irian.HelloWorld") class HelloWorldRenderer extends Renderer { override def encodeEnd(context: FacesContext, component: UIComponent) { val responseWriter = context.getResponseWriter, val text = component.asInstanceOf[HelloWorld].getSayHello2(), val id = component.getClientId responseWriter.write( <div class="helloWorld" layout="block" id={id}> {text} </div>.toString ) } } |
Now the interesting part of this renderer is following code:
1 2 3 4 5 | responseWriter.write( <div class="helloWorld" layout="block" id={id}> {text} </div>.toString ) |
As we can see, we simply write the html directly constructs like id={id} and {text} allow for inline templating.
There are constraints to this approach
- We cannot write out partial xml. The xml written always must be complete, hence we cannot simply write an open tag first, call a subclass and then close the tag
- We do not use the startElement, endElement. The plus side is readability.
- In the end a composite component and its direct xhtml rendering should always be the first choice. Xhtml simply is the target platform in most cases why not use xhtml also for the component renderer part.
References
- Marrying JSF and Scala Part1
- Marrying JSF and Scala Part2
- Custom Components in JSF (German)
- Scala Documentation
Keine Kommentare:
Kommentar veröffentlichen