[jsr-314-open] JavaScript disabled support [Was: Outcome of JSFDays discussions]

Andy Schwartz andy.schwartz at ORACLE.COM
Thu Apr 2 14:53:48 EDT 2009


Hey Martin -

Thanks for writing this up. (I decided to break the disabled JavaScript 
topic out into a separate email thread to make this easier to track.)

Martin Marinschek wrote On 4/2/2009 3:24 AM ET:
>
> - 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.  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?

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.

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.

The main use case for 3B is master-detail components.  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.

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.

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.

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.

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).

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.

Andy




More information about the jsr-314-open-mirror mailing list