jsr344-experts@javaserverfaces-spec-public.java.net

[jsr344-experts] Facelet page with dynamic content and update ajax content does not work as user expects

From: Leonardo Uribe <lu4242_at_gmail.com>
Date: Wed, 19 Oct 2011 14:07:46 -0500

Hi

There are some use cases that are asked frequently by users and that does not
work as expected in JSF. I think it is important to discuss them, because at
the end it is necessary to include some changes on the spec and some
side effects has been discussed earlier or are related to the changes planned
to do on JSF 2.2 spec.

In short, some of these issues happens when an ajax request try to update
content that is generated in a dynamic way, or in other words, there is some
component tree manipulation (add, remove or relocate component). Below there
is a full list of the problems. I ask you to read this carefully and take this
seriously. To make JSF work in a reliable way in such cases it is necessary
to resolve each one of them. I already did some work on MyFaces, so at least
my intention is solve them there. It would be a pity if JSF EG does not
solve these problems.

1. PSS STATE UNSTABLE

In theory, facelets creates a AST (abstract syntax tree) that later produce
a JSF component tree. The problem start with the tags that change dynamically
the tree, or in other words change the way the component tree is generated.
These are the known use cases:

- <c:if ...>
- <c:when>
- <ui:include src="#{...}">
- <ui:decorate template="#{...}">
- <c:forEach ...>

The problem with these tags is from start they break partial state saving
(PSS) algorithm. PSS is based on the assumption that you can restore the
initial state just applying the AST, and then on that tree you can just apply
the delta.

But these tags makes each time the component tree is recreated a different
component tree could be restored. All components affected by those tags will be
"unstable". Users have reported this like "... this use case using ui:include
worked on facelets 1.1.x and now it does not work ...".

In MyFaces core I did a hack adding a web config param called:

org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS_PRESERVE_STATE

that mark the parent of the affected component, so it and all its children
will be restored fully like in JSF 1.2. That solves the problem in a reliable
way but it is not a full solution (or in other words it solves the problem
but doing cheating). Some days ago I added a full solution
which involves change the id generation to use a "hierarchical counter",
to ensure stability for client and component ids. Note this is critical for
PSS algorithm. For more information take a look at:

https://issues.apache.org/jira/browse/MYFACES-3329

To solve this I added an object that store the initial state of c:if,
c:when, ui:include and ui:decorate, and associate an id for such tags.

The fix done works but you can't create custom tags like the ones affected,
so I suppose a fix on the spec should be done to add the required semantic.

2. JAVASCRIPT DOES NOT UPDATE <link>

Now take a look at this example:

include.xhtml

<h:commandLink ...>
   <f:ajax render="@all"/>
</h:commandLink>

...
<f:subview id="content">
<ui:include src="#{testManagedBean.page}"/>
</f:subview>

page1.xhtml

<ui:composition
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:outputText id="component1" value="Page 1"/>
<!-- ... more components ... -->
</ui:composition>

page2.xhtml

<ui:composition
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:outputStylesheet ... />
<h:outputText id="component2" value="Page 2"/>
<!-- ... more components ... -->
</ui:composition>

There are one main page (include.xhtml) that uses <ui:include src="#{...}>
hack to change the inner content dynamically. To change a page it uses
a command link with a <f:ajax render="@all">. In theory it should work but
in practice <h:outputStylesheet> entries are ignored. The same happens for
components with ResourceDependency annotation. The reason is the <head>
section of the html document is not updated, only the scripts are executed.

After discuss this on myfaces dev list, the conclusion was this is possible
and this should be considered a bug (for both MyFaces and Mojarra).

3. NO @head OR @body ON THE SPEC

Note the javascript API on the spec mention the following payload:

<update id="javax.faces.ViewHead">
   <![CDATA[...]]>
</update>

<update id="javax.faces.ViewBody">
   <![CDATA[...]]>
</update>

but there is no @head or @body. A clarification is necessary on the spec.

4. DYNAMIC CONTENT SHOULD BE UPDATE FULLY

Now consider replace the previous link of the problem 2. with this one:

<h:commandLink ...>
   <f:ajax render="content"/>
</h:commandLink>

Here the problem is if the dynamic content changes and add a resource under
"head" target (h:outputStylesheet does that), shouldn't be added a section
on the ajax payload to update the <head> section? In theory yes, because
this breaks encapsulation principle. If the user says render all inside
content if the <head> section changes it is responsability of the framework
(in this case PartialViewContext) to detect that an send the correct
payload, right?. Here we have two options:

a. Keep track of the resources rendered and save that on the state, then use
that information to check if the head should be rendered.
b. Use PostAddToViewEvent to check when a change on the component tree has
triggered a change on the head.

Option b. save some bytes on the state but it could cause render <head>
section more than necessary (for example a dynamic change but the head
has already rendered the resource, so it is not necessary). Option a.
impose that you need a way to check if the <head> was changed, and
require changes on the spec.

I'll solve this problem adding a web config param on MyFaces and doing some
changes on the algorithm, adding a flag to indicate if a view is being built
by first time. I think the spec lacks of some flags to indicate if the view
is being created, refreshed or if the state is being restored. In 2.1
a flag to know if the state is being saved was added but the other ones
are useful too.

5. IT IS NOT CLEAR HOW TO CREATE A RELOCATABLE COMPONENT

Components like h:outputScript or h:outputStylesheet lose the state, because
facelets algorithm should be able to find the component after relocation
and prevent the duplicate creation and later discard. Checking Mojarra I
have seen an alternative solution, but in my personal opinion this detail
should be level up to the spec. Maybe some utility method on FaceletContext,
that could be called by the TagHandler and a interface to indicate which
ComponentHandler instances use them:

public interface RelocatableResourceHandler
{
    public UIComponent findChildByTagId(FaceletContext ctx,
                                        UIComponent parent,
                                        String id);
}

So you can override in this way:

    public UIComponent findChildByTagId(FaceletContext ctx,
                                        UIComponent parent,
            String id)
    {
        //Script with no target and no relocation is possible
        UIComponent c = ctx.findChildByTagId(parent, id);
        if (c == null)
        {
            UIViewRoot root = ctx.getViewRoot(ctx, parent);

            if (root.getFacetCount() > 0)
            {
                Iterator<UIComponent> itr =
                             root.getFacets().values().iterator();
                while (itr.hasNext() && c == null)
                {
                    UIComponent facet = itr.next();
                    c = ctx.findChildByTagId(facet, id);
                }
            }
            return c;
        }
        else
        {
            return c;
        }
    }

It is a lot easier.

6. UPDATE "javax.faces.ViewState" IN A CONSISTENT WAY AND FIX FLASH SCOPE

This issue has been discussed earlier, but again it suppose a problem when
update content using ajax. After discuss and thinking this issue, it seems
necessary to generate a unique id per JSF environment/UIViewRoot-window
instance, and store that in a hidden field. With that, the js part could
locate the affected fields on a form and update them. This will work on
portlet case, and can be used for derive a windowId.

In practice, flash scope relies on cookies to work, but this scope gets
broken on multi-window applications. With the additional hidden field could
be used by flash scope.

regards,

Leonardo Uribe