users@javaserverfaces-spec-public.java.net

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

From: Blake Sullivan <blake.sullivan_at_oracle.com>
Date: Fri, 08 Jun 2012 11:25:09 -0700

I think that the jsfc and passThru attribute discussions suggest two
different use cases:
1) Annotating/overriding the output of the rendering of normal JSF
components (the passThru attribute case)
2) Associating JSF components with HTML markup (the current jsfc case)

For the second of these, I question what jsfc is bringing to the table
if we simply say that the presence of a component attribute implies the
presence of a component:

<div class="comp-div" data-foo="bar" c:rendered="#{el.to.rendered}">

If a page author wants to use template markup with the odd component,
this seems sufficient. The only slightly wonky bit has to do with ids,
since the renderer wants to own the id in order to apply the naming
container rules. For example, this could lead to trouble:

<div*id="myDiv"* class="comp-div" data-foo="bar" c:rendered="#{el.to.rendered}">

For clarity reasons, I would propose that in cases where a component is
associated with the markup, we disallow id and suggest c:id instead:

<div*c:id="myDiv"* class="comp-div" data-foo="bar" c:rendered="#{el.to.rendered}">

Of course, if we aren't using jsfc, there is the question of how
associate the component (and renderer) with the markup. We could just
require the use of JSFc and be done with it, but if our goal is to make
things as simple as possible for page authors, why would we? It would
seem preferable to use the markup to associate the component for the
page author by default. If the page author wants a different component,
she can use the jsfc attribute (which maybe should be c:jsfc to do so).

That actually leads to four more questions (with my opinions)
1) Should the Renderer actually be rendering anything (other than the
clientId) (probably not--the page author already specified her markup)
2) Should the non-component attributes be present on the component
instance (as passThru attributes?) (yes)
3) If we are already associating the html markup with the components,
should we work harder to map the HTML attributes to component attributes
(yes)
4) Should we support an explicit jsfc value for "use the default
component/renderer combinations). (yes c:jsfc="auto" or some such)

Here is an example of component mapping:

<input c:id="name" type=text" value="#{myBean.name}" disabled="disabled"/>

I think that a page author might reasonably expect that the markup would
have an associated inputText conponent with a valueExpression that
writes back into the managed bean and sets the disabled attribute to true.

This does potentially cause problems if we are sticking the pass through
attributes into the the pass thru map on the component--we don't want
the same attribute in two different places. I would say that only the
"type" attribute ends up in the passThru map in this case. The downside
of this is that it is probably not clear which attributes should end up
in the passThru map vs. being consumed. This can also cause backwards
compatibility problems if we add a component attribute at a later point
in time and would like to map it from HTML.... That blows. I change my
mind--I would say that all non-explicit component attributes populate
the passThru map but the attributes are not bidirectional with the
component attributes.

-- Blake Sullivan


On 6/7/12 11:29 AM, Frank Caputo wrote:
> Hi Andy,
>
> great summary. For the jsfc syntax we should consider namespacing the component attributes and passthrough all others as you suggested earlier:
>
> <div class="comp-div" data-foo="bar" c:rendered="#{el.to.rendered}" jsfc="h:panelGroup">
>
> But let's discuss enhancing the jsfc syntax in another issue after finishing passthrough attributes and maybe the generic element.
>
>
> Cheers,
> Frank
>
> Am 06.06.2012 um 18:49 schrieb Andy Schwartz:
>
>> 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
>>
>