[jsr-314-open] Behavior API overview

Andy Schwartz andy.schwartz at ORACLE.COM
Wed Mar 11 16:27:23 EDT 2009


Hey Martin,

Okay, I see where you are coming from.  So one option (leaving schedule
issues aside for a moment) would be to go back to our old names:

- Behavior->ClientBehavior
- BehaviorHolder->ClientBehaviorHolder

And I guess also for the new APIs:

- BehaviorContext->ClientBehaviorContext
- BehaviorRenderer->ClientBehaviorRenderer
- BehaviorBase->ClientBehaviorBase
- BehaviorHint->ClientBehaviorHint (or give up on the enum and just use
a boolean).

And, as you suggest, we would need to define a new base Behavior
contract that ClientBehavior extends.  Or, at least, I think we need
this, since we would want to keep the behavior-related configuration
stuff generic.  That is, I assume that we would still want this method
in Application:

    public abstract Behavior createBehavior(String behaviorId)

As opposed to this:

    public abstract ClientBehavior createClientBehavior(String behaviorId)

(And similarly, still want <behavior> elements in faces-config.xml and
Facelets taglib.)

While I think we would need to introduce a new Behavior contract (so
that we have something generic to return from
Application.createBehavior()), I am not sure that  we need a base
BehaviorHolder contract.  Just doesn't seem like this would be useful.

If we do this, then in 2.1 we could introduce a PhaseBehavior interface
that extends Behavior.  This would allow us to implement
phase-listening/apsect-like behaviors in 2.1 without requiring that they
implement the getScript() method.  We may also add a PhaseBehaviorHolder
contract for components that allow PhaseBehaviors to be attached
independently of any event attach point.  (The ClientBehaviorHolder
contract is all about attaching to "events" - whereas a PhaseBehavior
may not care at all about attaching to a particular event.)

Note that if we decide to add support for behaviors that do
component-level/aspect-like phase listening in 2.1, I would prefer that
we do this by introducing a new sub-interface of the generic Behavior
interface, rather than add phase listening capabilities directly into
the base, since phase listening capabilities may not be generic to all
Behaviors (just like script generation may not be generic to all
Behaviors - for example, AjaxBehavior doesn't want to be a PhaseBehavior)

So, I think this could work, but two problems:

1. I am not sure what the base Behavior contract should be.

Would it be okay for this to just be a marker interface?  This would
certainly be easier than trying to design a more meaningful/generic base
Behavior contract now.

2. Time

The re-factoring shouldn't be all that hard, but it will require some
work, both on the implementation and in the specification, and as we all
know, we are definitely running out of time.

Andy

Martin Marinschek wrote On 3/11/2009 11:06 AM ET:
> 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
>>>>
>>>>
>>>>
>>>>
>>>
>>>
>>
>
>
>

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


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