[jsr-314-open] Behavior API overview
Martin Marinschek
mmarinschek at APACHE.ORG
Tue Mar 10 15:02:16 EDT 2009
Hi Andy,
I think this all reads very well - however, I was wondering why you
stopped at providing decode and renderScript (especially the
get-script method feels a little awkward in something which sounds as
generic as a behaviour class).
For me, this whole behavior thing sounds like a component aspect:
something which is called before/after/around component lifecycle
methods are executed... at least before/after/around decode and
before/after/around rendering.
regards,
Martin
On 3/10/09, Andy Schwartz <andy.schwartz at oracle.com> wrote:
> Gang -
>
> I wanted to send one last email on the new Behavior API to help folks
> who were not able to keep up with the progress on this API as it
> evolved. Much of this information has been covered in previous emails,
> but some of the API details are new, so please read on if you are
> interested.
>
> * Core APIs
>
> Just to remind everyone why we started down this path... As of the
> public review draft, we had a single non-generic "behavior" - the
> AjaxBeahvior - which could only be attached to EditableValueHolders and
> ActionSource components, and even then, only for valueChange/action
> events. So, two obvious limitations that we decided to address:
>
> 1. We wanted to allow other types of "behaviors" to be provided - not
> limit the solution to AjaxBehavior.
> 2. We wanted to allow components other than
> EditableValueHolders/ActionSources to host "behaviors".
>
> In order to address #1, we introduced the generic
> javax.faces.component.behavior.Behavior class. The class provides the
> base contract for all behavior implementations. Note that early on we
> decided that a "behavior" was something distinct from a "component".
> Behaviors, like converters/validators, are objects that are attached to
> existing components in order to enhance the component with functionality
> that is not provided by the component itself.
>
> The main responsibility of a Behavior implementation is to cough up a
> script - ie. a bit of JavaScript code that can be attached to one of the
> component's client-side event handlers (eg. attach to "onclick"). This
> responsibility is implemented via the Behavior.getScript() method.
>
> In order to address #2, we introduced the
> javax.faces.component.behavior.BehaviorHolder interface. Components
> that want to allow behaviors to be attached implement this interface.
> This allows the component to communicate the valid attach points
> (getEventNames()) and the default attach point (getDefaultEventName()),
> provides a mechanism for registering behaviors (addBehavior()), and also
> provides access to any registered behaviors (getBehaviors()).
>
> While UIComponentBase does not implement the BehaviorHolder interface
> itself, it provides default implementations of the BehaviorHolder
> methods in order to make it easier for subclasses to implement
> BehaviorHolder. The minimal requirement for a UIComponentBase subclass
> to implement BehaviorHolder is to provide an implementation of
> getEventNames() that returns a non-empty set of the event names
> supported by the component.
>
> These two contracts, Behavior and BehaviorHolder, form the foundation of
> our new component behavior solution. Hopefully the above should be
> familiar since it has been discussed the number of times on the EG list
> and was also covered in Appendix C of the recent spec drafts. The
> actual set of APIs that we need has grown over the last few months, so I
> wanted to also discuss some of the more recent additions...
>
> * BehaviorRenderer
>
> Once we got the core Behavior/BehaviorHolder contract in place, we
> (Roger, Ted, Alex and I) quickly realized that additional flexibility
> was needed. In particular, we felt that the scripts that are produced
> by Behaivor implementations may need to vary based on the RenderKit. In
> order to support RenderKit-specific Behavior script generation, we
> followed the existing UIComonent/Renderer pattern and introduced a
> BehaviorRenderer API. BehaviorRenderers are similar in spirit to
> component Renderers, but provide a contract that is geared towards
> Behaviors. So, while component Renderers are designed for HTML content
> generation and write directly to the ResponseWriter, BehaviorRenderers
> simply cough up a script - ie. the main API on BehaviorRenderer is
> getScript() (mirrors Behavior.getScript()).
>
> Of course, as with component Renderers, BehaviorRenderers are entirely
> optional. A Behavior implementation does not need to use a
> BehaviorRenderer at all - it can simply override Behavior.getScript()
> and produce the script locally.
>
> * Behavior Event Handling
>
> The next enhancement that we tackled was the ability for Behaviors to
> participate in decoding. This was necessary in order to support a very
> important use case: the ability for Behavior implementations to deliver
> events to registered listeners. The main case that we had in mind was
> AjaxBehavior, ie. we wanted to make it possible to do this:
>
> <h:commandButton>
> <f:ajax event="mouseover"
> listener="#{bean.doSomethingInResponseToMouseOver}"/>
> </h:commandButton>
>
> In order to support this we added the following APIs:
>
> - Behavior.decode()
> - BehaviorRenderer.decode()
> - Behavior.broadcast()
> - BehaviorEvent (extends FacesEvent)
> - BehaviorListener (extends FacesListener)
>
> We also specified the mechanism/conditions under which BehaviorHolder
> components must give attached Behaviors the opportunity to decode.
>
> * BehaviorBase
>
> Around this time we also introduced a BehaviorBase base class that
> provides base functionality for Behavior implementations (such as
> listener registration, event broadcast support, BehaviorRenderer
> delegation wiring). BehaviorBase is to Behavior what UIComponentBase is
> to UIComponent.
>
> * Behavior.Parameter
>
> As we started integrating the new behavior contract with our existing
> renderers, we realized that Behavior/BehaviorRenderer getScript()
> implementations may need access to various pieces of information to help
> them generate the correct scripts. For example, in the h:commandButton
> case, the component may have f:params attached. These f:param
> names/values must be included in the request, whether the request is a
> full page postback provided by the component, or an Ajax postback
> provided by the AjaxBehavior. In order to support this case, we added a
> trivial static inner Parameter class to Behavior. This allows
> components/renderers to pass (resolved) parameter name/values into
> Behavior.getScript().
>
> * BehaviorContext
>
> At this point we realized that there is danger in adding more arguments
> to the Behavior.BehaviorRenderer.getScript() methods. The problem is
> that we may not be able to foresee all of the possible information that
> getScript() implementations may need access to. Rather than risk having
> to later define new overrides of getScript(), we decided to introduce a
> BehaviorContext class. The BehaviorContext is used to pass information
> (FacesContext, component, parameters, source id) into
> Behavior.getScript(). If we find that we need to pass additional
> information into getScript() in a later release, we can do so by adding
> new methods to the BehaviorContext.
>
> BehaviorContext is defined as an abstract class, but provides access to
> a implementation via the BehaviorContext.createBehaviorContext() factory
> method. Note that we explicitly avoid going through the FactoryFinder
> mechanism here since this is a trivial data transfer object - and one
> which may need to be instantiated many times during a typical request -
> so we want to keep the overhead low.
>
> I keep going back and forth as to whether Behavior.decode() should also
> take a BehaviorContext. Currently it just takes a FacesContext and the
> UIComponent. I am tempted to pass in the BehaviorContext instead,
> though certain information on the BehaviorContext (like the parameters)
> are clearly specific to script generation.
>
> * BehaviorHint
>
> Another issue that arose while integrating Behavior support into
> h:commandButton is that the component renderer may itself need to vary
> its content depending on whether or not it has a Behavior attached, and
> in particular depending on whether that Behavior provides a script that
> performs a postback. The issue here is that certain renderers - eg.
> commandButton/Link renderers - may generate their own postback scripts
> (in Mojarra these renderers generate calls to mojarra.jsfcljs()).
> However, when an AjaxBehavior is attached, it provides its own postback
> script that should replace/suppress the postback script that the
> component renderer normally produces. Since not all Behavior
> implementations actually perform postbacks (AjaxBehavior does, but most
> won't), the component renderer needs some way to determine whether or
> not an attached Behavior is going to perform a postback.
>
> In order to solve this problem, we added a new enum, BehaviorHint, and a
> new method to Behavior: Behavior.getHints(). getHints() returns a
> Set<BehaviorHint> (implemented under the covers as an EnumSet of course)
> that provides information about the Behavior that the consuming
> component/renderer may be interested in.
>
> At the moment, the BehaviorHint enum only defines a single value -
> BehaviorHint.SUBMITTING. However, we chose to use an enum rather than a
> boolean (ie. rather than a Behavior.isSubmitting() method) to allow for
> additional hints in the future. If people feel that it is too awkward
> to have a single-valued enum in our API, please let me know. We can
> consider reverting to a boolean method, though an enum is more future proof.
>
> * Behavior configuration
>
> In order to provide complete support for configuring Behavior and
> BehaviorRenderer implementations, we had to touch a number of places,
> including:
>
> - javax.faces.application.Application now supports methods for adding
> and creating Behavior instances
> - faces-config.xml now supports <behavior> and <behavior-renderer> elements
> - We have added annotations for identifying Behavior and
> BehaviorRenderer implementations so that faces-config entries are not
> required
> - The Facelets taglib file now supports <behavior> tags.
>
> In all cases we followed the precedents already set by converter/validator.
>
> * Standard HTML component BehaviorHolders
>
> Finally, once we had agreement our generic Behavior solution, we also
> decided that it was silly not to enhance the standard HTML component set
> to take advantage of this. Most of the standard HTML components now
> implement BehaviorHolder and thus allow Behaviors to be attached. The
> attach points for the standard HTML components correspond to the
> standard "on<Event>" attributes. So, whereas before it was only
> possible to attach behaviors to EditableValueHolder/ActionSource
> components, it is now possible to attach Behaviors to other standard
> components, eg. <h:panelGroup>:
>
> <h:panelGroup>
> <f:ajax event="mouseover" listener="#{bean.foo}"/>
> </h:panelGroup>
>
> This gives us significantly more flexibility - and a much more powerful
> solution than we had outlined in our original plans.
>
> One quick note on event names... The event names/attach points do not
> include the "on" prefix that is used in the component attribute names. So:
>
> <f:ajax event="mouseover"/>
>
> Not:
>
> <f:ajax event="onMouseover"/>
>
> The standard ActionSource components still support a logical "action"
> event/attach point. And the standard EditableValueHolder components
> still support a logical "valueChange" event/attach point. This allows
> page authors to attach Behaviors to these components without having to
> be aware of the unerlying DOM event that triggers the action/value
> change. Also note that the "action" and "valueChange" events are
> defined as default events for these components, which makes it possible
> to do omit the event attribute, eg:
>
> <!-- Fires Ajax requests in response to actions -->
> <h:commandButton>
> <f:ajax/>
> </h:commandButton>
>
> <!-- Fires Ajax requests in response to value changes -->
> <h:inputText>
> <f:ajax/>
> </h:inputText>
>
> BTW, Jeffrey Stephenson (JSR-276) and I have been having some offline
> discussions on the possibility of getting event name/default event name
> metadata in place. This would clearly help the development tool experience.
>
> * Limitations
>
> While I think that the Behavior API is a great addition to JSF, there
> are a few limitations that we have not been able to address for 2.0.
> These include:
>
> 1. Custom renderer wiring
>
> Implementing the BehaviorHolder contract is trivial. However, actually
> rendering the Behavior scripts - possibly multiple scripts which may
> need to be chained with other component/renderer-defined scripts - can
> be tedious. To help simplify this, we have introduced a new utility
> method into our JavaScript API: jsf.util.chain(). This method takes an
> arbitrary number of script arguments and executes each one until it
> finds one that returns false, at which point it short-circuits. While
> this simplifies things somewhat, it is still nontrivial for a Renderer
> author to produce the proper call to jsf.util.chain().
>
> If we had more time, I would recommend trying to get some utility
> methods into the JSF API so that we can make it easier for custom
> Renderer authors to produce the properly chained scripts. However,
> given how little have time we have left, I do not think that we have
> time to get this right. My current plan is to try to provide some open
> source utilities to help Renderer authors solve this problem. Once
> we've worked out the right APIs in open source, I am hoping that we can
> incorporate these back into the 2.1 spec.
>
> 2. Behavior attribute-property transparency
>
> I have been thinking that, like components, Behaviors should expose an
> attribute map that provides attribute-property transparency. That way,
> BehaviorRenderers do not have to worry about the actual Behavior type -
> they can simply grab attributes out of the attribute map.
>
> I briefly considered implementing this functionality, but after a quick
> look at UIComponentBase.AttributeMap I got scared away. I think the
> right solution is to:
>
> - Break UIComponentBase.AttributeMap out into its own top-level public
> class.
> - Introduce an AttributeHolder contract
> - Have UIComponentBase and BehaviorBase implement AttributeHolder and
> use AttributeMap to hold their attributes.
>
> However, for obvious reasons I decided not to push this for 2.0.
> Perhaps we can think about this for 2.1.
>
> Since we do not have a Behavior.getAttributes() API, at the moment,
> BehaviorRenderer implementations are forced to cast to the actual
> Behaivor type (eg. AjaxBehaviorRenderer casts the Behavior to
> AjaxBehavior). I am not happy about this, but willing to live with it
> for now.
>
> 3. Behavior resources
>
> Behavior implementations clearly are going to want to pull in JavaScript
> libraries so that these do not need to be pulled in explicitly by the
> application developer. However, at the moment we do not have a
> solution/recommendation for this. (Well, there might be some solution -
> just haven't identified what it is yet.)
>
> This is another one that will need to wait for 2.1.
>
> I think that's it! If you have any questions/comments on this new API,
> please follow up here.
>
> I am hoping to do some blogging on all of this if I can ever find the
> time. :-)
>
> Andy
>
>
--
http://www.irian.at
Your JSF powerhouse -
JSF Consulting, Development and
Courses in English and German
Professional Support for Apache MyFaces
More information about the jsr-314-open-mirror
mailing list