users@javaserverfaces-spec-public.java.net

[jsr344-experts mirror] [jsr344-experts] Re: [1089-HTML5-data-*attributes] PROPOSAL

From: Andy Schwartz <andy.schwartz_at_oracle.com>
Date: Wed, 06 Jun 2012 21:19:47 -0400

On 6/6/12 2:37 PM, Phil Webb wrote:
> Thanks Andy, this is a fantastic summary that really helps clarify the issues.

Great, thanks for taking the time to read through my tl;dr mail! :-)

> I pretty much agree with all the proposals except I wonder if it might be sensible to start with proposal 1a (new tag) and before 1b (namespace attributes). There are a couple of reasons for this:
>
> - Having tags f:attribute, f:attributes and f:passThoughAttributes without f:passThoughAttribute seems a little odd.
> - Using f:passThoughAttribute will not break existing tooling and so provides a more gradual route for upgrade.
>

Yes. Good points. The lack of symmetry between f:attribute/f:attributes
and p:<attribute name>/f:passThroughAttributes was irking me as well.

> I would like to see the namespace proposal implemented, but I think it should be in addition to f:passThoughAttribute.
>

+1. Ending up with:

- f:attribute/f:attributes
- f:passThroughAttribute/f:passThroughAttributes
- p:foo="bar" as a nifty conveniencee for <f:passThroughAttribute
name="foo" value="bar"/>

Is feeling right to me.


> 6: +1 for user override. This leads to an interesting question about how passthough attributes are rendered.

Oh, yep. This is important...

> At a technical level it feels like existing renderers will need to be changed.


I would like to avoid requiring Renderer changes if at all possible.
Thinking we would try to hide all of this magic at the ResponseWriter
implementation level. Roughly:

In cases where a non-null component is provided,
ResponseWriter.startElement() grabs the pass through attribute map from
the component.

In most cases, the pass through attribute map will be empty. Nothing
interesting happens.

In cases where the pass through attribute map is non-empty, the
ResponseWriter kicks into a special pass through attribute handling mode.

In this mode, the ResposneWriter monitors all calls to
writeAttribute()/writeURIAttribute().

For each attribute that is written, the ResposneWriter first checks to
see whether an equivalent pass through value is available.

If so, the pass through attribute value is written in place of the value
that was passed into writeAttribute()/writeURIAttribute().

When we reach the close of the start element (eg. startElement(),
endElement(), writeText() is called), we blast out any remaining pass
through attributes that haven't yet been written.

Okay, I've ignored some of the complexities of doing this... it might
end up being easier to just buffer up all attributes in a temporary map
and blast everything out at the element close.

In any case, I am hoping that we'll be able to come up with something that:

a) works, and...
b) doesn't impose too much new overhead, particularly for cases where no
pass through attributes are present.

The above proposal does impose some new overhead in all cases: we're
adding one property lookup (to fetch the pass through attribute map) for
every component. Adding any new per-component overhead always makes me
nervous, though this seems relatively small. We'll want to performance
test this, along with all of our other new JSF 2.2 features, before the
spec goes final.


> Do we need a ResponseWriter.writePassThoughAttributes(UIComponent component) method. Some renderers might write more than one element so I don’t see an easy way to indicate where passthough attributes are applied unless per renderer

I was assuming (and the above implementation sketch assumes) that pass
through attributes would always be written out to component's root element.

If we need something more sophisticated than this, we'll need to
re-evaluate our strategy.

Andy

