[jsr-314-open] Behavior API overview

Martin Marinschek mmarinschek at APACHE.ORG
Wed Mar 11 11:06:38 EDT 2009


Hi Andy,

ok, I see. Well, I just wanted to make sure that we don't pollute the
namespace: again, behavior seems to generic a name to me to have a
method providing a script. Should we have a behavior interface, an
extending script-behavior interface, and then we can make behavior
(the base-interface) more powerful in 2.1?


regards,

Martin

On 3/10/09, Andy Schwartz <andy.schwartz at oracle.com> wrote:
> 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
>>>
>>>
>>>
>>
>>
>>
>
>


--

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