jsr344-experts@javaserverfaces-spec-public.java.net

[jsr344-experts] [1111-PassThruElements] Proposal second version (was: First commit complete)

From: <edward.burns_at_oracle.com>
Date: Fri, 14 Sep 2012 14:36:52 +0000 (GMT)

PROPOSAL: Use TagDecorator approach to provide passthrough elements,
falling back on a special Renderer if necessary.

SECTION: Proposal History

In the last EG conference call, on 20120808, Frank Caputo proposed a
different approach to issue 1111 then Ed had been persuing. The group
agreed the approach had merit, and Frank graciously agreed to prototype
it. In the following days and weeks Frank's prototype took shape, with
Ed giving occasional look-ins. Since 20120907, Ed has been working
toward integrating the prototype into the trunk.

SECTION: The Goal of the Feature

The goal of this feature is to enable page authors to write almost
entirely plain HTML (including HTML5) and let the runtime identify such
markup as traditional JSF components. We want to minimize the "almost"
in the previous sentence. A corollary of this minimization is that the
range of HTML supported by the feature is maximized. The current
proposal has the "almost" shrunk down to two requirements.

1. Declare the <http://java.sun.com/jsf> namespace in the Facelet view.


2. For each HTML markup that the page author wants to be considered as
a
JSF component, put at least one attribute in that namespace. Usually
"id" is the chosen attribute.

SECTION: Summary of Frank's TagDecorator approach

Frank used the existing JSF 2.0 Facelets TagDecorator API to establish
a
mapping between the existing component/renderer pairings in the
<http://java.sun.com/jsf/html> taglibrary and plain HTML markup. To
this idea, Ed added the existing idea of a "Passthrough Renderer" to
handle those cases where such a mapping does not exist or does not make
sense.

SECTION: Details

This prototype data structure conveys the mapping. Obviously, we'll
need to decide how best to specify the mapping. Some choices include:

* A text table in the spec prose document PDF

* Some kind of XML syntax in the standard-html-basic.xml renderkit

    private static enum Mapper {
        // TODO can we handle h:commandLink and h:outputLink?
        img("h:graphicImage"), body("h:body"), head("h:head"),
label("h:outputLabel"), script("h:outputScript"),
        link("h:outputStylesheet"),

        form("h:form"), textarea("h:inputTextarea"),
        // TODO if we want the name of the button to become the id, we
have to do .id("name")
        button("h:commandButton"),

        select(new ElementConverter("h:selectOneListbox", "multiple")
                // TODO this is a little bit ugly to handle the name as
if it were jsf:id. we should not support this
                .id("name")
                .map("multiple", "selectManyListbox")),

        input(new ElementConverter("h:inputText", "type")
                // TODO this is a little bit ugly to handle the name as
if it were jsf:id. we should not support this
                .id("name")
                .map("hidden", "inputHidden")
                .map("password", "inputSecret")
                .map("number", "inputText")
                .map("search", "inputText")
                .map("email", "inputText")
                .map("datetime", "inputText")
                .map("date", "inputText")
                .map("month", "inputText")
                .map("week", "inputText")
                .map("time", "inputText")
                .map("datetime-local", "inputText")
                .map("range", "inputText")
                .map("color", "inputText")
                .map("url", "inputText")
                .map("checkbox", "selectBooleanCheckbox")
                .map("file", "inputFile")
                .map("submit", "commandButton")
                .map("reset", "commandButton")
                .map("button", "button"));

        private ElementConverter elementConverter;

        private Mapper(ElementConverter elementConverter) {
            this.elementConverter = elementConverter;
        }

        private Mapper(String faceletTag) {
            elementConverter = new ElementConverter(faceletTag);
        }
    }

The existing TagDecorator feature enables us to poke into the page
compilation process and inspect the attributes on every tag in the
Facelet page with minimal speed performance impact. If there are no
attributes in the <http://java.sun.com/jsf> namespace, the custom
TagDecorator takes no action. Otherwise, the following actions are
taken.

