users@javaserverfaces-spec-public.java.net

[jsr372-experts mirror] [jsr372-experts] Improvements for ajax update (related to Handling Focus after Ajax update)

From: Leonardo Uribe <leonardo.uribe_at_irian.at>
Date: Tue, 14 Apr 2015 20:42:54 -0500

Hi

I would like to point out some details on the JSF spec about how the ajax
update is being done. This is related to the topic "Handling Focus on Ajax".
My intention is answer the question "why JSF update the whole markup and
not only the affected attributes" and identify the current flaws on the spec.

In my opinion this is a REALLY IMPORTANT FIX, and fortunately the solution can
be done with a bit of effort.

The problem is all about how a JSF Component is rendered using an ajax request.
According to JSF 2.2 spec section 13.4.3 Partial View Rendering, it
says something
like this:

"... The UIViewRoot getRendersChildren and encodeChildren methods must determine
if the request is an Ajax request using the FacesContext.isAjaxRequest() method.
If PartialViewContext.isAjaxRequest() returns true, then the getRendersChildren
method must return true and the encodeChildren method must perform partial
rendering using the PartialViewContext.processPartial implementation ..."

This is what it says on PartialViewContext.processPartial(...) javadoc:

"... Perform lifecycle processing on components during the indicated phaseId.
Only those components with identifiers existing in the Collection returned from
getExecuteIds() and getRenderIds() will be processed. ..."

In few words, PartialViewContext.processPartial(RENDER_RESPONSE_PHASE) method
is the one responsible of call encodeXXX methods.

The problem can be seen when you take a look at how the default algorithm for
PartialViewContext.processPartial(...) really works. I'll describe the steps
done by this algorithm by default in MyFaces (only what's relevant of course):

- Get PartialResponseWriter
- Call writer.startDocument() and set id attribute
- If isRenderAll() true then
  - Call writer.startUpdate()
  - Call component.encodeAll() for every children of UIViewRoot instance
  - Call writer.endUpdate()
- Otherwise for each component in renderIds:
  - Call writer.startUpdate()
  - Call component.encodeAll()
  - Call writer.endUpdate()
- Call writer.endDocument()

Now, let's suppose you have a component and you know that the only thing that
changes between ajax request is the attributes attached with ValueExpression
instances. In

<h:inputText value="#{myBean.value}">
    <f:ajax ...>
</h:inputText>

HTML Markup

<input id="j_id_23" type="text" value="..."/>

Only changes "value" attribute, right?.

How to do it with the current plumbing? If you take a look at the xsd of
web-partialresponse, you can see this is a possible response:

<partial-response id="j_id_0">
    <changes>
        <attributes id="j_id_23">
            <attribute name="value" value="new value">
        </attributes>
        <update id="otherCompId"><![CDATA[... HTML Markup ...]]></update>
    </changes>
</partial-response>

The problem is the default algorithm is not flexible enough, because the
component cannot choose the proper command. By default, a call to update
the DOM markup is always done and it is always called encodeAll().

Third-Party vendors usually override PartialViewContext.processPartial(...)
to do some extra processing, but on the way, they override existing behavior
and create incompatibilities between libraries overriding the same lines of
code.

The proposal is add a new method in UIComponent called
encodeAjax(FacesContext context, PartialResponseWriter writer) that by
default execute something like this:

try
{
    writer.startUpdate();
    this.encodeAll();
} finally {
    writer.endUpdate();
}

In other words, move the code that writes the "update" section to the component,
to let the component choose how and when should be updated. In that way,
the component can choose between the valid options:

- update
- insert DOM node
- delete
- attributes
- eval
- extension

This change will provide a lot of flexibility that currently we just don't have
"out of the box".

Let's try something more complex. Suppose this code:

<div id="myBigAndComplexPanel">
    <!-- A big stateless chunk of HTML Markup -->
    <input id="j_id_23" type="text" value="..."/>
    <!-- Another big fat stateless chunk of HTML Markup -->
</div>

And I want to update "myBigAndComplexPanel", but I know that the only thing I
really need is update the input value. How?

The key point is to know if each component that matters has an id rendered on
the DOM markup. If the id is not rendered, we can't update the component
individually, because we don't have the reference, and we will be forced to
update the whole DOM markup for the panel. Suppose the triggering ajax
says this:

<f:ajax ... render="myBigAndComplexPanel"/>

The algorithm call UIComponent.encodeAjax(...) for the panel. Inside, the
component can check if is "stateless" or not, and then invoke
UIComponent.encodeAjax for each child component. UILeaf instances (HTML markup)
will not process anything, unless they contain embedded EL, but the input
component will think the best way to update itself and at the end it will
add the <attributes>...</attributes> block. If we can identify when the
output of a component has an id (UIComponent.isIdRenderedOnMarkup()) or
contains static markup or not (UIComponent.isRenderStatelessMarkup()), this
strategy will work. Also something like UIComponent.getUpdatedAttributes()
that used the "delta" state or the existence of ValueExpression instances in
attributes, to get which ones were changes and build the response.

In summary: "why JSF update the whole markup and not only the affected
attributes". Because "<update>" solves most of the use cases in core JSF.
It can be done, but there are some use cases that are not so simple to do
and requires some work. Anyway the fix for "handling focus" is required.

regards,

Leonardo Uribe