dev@javaserverfaces.java.net

REVIEW REQUESTED: Proposed change for Spec Issue 514

From: Jason Lee <jason_at_steeplesoft.com>
Date: Wed, 17 Dec 2008 16:57:20 -0600

Here is the change bundle for spec issue 514, the addition of the
f:event tag for declarative event handling for page authors. The
changed and added files are attached. Below is the updated spec prose.

To make the new JSF 2 event model available to page authors, this
proposal will detail the addition of a new tag, f:event, which will
allow the page author to associate a method on a managed bean with a
specific event on a specific component.

Sections Added
==============

3.4.3.2 Named Events
    To aid in the registration for Listeners for a custom event,
developers may annotate the class using the optional @NamedEvent
annotation. For example,

    @NamedEvent(name="userLogin")
    public class UserLoginEvent extends ComponentSystemEvent {
        ...
    }

    For each class with this annotation, the following logic will be
applied:

        1. Get the unqualified class name (e.g., UserLoginEvent)
        2. Strip off the trailing "Event", if present (e.g., UserLogin)
        3. Convert the first character to lower-case (e.g., userLogin)
        4. Prepend the package name to the lower-cased name
        5. If the shortName attribute is specified, register the event by
that name as well.

    In the event that two events specify the same name, a
FacesException will be thrown on the first use of the event name. The
Exception message should list, at a minimum, the event name and the
offending classes.

    The following specification classes will be registered
automatically by the run-time using the logic above:
BeforeRenderEvent, AfterAddToParentEvent, and AfterAddToViewEvent,
using both the fully-qualifed name, and a short name, comprised of the
event's simple name only.

3.4.3.6 Declarative Listener Registration
    Page authors can subscribe to events using the <f:event/> tag.
This tag will allow the application developer to specify the method to
be called when the specifed event fires for the component of which the
tag is a child. The tag usage is as follows:

    <h:inputText value="#{myBean.text}">
        <f:event type="beforeRender"
target="#{myBean.beforeTextRender}" />
    </h:inputText>

    The type specifies the type of event, and can be any of the
specification-defined events or one of any user-defined events, but
must be a ComponentSystemEvent, using either the short-hand name (see
section 3.4.3.2) for the event or the fully-qualified class name
(e.g., com.foo.app.event.CustomEvent). If the event can not be found,
a FacesException listing the offending event type will be thrown.

    The method signature for the handling method must match the
following:

    public void methodName (ComponentSystemEvent event);


Sections Modified
=================

    Listener Classes (3.4.3.2) moved to 3.4.3.3
    Listener Registrations (3.4.3.3) moved to 3.4.3.4
    Listenter Registration by Annotation (3.4.3.4) moved to 3.4.3.5
    Event Broadcasting (3.4.3.5) moved to 3.4.3.7

Classes Added
=============

javax.faces.event.NamedEvent:
/**
  * <p class="changed_added_2_0">The presence of this annotation on a
  * class automatically registers the class with the runtime as a {_at_link
  * ComponentSystemEvent}. The value of the {_at_link #shortName}
attribute is taken to
  * be the shor name for the {_at_link
javax.faces.event.ComponentSystemEvent}.
  * The implementation must guarantee that for
  * each class annotated with <code>NamedEvent</code>, found with the
  * scanning algorithm at "<em><a target="_"
  * href="../application/
FacesAnnotationHandler
.html
#configAnnotationScanningSpecification
">configAnnotationScanningSpecification</a></em>",
  * the {_at_link javax.faces.event.ComponentSystemEvent} must registered
with the runtime.
  * If the shortName has already been registered, the current class
must be added to a
  * List of of duplicate Events for that name. If the event name is
then reference by an
  * application, an Exception must thrown listing the shortName and
the offending classes.</p>
  */

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface NamedEvent {

     /**
      * <p class="changed_added_2_0">The value of this annotation
      * attribute is taken to be the short name for the {_at_link
      * javax.faces.event.ComponentSystemEvent}</p>
      */
     String shortName() default "";
}

com.sun.faces.facelets.tag.jsf.core.EventHandler:
    /**
     * This is the TagHandler for the f:event tag.
     *
     * @author jasonlee
     */
    public class EventHandler extends TagHandler {
        protected final TagAttribute type;
        protected final TagAttribute target;

        public EventHandler(TagConfig config) {
            super(config);
            this.type = this.getRequiredAttribute("type");
            this.target = this.getRequiredAttribute("target");
        }

        public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException {
            Class<? extends SystemEvent> eventClass =
getEventClass(ctx);
            if (eventClass != null) {
                parent.subscribeToEvent(eventClass,
                        new
DeclarativeSystemEventListener(ctx.getFacesContext().getELContext(),
                        target.getMethodExpression(ctx, Object.class,
new Class[] { ComponentSystemEvent.class })));
            }
        }

        protected Class<? extends SystemEvent>
getEventClass(FaceletContext ctx) {
            Class<? extends SystemEvent> clazz = null;
            String eventType = (String)
this.type.getValueExpression(ctx, String.class).getValue(ctx);
            if (eventType == null) {
                throw new FacesException("Attribute 'type' can not be
null");
            }
            ApplicationAssociate associate =
                       
ApplicationAssociate
.getInstance(ctx.getFacesContext().getExternalContext());
            NamedEventManager nem = associate.getNamedEventManager();

            clazz = nem.getNamedEvent(eventType);
            if (clazz == null) {
                try {
                    clazz = Util.loadClass(eventType, this);
                } catch (ClassNotFoundException ex) {
                    throw new FacesException ("An unknown event type
was specified: " + eventType);
                }
            }

            return clazz;
        }

    }


    class DeclarativeSystemEventListener implements
ComponentSystemEventListener {

        private ELContext elContext;
        private MethodExpression target;

        public DeclarativeSystemEventListener(ELContext elContext,
MethodExpression target) {
            this.elContext = elContext;
            this.target = target;
        }

        public void processEvent(ComponentSystemEvent event) throws
AbortProcessingException {
            target.invoke(elContext, new Object[]{event});
        }
    }





Jason Lee, SCJP
Senior Java Developer, Sun Microsystems
Mojarra and Mojarra Scales Dev Team
https://mojarra.dev.java.net
https://scales.dev.java.net
http://blogs.steeplesoft.com