* All attributes declared in the <http://java.sun.com/jsf> namespace
get
  set as proper UIComponent attributes, subject to any existing rules
  about attribute/property transparency.

* All attributes without a namespace are set as passthrough attributes,
  in the existing <http://java.sun.com/jsf/passthrough> namespace, in
  Passthrough Attribute map on UIComponent.

* The localName of the element is saved as a passthrough attribute
under
  a special name given by the value of the symbolic constant
  Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY.

Then, the mapping table is consulted. If the incoming markup matches
an
entry in the data structure, the internal representation of the tag is
converted to be as if the page author had typed the tag from the
<http://java.sun.com/jsf/html> taglibrary (with the above
modifications). Otherwise, the internal representation of the tag is
converted to be as if the page author had typed the jsf:element tag
from
the <http://java.sun.com/jsf> taglibrary (with the above
modifications).

The <jsf:element> tag causes a UIInput with a
javax.faces.passthrough.Input Renderer to be placed in the tree. Here
is the spec for this renderer.

Decode Behavior

  Define a data structure that represents the following table.

  localName submittedValueAttributeName renderCurrentValue
  ========= =========================== ==================

  keygen name false

  <other elements to follow>

 
----------------------------------------------------------------------

  Look in the pass through attribute map for a key given by the value
of
  the symbolic constant Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY.
If
  not found, throw a FacesException. Use the localName as the key into
  the preceding table. Find the submittedValueAttributeName entry for
  that key. Look in the passthrough attribute map for a key given by
  the value of the submittedValueAttributeName entry. If found, use
  that value to look in the requestMap. If the requestMap has a value,
  set it as the submittedValue of the UIInput. Aside from the
  previously mentioned FacesException, if none of the data structure,
  passthrough attribute map or requestMap lookups yields a value, take
  no action.

Encode Behavior

  Look in the component's attribute map for an entry under the key
given
  by the value of the constant
  Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY. The value of this key is
  the element name to render. If the component has a manually declared,
  not auto-generated clientId, or if the component has behaviors
  attached to it, render the clientId as the value of the "id"
  attribute. Render the "name" attribute with the value coming from the
  clientId. Note that markup authors may set this value directly on the
  markup and the VDL processing must guarantee that the "name"
attribute
  is set as the component's clientId. If the data structure has an
entry
  for the localName, and the value of the renderCurrentValue is true,
or
  the data structure has no entry for the localName, render the current
  value of the component as the value of the "value" attribute.
  Otherwise do not render the value of the component.

SECTION: Open issues:

- Our decision to standardize TagDecorator, but not provide default
  implementations for its helper classes has led to this feature being
  underutilized.

  It may be the case that we need to expose these helper classes as
part
  of the work that is being done in 1111-passThroughElement.

- How do we deal with the case when people try to use the feature
  outside the box in which they are allowed to use it? For example
  using <form method="GET">?

  This is actualy a benefit. Consider this text based on
  <http://html5doctor.com/the-output-element/>.

  <form jsf:id="form"
oninput="o.value=parseInt(a.value)+parseInt(b.value)">

    <input jsf:id="a" name="a" type="number" step="any"> +
    <input jsf:id="b" name="b" type="number" step="any"> =
    <output jsf:id="o" name="o"></output>

  </form>

  The new system will handle this cleanly, and do the "right thing"
with
  respect to the form as well. That's a win!

- Revisit the contract between DefaultTagDecorator and
  HtmlResponseWriter.

  Frank's code gets the elementName and stores it as a TagAttribute
  instance in the tag's TagAttributes impl in the passThrough attribute
  namespace. The HtmlResponseWriter knows to look in the passThrough
  attribute set for a specially named attribute called "elementName".

  My code has a component attribute
  Renderer.MARKUP_RENDERER_LOCALNAME_KEY that gets passed in from the
  PassThroughElementHandler.

ACTION: Please respond by 09:00 EDT Tuesday 18 September 2012