[jsr-314-open] Behavior API overview

Andy Schwartz andy.schwartz at ORACLE.COM
Tue Mar 10 17:21:36 EDT 2009


Hey Martin -

Thanks for reading through my long email and for the comments!

We ended up with the getScript() and decode() methods out of necessity -
these were the minimal APIs that we needed to encapsulate our
Ajax-related functionality into a Behavior object.  As currently
designed, our Behavior solution is definitely focused on creating and
attaching scripts to components - thus we ended up with getScript() as
the centerpiece of the Behavior contract.

I find your idea of Behaviors as component aspects very interesting, and
can see how this would add another degree of flexibility.  However, I am
a little uncomfortable expanding the scope at this point in the 2.0
release.   For 2.1, I do think it would be worth considering whether we
can address this, perhaps by adding a new "PhaseBehavior" interface.
The idea is that this would identify Behaviors that want to be notified
before/after each phase, similar to a PhaseListener, but at the
component instance-level rather than at the view-level.

Do you feel that this is something that we can evaluate for 2.1?  Or do
you feel that it is critical to address this in 2.0?

Andy

Martin Marinschek wrote On 3/10/2009 3:02 PM ET:
> 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
>>
>>
>>
>
>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/jsr-314-open-mirror/attachments/20090310/e67349b8/attachment.html 


More information about the jsr-314-open-mirror mailing list