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

Martin Marinschek mmarinschek at APACHE.ORG
Tue Apr 7 08:43:35 EDT 2009


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




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