Hi Andy,
thanks for your very long and extensive mail - you covered everything.
Comments inline.
>
> - In the presentation about JSF 2.0 features, there was a question from the audience
what happens with AJAX support when JavaScript is disabled.
There are at least 3 different cases to consider:
1. Command components firing Ajax-based action events, eg:
<h:commandButton>
<f:ajax/>
</h:commandButton>
2. Editable value holders firing Ajax-based valueChange events, eg:
<h:inputText>
<f:ajax/>
</h:inputText>
3. Ajax-based events that other than #1 and #2, eg:
<h:panelGroup>
<f:ajax event="mouseOver"/>
</h:panelGroup>
Or even:
<h:commandButton>
<f:ajax event="mouseOver"/>
</h:commandButton>
Case #3 is the simplest, so let's look at it first. What is the correct fallback
behavior for client behaviors/scripts that are invoked in response to user interface
events such mouseOver/mouseOut, or say, focus/blur? I don't believe that there is any
meaningful fallback content that we can provide here - a "Refresh" button
isn't appropriate. My feeling is that for these cases, the correct fallback behavior
is to do nothing.
I agree.
Application developers who need to build applications that run with JavaScript disabled
should only leverage such events for progressive enhancement purposes - ie. such
applications should not rely on these user interface events as the only means to access
mission critical functionality. So, think we are okay for this particular case - I
don't see the need to do anything here.
Case #1 is more interesting... So what fallback behavior do we want when we are using
Ajax to send an action event to the server in response to a click on a command component?
The correct answer here is clear: when JavaScript is disabled, the command component
should still deliver the action event, but via traditional form submission instead of over
Ajax.
Actually, this is exactly the fallback behavior that we have in the trivial
<h:commandButton> example shown above... When Ajax is disabled, the
<h:commandButton> will still submit - it just an HTML <input
type="submit"> after all - and any registered action listeners will be
called. So we've got a little graceful degradation happening already. Well, in this
trivial case at least. However, there are at least two cases where we do not have a
solution:
- <h:commandButton> with <f:param>
- <h:commandLink> (with our without <f:param>)
Both of these cases currently require JavaScript execution for form submission purposes.
Note that this is not in any way specific to Ajax or Behaviors, or even JSF 2.0. So
we've got a more fundamental problem here.
Leaving Ajax/Behaviors aside for a moment - how would we go about solving more basic
cases such as supporting <h:commandLink> when JavaScript is disabled? The solution
requires two pieces:
- We need some way to detect when JavaScript is disabled.
- Components that care need to render alternate content when JavaScript is disabled.
For example, if we had a way for the <h:commandLink>'s renderer to determine
that JavaScript is disabled, then the renderer could fall back to rendering an HTML
<input type="submit"> instead of a link with an onclick handler.
The <f:param> case is a bit trickier. I suppose that the link/button renderer
could spit parameter values out as hidden fields instead of using JavaScript to include
these values in the form dynamically. The drawback of course is that the hidden fields
will be submitted with every postback, not just when the corresponding button is clicked.
Or perhaps we could use an HTML <button> element and encode the parameters into the
button's value attribute? I believe that Spring Faces has solved this problem (Jeremy
- can you comment)? Does cs-JSF deal with this? Any other frameworks provide solutions
for this?
cs-JSF deals with this. What we do is we encode params into the
button-value, and decode these params in a request-parameter map
wrapper so that they are available to the rest of the lifecycle just
as if they were normal params.
In any case, the point is that for the command/action case, the solution really lies in
the component renderer - this is not an Ajax/Behavior issue. If we solve the generic
problem of supporting <h:commandLink> and <h:commandButton> with
<f:param> when JavaScript is disabled, then this is sufficient to address the Ajax
case as well.
yes, this is true - the ajax-behaviour is supposed to render nothing
in this case (so it is also not expected to provide a script).
Case #3 is the trickiest... There are actually two sub-cases here:
3A. The Ajax request is optional - Ajax is used to progressively enhance the editable
value component.
3B. The Ajax request is required - Ajax is used to provide functionality that the end
user needs in order to complete some task in the application.
An example of 3A might be Ajax-based validation. The application should of course
re-validate any data when the form is submitted. Disabling JavaScript is not a problem
here, since the Ajax-based functionality is an enhancement, not a required feature.
rather seldom used feature.
The main use case for 3B is master-detail components.
.. and a lot of other stuff - it is the most-used use-case for us for
partial page update with regards to input-components. You change some
input, and somewhere else, some input shows up, vanishes, gets
disabled enabled, etc.
A common case is using a select component as the master and updating the detail contents
based on the user's selection. For example, the user selects a car manufacturer from
an Ajax-enabled <h:selectOneMenu>. This populates a second <h:selectOneMenu>
with the appropriately filtered set of car models. If JavaScript is disabled, and the
<f:ajax event="valueChange"> is the only means provided by the application
to trigger the population of the second select component, any users who have disabled
JavaScript will be stuck.
There are a range of possible solutions here:
i. Give Behaviors the ability to render arbitrary contents before/after/during component
rendering.
One concern that I have about this approach is that it means that the Behavior
implementation needs to be familiar with the component - and possibly the component's
content - to know when to render, or whether to render anything at all. For example, if
the Behavior is attached to an <h:selectOneMenu>, then it might want to render a
"Refresh" button. However, if the Behavior is attached to an
<h:commandButton> (or some other component, like an <h:panelGrid>), then no
"Refresh" button should be rendered.
you are right - it is component dependent. but don't we have several
decisions already in the behaviour classes, depending on the
component-type? e.g. the default event-type (onclick, onchange, etc.).
So how is this different?
I would prefer that we come up with a solution where the component
Renderer decides when/where to have the Behavior render its fallback content, since the
component Renderer knows both whether it requires fallback content at all and also knows
where the best placement of the fallback content might be.
ok, but with the same argument we could have said that ajax-rendering
should be happening directly in the renderer from the beginning,
right? The idea is that any component can be AJAX enabled, now when
JavaScript is disabled, the arbitrary component will not have any idea
that AJAX was enabled from the beginning, so also not any knowledge at
all that it should render a fallback now.
ii. Provide a more targeted Behavior API that component renderers can
use to ask a Behavior to render fallback content.
Instead of allowing Behaviors to render arbitrary content before/after/during component
rendering, we would instead provide a couple of new methods, perhaps on ClientBehavior
(not sure about the method names):
- public boolean isFallbackAvailable()
- public void renderFallback(BehaviorContext)
Or, alternatively, we can add these on a new FallbackClientBehavior interface (extends
ClientBehavior).
Components/renderers that want to render fallback content (eg. input/select components)
would call renderFallback() at the appropriate point in order to insert the Behavior's
fallback content into the response. Components/renderers that do not want to render
fallback content (eg. pretty much everything other than the input/select components)
wouldn't bother calling this.
in this case, we would really need to provide something in the spec
which checks if javascript is enabled. Can we really still do this for
2.0?
iii. Let the component renderers provide fallback content.
The idea here is that the input/select component renderers already have enough
information available to determine whether or not fallback content (ie. a
"Refresh" button) is required. (Well, there still is the question of how to
detect whether JavaScript is disabled, but leaving that aside for the moment). So, for
example, the <h:selectOneMenu> renderer could very easily check to see:
- Whether it has any ClientBehaviors attached for the "valueChange" event.
- Whether any of these ClientBehaviors are "submitting" behaviors.
In the event that:
- The <h:selectOneMenu> has a submitting valueChange ClientBehavior attached,
and...
- JavaScript is disabled
The <h:selectOneMenu> could then render its own "Refresh" button. No
need for the Behavior to do anything.
Okay, so after writing this option up, it occurs to me that not all submitting behaviors
would require fallback content to be rendered - eg. the presence of an Ajax-based
validator (case #3A above) should not cause a "Refresh" button to appear. So if
we want to leave it up to the component renderer to provide fallback content, we would
need some API on the ClientBehavior (maybe a new hint), that the component renderer would
check to see whether fallback content is actually needed.
again, we would need to bake javascript detection into the spec.
iv. Don't provide any automatic solution - let the application
provide a solution.
The idea here is that if we provide some easy way to determine whether JavaScript is
disabled (eg. a method on the FacesContext), page authors who care about this can do
something along the lines of:
<h:selectOneMenu>
<f:ajax/>
</h:selectOneMenu>
<!-- Provide a refresh button, but only when scripting is disabled -->
<h:commandButton value="Refresh"
rendererd="#{facesContext.scriptingDisabled}"/>
Think that's it for possible solutions.
If we want to enhance the Behavior API to support fallback content rendering, I strongly
prefer option (ii) over option (i). This solution seems simpler, more in line with the
existing ClientBehavior model, and requires that fewer assumptions be built into Behavior
implementations. I think this is a better split of responsibilities between the component
and the Behavior (ie. component decides when/where fallback content is needed, Behavior
produces the fallback content).
I see it exactly the other way round - I prefer (i) over (ii), for the
reasons above.
BTW, an important question that we should consider is whether JSF
itself should provide a facility for detecting whether client-side scripting is enabled.
This could be very useful - ie. could allow us to solve long-standing problems. For
example, if we have a way to detect when JavaScript is disabled, seems like we could
provide an <h:commandLink> that works when JavaScript is disabled fairly easily
(fall back to an <input type="submit">).
As for how to go about detecting whether JavaScript is disabled, I was thinking we would
do something along these lines:
- FormRenderer spits out a <script> that dynamically adds a hidden field with some
well known name.
- On postback, we check to see whether this hidden field is present
- If the hidden field is present, we know that scripting is enabled. Yay.
- If the hidden field is not present, we assume that scripting is disabled.
Of course, this means that we cannot detect whether JavaScript is enabled until the first
postback is performed. Is there some way to get around this? Martin or Jeremy - I know
you guys have provided solutions for this sort of thing - please let jump in if you have
any suggestions.
ok, now this is not so extremely easy. What we do in cs-JSF is we have
a noscript tag - in this noscript tag, we render content which
displays a modal dialog, telling the user that javascript is disabled
and that he is forwarded to a javascript-free version of the page.
I don't see how we can easily do the same in the spec - what we could
do is we could provide some information in the faces-messages section
of the page including a link. This information would only be rendered
in a noscript environment and - on clicking the link - would forward
the user to the script-free version of the page.
Without this, we cannot check if javascript is enabled on the first
page, and therefore we cannot make sure that rendering is done
appropriately on the first page. Another option would be to let the
application decide how it detects the JavaScript state (or have the
message be a default behaviour, and optionally let the application
reconfigure).
regards,
Martin