> (apologies if I have missed something in the earlier emails on this).
>
> Cheers,
> Phil.
>
>
> ----- Original Message -----
>
>> From: "Andy Schwartz"
>> To: jsr344-experts_at_javaserverfaces-spec-public.java.net
>> Sent: Wednesday, June 6, 2012 9:49:04 AM
>> Subject: [jsr344-experts] Re: [1089-HTML5-data-*attributes] PROPOSAL
>>
>> Gang -
>>
>> I wanted to take a shot at summarizing this discussion.
>>
>> Let's start with the good news: it seems that we agree on the core
>> nature of this API - ie. we all want to add support for new
>> Map<String,
>> Object> that contains bonus attributes that are passed through to the
>> rendered output.
>>
>> The bad news: we don't seem to agree on anything else. ;-)
>>
>> Open issues/questions:
>>
>> 1. How is this pass through attribute map populated?
>>
>> Let's start with the simplest case: I want to explicitly specify a
>> small
>> set of static pass through attributes.
>>
>> We discussed a few options:
>>
>> 1a) New tag
>>
>> <h:panelGroup rendered="#{el.to.rendered}>
>> <f:passThroughAttribute name="role" value="banner"/>
>> <f:passThroughAttribute name="aria-hidden"
>> value="#{el.to.hidden}"/>
>> </h:panelGroup>
>>
>> This would be similar to <f:attribute>, but populates the pass
>> through
>> attribute map instead of the component attribute map.
>>
>> 1b) Namespaced attributes
>>
>> To reduce verbosity, we could introduce a new namespace specifically
>> for
>> pass through attributes, eg:
>>
>> <h:panelGroup rendered="#{el.to.rendered} p:role="banner"
>> p:aria-hidden="#{el.to.hidden}">
>>
>> Any attributes in this namespace would end up on the pass through
>> attribute map.
>>
>> 1c) Non-namespaced/unknown attributes
>>
>> Another option would be to translate any "unknown" attributes
>> specified
>> on the tag to the pass through attribute map, eg:
>>
>> <h:panelGroup rendered="#{el.to.rendered} role="banner"
>> aria-hidden="#{el.to.hidden}">
>>
>> The concerns about this are:
>>
>> - This is a change in behavior: Facelets already specifies that such
>> attributes are added to the component attribute map. (Though we
>> could
>> make this configurable.)
>> - This blurs the lines between component vs. pass through attributes
>> in
>> a way that can be confusing for page authors - ie. there is no easy
>> way
>> to determine whether a particular attribute corresponds to HTML
>> markup
>> or a component attribute.
>> - Typos in attribute names would result in data being transferred to
>> the
>> client in a potentially non-obvious way.
>>
>> Proposal:
>>
>> Let's start with 1b).
>>
>> I understand the motivation behind 1c). We would like to move in a
>> direction whereby web designers can feel more comfortable authoring
>> JSF-based pages using familiar technologies (eg. HTML5 instead of
>> <h:*>). However, I think that the way to tackle this is by enhancing
>> our jsfc/pass through element solution. Let's see what we can do
>> that
>> before we consider mixing HTML + component attributes on our
>> component-centric tags.
>>
>> 2. What about the case where we need to dynamically specify an
>> arbitrary # of pass through attributes?
>>
>> As Paul pointed out, we need to support cases where the page author
>> doesn't necessarily know all of the pass through attributes ahead of
>> time. The set of pass through attributes may be determined
>> dynamically
>> (eg. via a managed bean/CDI or a web service).
>>
>> Two possibilities to address this:
>>
>> 2a) New tag
>>
>> <h:panelGroup rendered="#{el.to.rendered}>
>> <f:passThroughAttributes value="#{el.to.map}"/>
>> </h:panelGroup>
>>
>> The "value" attribute evaluates to a Map<String, Object>. Any
>> entries
>> in this map are added to the component's pass through attribute map.
>>
>> Imre pointed out that we've got a related problem for plain-old
>> (non-pass through) component attributes, especially when dealing with
>> composite components, eg:
>>
>> <cc:implementation>
>> <h:commandButton styleClass="#{cc.attrs.styleClass}
>> style="#{cc.attrs.style}"
>> onclick="#{cc.attrs.onclick}"
>> title="#{cc.attrs.title}"
>> value="#{cc.attrs.value}"
>> isThisAnnoying="#{cc.attrs.yes}"/>
>> </cc:implementation>
>>
>> If we're going to add a mechanism for allowing multiple pass through
>> attributes to be specified via EL, perhaps we should generalize this
>> to
>> include support for normal (non-pass through) component attributes as
>> well, eg:
>>
>> <cc:implementation>
>> <h:commandButton>
>> <f:attributes value="#{some:filter(cc.attrs)}"/>
>> </h:commandButton>
>> </cc:implementation>
>>
>> We could then either:
>>
>> - Control the target map via an attribute, eg.
>>
>> <f:attributes value="#{el.to.map}" target="passthrough |
>> component"/>
>>
>> Or...
>>
>> - Break this out into two tags:
>>
>> <f:attributes
>> value="#{el.to.map.containing.bonus.component.attrs}">
>> <f:passThroughAttributes
>> value="#{el.to.map.containing.bonus.passthrough.attrs}"/>
>>
>> 2b) Namespaced attribute
>>
>> Similar to 1b), we could do:
>>
>> <h:panelGroup rendered="#{el.to.rendered}
>> p:attributes="#{el.to.map}">
>>
>> That is, we could treat "p:attributes" as a special case of the pass
>> through namespace that is used to explicitly populate the pass
>> through
>> attribute map.
>>
>> I kind of like 1b), though it doesn't quite extend as well to Imre's
>> use
>> case.
>>
>> Proposal:
>>
>> Let's do 2a).
>>
>> I would prefer that we do this via two new tags:
>>
>> <f:attributes
>> value="#{el.to.map.containing.bonus.component.attrs}">
>> <f:passThroughAttributes
>> value="#{el.to.map.containing.bonus.passthrough.attrs}"/>
>>
>> Rather than a single multi-purpose tag:
>>
>> <f:attributes value="#{el.to.map}" target="passthrough |
>> component"/>
>>
>> Though open to discussion on this.
>>
>> 3. When are #{el.to.map} expressions evaluated?
>>
>> Assuming we add something resembling:
>>
>> <f:passThroughAttributes value="#{el.to.map}"/>
>>
>> We'll need to specify when the value EL expression is evaluated/when
>> the
>> bonus attributes are made available via the target attribute map.
>>
>> We've got two options here:
>>
>> 3a) Specify that this work is performed at tag execution (Facelet
>> handler appy) time.
>> 3b) Specify that this work is performed during Faces lifecycle
>> processing
>>
>> Option 3a) is way, way easier to implement. It has one small
>> downside:
>> this means that in stamping cases (eg. ui:repeat, h:dataTable), it
>> will
>> not be possible to EL bind to a different set of bonus attributes for
>> each iteration/row.
>>
>> Proposal:
>>
>> Let's do 3a). It's slightly more limiting, but substantially more
>> straightforward.
>>
>> As a result, these two snippets:
>>
>> <h:panelGroup>
>> <f:attribute name="styleClass" value="pretty"/>
>> </h:panelGroup>
>>
>> <h:panelGroup>
>> <f:attributes
>> value="#{el.to.map.containing.single.styleClass.entry}"/>
>> </h:panelGroup>
>>
>> Would be equivalent, as would this:
>>
>> <h:panelGroup p:role="banner">
>>
>> And:
>>
>> <h:panelGroup>
>> <f:passThroughAttributes
>> value="#{el.to.map.containing.single.role.entry}"/>
>> </h:panelGroup>
>>
>> 4. Do we need to support JSON literal/inline representations?
>>
>> The idea here would be to allow JSON literals to be specified either
>> as
>> attribute values:
>>
>> <f:passThroughAttributes value="{'role':'banner',
>> 'aria-hidden':true}"/>
>>
>> Or possibly within the tag body, eg:
>>
>> <f:passThroughAttributes>
>> {
>> 'role' : 'banner',
>> 'aria-hidden' : true
>> }
>> </f:passThroughAttributes>
>>
>> These JSON literals would include support for inline value
>> expressions.
>> We've also considered implementing a name decoration facility for
>> making
>> it more convenient to specify multiple attributes that start with the
>> same prefix.
>>
>> Proposal:
>>
>> Seems like this isn't going to be a popular proposal, but, how about:
>> no? :-)
>>
>> For cases where the page author needs to specify a few well-known
>> attributes, our namespaced-based approach seems much simpler/cleaner.
>>
>> That is, I much prefer:
>>
>> <h:panelGroup rendered="#{el.to.rendered} p:role="banner"
>> p:aria-hidden="#{el.to.hidden}">
>>
>> Over:
>>
>> <h:panelGroup>
>> <f:passThroughAttributes>
>> {
>> 'role' : 'banner',
>> 'aria-hidden' : #{el.to.hidden}
>> }
>> </f:passThroughAttributes>
>> </h:panelGroup>
>>
>> For the arbitrary # of dynamically determined attributes case,
>> literal/inline JSON representation does not help.
>>
>> Let's keep things simple and hold off on introducing literal JSON
>> representations.
>>
>> This might be something that we'll want re-evaluate, though I would
>> prefer that we tackle this separately from our pass through attribute
>> solution. Ideally we should look at this in a generic manner that
>> takes
>> other collection-based attribute cases (eg. h:dataTable's value
>> attribute) into account.
>>
>> 5. Do we need to support EL-binding to JSON strings?
>>
>> This would be to address the case where bonus attributes are
>> determined
>> via some 3rd party code (eg. a web service or a CDI producer) that
>> coughs up a JSON string, eg:
>>
>> <f:passThroughAttributes value="#{el.to.jsonString}"/>
>>
>> Proposal:
>>
>> I would prefer that we keep our solution simple and only support
>> binding
>> to Map<String, Object>, at least for this first pass. Like #4, if we
>> decide that this is an important use case to tackle, I would prefer
>> that
>> we evaluate this from a more generic perspective - eg. should
>> ui:repeat
>> support binding to a JSON string-specified array?
>>
>> Rather than complicating EL requirements, an alternate approach would
>> be
>> to expose an EL functions that are capable of performing translations
>> from JSON string to various collection types, eg:
>>
>> <f:passThroughAttributes
>> value="#{jsf.jsonToMap(el.to.jsonString)}"/>
>>
>> 6. Who wins: pass through or renderer attribute?
>>
>> Frank raised this use case:
>>
>> <h:inputText value="#{el.value} p:type="email"/>
>>
>> However, the inputText renderer already renders the type attribute.
>>
>> Should the result be that inputText renderer wins, in which case the
>> rendered HTML ends up as:
>>
>> <input type="value">
>>
>> Or the pass through attribute wins, in which case the rendered HTML
>> is:
>>
>> <input type="email">
>>
>> Proposal:
>>
>> Allowing page authors to override renderer-produced markup is a
>> tricky
>> business. This will lead to situations where the page author breaks
>> functionality that the renderer is relying on. However, it is also
>> tremendously powerful, as shown in Frank's trivial use case above.
>>
>> It's a bit unusual for me, but in this case I would recommend that we
>> give our users this rope - ie. I would like to see pass through
>> attributes trump others. I am concerned that if we don't do this, we
>> will be artificially limiting the usefulness of our pass through
>> attribute solution.
>>
>> Again, open to more discussion on this.
>>
>> 7. Do we need to add new HTML5 attributes to our tags anyway?
>>
>> Er… pass through attributes are great, particularly for cases where
>> our
>> components/tags fall behind markup language specs (HTML5, Aria,
>> etc…).
>>
>> However, we probably need to take a look at whether there is some
>> subset
>> of HTML5-related attributes that should be exposed as proper
>> component
>> attributes on the h:* components.
>>
>> One area that is particularly interesting is events, since we'll
>> likely
>> want to allow ClientBehaviors (eg. f:ajax) to be registered for
>> various
>> new client-side events. Our pass through attributes solution does
>> not
>> help with this.
>>
>> No proposal here, but wondering whether perhaps this is an area that
>> someone has already looked into? If not, we should add it to the 2.2
>> todo list (separate from pass through attributes).
>>
>> SUMMARY
>>
>> Let's add a new namespace for specifying pass through attributes on
>> any
>> component tags, eg:
>>
>> <h:panelGroup rendered="#{el.to.rendered} p:role="banner"
>> p:aria-hidden="#{el.to.hidden}">
>>
>> Let's add two tags for specifying EL bindings to Map<String, Object>
>> that can be used to populate the component attribute and pass through
>> attribute maps with bonus attributes, eg:
>>
>> <f:attributes
>> value="#{el.to.map.containing.bonus.component.attrs}">
>> <f:passThroughAttributes
>> value="#{el.to.map.containing.bonus.passthrough.attrs}"/>
>>
>> (Alternatively, one <f:attributes> tag with an attribute for
>> specifying
>> the map to target.)
>>
>> Attribute maps (both component and pass through) will be populated at
>> tag execution time (ie. no ability to bind to a row-specific map.)
>>
>> Pass through attributes trump renderer-specified attributes.
>>
>> No inline or EL JSON support.
>>
>> That's it.
>>
>> Possible follow on issues:
>>
>> - Support for JSON literals in collection-based attributes.
>> - Support for EL-binding to JSON-based strings (possibly via an EL
>> function) for collection-based attributes.
>> - Support for new HTML5 attributes on h:* tags.
>>
>> Thoughts?
>>
>> Andy
>>
>>
>>