[jsr-314-open] Behavior API overview

Andy Schwartz andy.schwartz at ORACLE.COM
Thu Mar 12 08:07:29 EDT 2009


Gang -

Last night I re-factored our code as described in my previous email, ie:

- Behavior class -> ClientBehavior interface
- BehaviorHolder->ClientBehaviorHolder
- BehaviorContext->ClientBehaviorContext
- BehaviorRenderer->ClientBehaviorRenderer
- BehaviorBase->ClientBehaviorBase
- BehaviorHint->ClientBehaviorHint

I also introduced a new javax.component.behavior.Behavior interface,
which is empty.  While an empty interface is a bit unusual, it serves
three purposes:

1. It allows us to return something generic from
Application.createBehavior() (ie. return Behavior instead of
ClientBehavior).
2. It provides us with a generic super-type for the new ClientBehavior
interface.
3. It provides us with a a generic super-type for future Behavior contracts.

Regarding #3, in order to address Martin's use case, we would add a new
"PhaseBehavior" interface (extends Behavior) and a possibly a
PhaseBehaviorHolder interface in 2.1.  This would allow us to define a
new aspect/phase-listening type of Behavior in 2.1.

Martin -

While the code re-factoring was fairly straightforward (though touched a
number of places), if we are going get this in we still have a log of
spec edits to make.  So before we move forward, I would very much like
to hear whether you are happy with the approach that I have
proposed/implemented.  Given the schedule dates that Ed sent out
yesterday, we don't have much time to get this in, so please respond
soon if at all possible.

Kito -

When you said:

> Andy, your proposal seems like the simplest way to handle this for now.

I assume that means that you support this proposal (ie. renaming
Behavior->ClientBehavior, introduce generic Behavior interface).  Can
you confirm?

> However, if we assume that a behavior has a single purpose, couldn't
> we just rename "getScript" to "getContent" or something?

Hrm... I am on the fence on this one... While getScript() is not the
greatest name, it does seem relatively descriptive/informative.  Not
sure that moving to getContent() is a big improvement.  And to be
honest, since I am already pushing my luck with getting last minute
changes in, I think I would prefer to stick with our current method name
- one less spec edit to worry about. :-)

Andy

Andy Schwartz wrote On 3/11/2009 4:27 PM ET:
> 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/20090312/fc48b291/attachment.html 


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