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