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

[jsr372-experts] Re: [jsr372-experts mirror] Re: Re: [JAVASERVERFACES_SPEC_PUBLIC-1423] Dynamic resource loading in ajax requests

From: Leonardo Uribe <leonardo.uribe_at_irian.at>
Date: Thu, 23 Jun 2016 14:45:23 +0200

Hi

Since there is already a solution almost completed in MyFaces (which an
small sample
done some time ago) I did a prototype to see how it works and try to find
other details
that could give us a better idea about what to do.

From spec perspective the relevant changes are:

UIComponent: Add the following methods:

public void encodePartial(FacesContext context) throws IOException

Default implementation call startUpdate(), encodeAll(), endUpdate();

UIComponentBase: Add the following methods:

public void encodePartial(FacesContext context) throws IOException

Default implementation if renderer found call Renderer.encodePartial,
otherwise call
super.encodePartial(...)

Renderer: Add the following methods:

public void encodePartial(FacesContext context, UIComponent component)
             throws IOException

Default implementation call startUpdate(), encodeAll(), endUpdate();

RequestViewContext: (it could be located in FacesContext)

public void setRenderTarget(String target, boolean value,
            UIComponent component)

public List<UIComponent> getComponentsToRender(String target);
(it has an internal set to avoid duplicates, find a better name)


The remaining tasks are implement encodePartial(...) for h:outputScript,
h:outputStylesheet, and update the implementation in PartialViewContext to
call
encodePartial.

This is a fragment of the generated response when the change
is activated:

<partial-response id="j_id__v_0"><changes><eval>
<![CDATA[myfaces._impl.core._Runtime.loadScript(
"/client-window-example/javax.faces.resource/script1.js.jsf", null, null,
"UTF-8", false);]]></eval><update id="content"><![CDATA[ ...

The main page look like this:

<h:commandLink id="page1" value="Page 1"
actionListener="#{ajaxContentBean.setPage1}">
    <f:ajax render=":content"/>
</h:commandLink>
...
<h:panelGroup id="content" layout="block">
    <ui:include src="#{ajaxContentBean.page}.xhtml" />
</h:panelGroup>

And the included page has something like this:

<h:outputScript name="script1.js" target="head"/>
<h:commandButton type="button" value="MSG" onclick="script1()"/>

It works well for scripts but try to update something like this:

<h:outputScript target="head">
script2 = function(){
    alert("script2");
}
</h:outputScript>

or this:

<h:outputStylesheet name="style3.css"/> (target="head" is implicit)

is the real problem. You can't use an eval block because the encoding does
not work
correctly, so you really need to insert the DOM node. But the current API in
PartialResponseWriter is useless because head tag does not have an id or
name where you
can grab.

Ok, we can try to "make cheat" a little in this part and try:

prwriter.startInsertAfter("javax.faces.ViewHead");

but strictly speaking this should insert the html markup after </head>, but
we want
the code inside. I just modified the js code to include it inside in this
case, but for
a real implementation it is required to add some methods to
PartialResponseWriter like:

public void startInsertFirst(String targetId) throws IOException
public void startInsertLast(String targetId) throws IOException

(find a better name, or maybe startInsertInsideAfter?, look in another
framework how
they name this?)

There is also another problem: How do you know you need to insert the node
instead of
update it? The only clue is the node is processed specially in
PartialViewContext like
this:

List<UIComponent> componentsToRender = rvc.getComponentsToRender("head");

for (UIComponent resource: componentsToRender)
{
    resource.encodePartial(_facesContext);
}

So, we need some flag or context attribute or something to indicate we are
attempting
to include something new on the tree. Something like
JAVAX_FACES_INSERT_PARTIAL for
example and something like JAVAX_FACES_UPDATE_TARGET, so the Renderer in
encodePartial
can decide how to update the view.

The partial response markup looks like this:

<changes><insert><after id="javax.faces.ViewHead"><![CDATA[
<script type="text/javascript">
script2 = function(){
    alert("script2");
}
</script>]]></after></insert>

It should be better

<changes><insert><last id="javax.faces.ViewHead"><![CDATA[<script
type="text/javascript">
script2 = function(){
    alert("script2");
}
</script>]]></last></insert>

And that's it the proposal from MyFaces side. I think it is flexible enough
to allow the
algorithm using "two lists comparison", but also enough to allow the
implementation
inside MyFaces.


But note the issue proposed here:

JAVASERVERFACES_SPEC_PUBLIC-1404 Add
UIViewRoot#getRenderedComponentResources()

is still valid. But I'll send the comments related to that one in other
email.


Suggestions are welcomed.

regards,

Leonardo Uribe



2016-06-22 9:19 GMT+02:00 Cagatay Civici <cagatay.civici_at_gmail.com>:

> Sure, actually Thomas Andraschko, the reporter of
> JAVASERVERFACES_SPEC_PUBLIC-1423 has implemented it in PF, I’ve asked him
> for details and his response briefly is;
>
> Have a look at:
> org.primefaces.application.resource.DynamicResourcesPhaseListener - it
> collects the initial resources on the ajax request
> org.primefaces.context.PrimePartialResponseWriter - ln 278; gets the
> resources again, creates a diff of the initial and new resources, render
> some JS to load the resources dynamically
>
> Regards,
>
> Cagatay Civici
> PrimeFaces Lead
> PrimeTek Informatics
>
> On Wednesday 22 June 2016 at 10:02, Bauke Scholtz wrote:
>
> Cagatay, can you point out it in the PrimeFaces codebase? Then I will look
> at similarities and differences and if necessary optimize the standard
> solution based on that.
>
> Cheers, B
>
> On Wed, Jun 22, 2016, 08:43 Cagatay Civici <cagatay.civici_at_gmail.com>
> wrote:
>
> We also have a custom solution to this in PrimeFaces as it is a very
> common case. A standard solution would be preferred for sure.
>
> Regards,
>
> Cagatay Civici
> PrimeFaces Lead
> PrimeTek Informatics
>
> On Tuesday 21 June 2016 at 22:34, Bauke Scholtz wrote:
>
> Coming to it, a new encodeAjax() method in UIComponent and Renderer (I'd
> rather call it encodePartial(), in line with the existing API) is a great
> suggestion which could indeed solve this problem and has potential for
> solving/optimizing ajax based problems in a much more flexible way. This is
> indeed a small and easy change in the API (and impl).
>
> +1 from me.
>
> Cheers, B
>
>
>
>
> On Tue, Jun 21, 2016 at 5:22 PM, Bauke Scholtz <balusc_at_gmail.com> wrote:
>
> As to writer.startEval(), as per
> https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1412 I've added
> a PartialViewContext#getEvalScripts() for JSF 2.3. This could be used.
>
> I will think about encodeAjax() and reply later.
>
> Cheers, B
>
> On Tue, Jun 21, 2016 at 4:01 PM, Leonardo Uribe <leonardo.uribe_at_irian.at>
> wrote:
>
> Hi
>
> I just wanted to repost a comment from MyFaces issue tracker in 2012 about
> the issue being discussed now:
>
>
> Luciano Deiru:
>
> Thanks for the reply.
>
> I don't think it's going to be possible for me to migrate my app to JSF2
> without a
> complete re-write of the frontend. I did a little more digging to see if
> richfaces was
> the issue and i can across this section of the richfaces 4.2.2 development
> guide...
> ...
>
> "JSF 2 does not allow resources such as JavaScript or Cascading Style
> Sheets (CSS) to
> be added if the element requiring the resource is not initially present
> in the JSF
> tree. As such, components added to the tree via Ajax must have any
> required resources
> already loaded. In RichFaces, any components added to the JSF tree should
> have
> components with corresponding resources included on the main page
> initially. To
> facilitate this, components can use the rendered="false" setting to not
> be
> rendered on the page."
> ...
>
> So the only workaround would be to add every possible richfaces and
> primefaces component
> to the login page so it loads the JS and css. That's a bit depressing
> because you
> can't create a "pure ajax"/dynamic application in JSF2 even tho i was
> possible in JSF1.
> It seems like a widely used approach and there are a lot of forum posts
> about the
> issue but no one is ever able to get it working without including the
> hidden components.
>
>
> regards,
>
> Leonardo Uribe
>
> 2016-06-21 15:55 GMT+02:00 Leonardo Uribe <leonardo.uribe_at_irian.at>:
>
> Hi
>
> I see.
>
> BS>> Also, to avoid duplicates and/or unnecessary re-rendering, all so far
> rendered
> BS>> resources must be remembered in JSF state. AFAIC this is currently
> not the case.
>
> Well, this is information related to the view that is implicit there, but
> you want to
> avoid to put it in the state.
>
> That's the reason why I like the way it is working in MyFaces, because we
> just keep
> track of the change, rather than store that information in the state.
>
> Write the script directly into the <eval> is possible, but right now from
> spec
> perspective there are not the necessary "building blocks" available.
>
> Both h:outputScript and h:outputStylesheet are components that are
> supposed to be
> rendered as resources, but both components have Renderer instances, which
> are
> the classes who finally decide how they should be rendered. Even if you
> have a list,
> this list could include javascript/css or whatever and the resources could
> be rendered
> in different ways.
>
> For example an h:outputScript could have children that could be js code.
> Or a
> h:outputStylesheet could have a "media" property set.
>
> So if we want a "general" solution to the problem, which includes script,
> stylesheets
> or whatever, it is necessary to know the component instance that needs to
> be added
> or updated, and then ask the component how it should be rendered.
>
> The problem from the "building blocks" perspective, is there is no way the
> component
> can know the current request is an ajax update. encodeAll(...) just means
> encode
> everything and don't ask questions.
>
> I would like to have a method in UIComponent/Renderer called
> encodeAjax(...) for the
> component, which by default start and end an <update> section and call by
> default
> to encodeAll(...). Something like this
>
> public void encodeAjax(FacesContext _facesContext, PartialResponseWriter
> writer) {
> try
> {
> writer.startUpdate(target.getClientId(_facesContext));
> target.encodeAll(_facesContext);
> }
> finally
> {
> writer.endUpdate();
> }
> }
>
> This part is done in PartialViewContext implementation, but if we can move
> it here
> we could ask the component in a gentle way how it should be rendered.
> Then, from
> this location we could override the method and call writer.startEval(...),
> which is
> something is not possible in this moment, because there is no way right
> now to
> customize this step for each component.
>
> In this way, you could add an script to load the stylesheet and then use
> the eval
> to include it in the response.
>
> I have already mention this improvement in this mail some time ago:
>
>
> https://java.net/projects/javaserverfaces-spec-public/lists/jsr372-experts/archive/2015-04/message/41
>
> It looks we have yet another use case where this could be useful.
>
> regards,
>
> Leonardo Uribe
>
>
> 2016-06-21 14:51 GMT+02:00 Bauke Scholtz <balusc_at_gmail.com>:
>
> Hi,
>
> OmniFaces has 3 solutions to dynamically include a (script) resource:
>
> 1: Ajax#load() (just write script straight to <eval>)
> 2: Components#addScriptResourceToHead()
> 3: Components#addScriptResourceToBody()
>
> Which one to use depends on the moment (which isn't yet refactored into a
> single utility method, this is TBD). When the request is an ajax request
> with partial rendering (i.e. ajax with NO @all), then use #1. Else when the
> current phase is NOT render response, or the view is in BUILDING state,
> then use #2. Else use #3.
>
> This is implemented for the unload script associated with OmniFaces
> @ViewScoped and this works quite well.
>
> Also, to avoid duplicates and/or unnecessary re-rendering, all so far
> rendered resources must be remembered in JSF state. AFAIC this is currently
> not the case.
>
> Cheers, B
>
>
> On Tue, Jun 21, 2016 at 2:37 PM, Leonardo Uribe <leonardo.uribe_at_irian.at>
> wrote:
>
> Hi
>
> I have seen this issue has been opened in the spec issue tracker
>
> https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1423
> Dynamic resource loading in ajax requests
>
> I would like to contribute with some thoughts about the topic, since in
> MyFaces there
> is a solution that is not perfect (if PartialViewContext is overriden, the
> code will
> not work, so some third party libraries will not be able to use it), but
> it helps to
> identify the missing points in the spec, and specify it faster.
>
> This issue could be also relevant for portlets.
>
> The issue has been reported long time ago in:
>
> - https://issues.apache.org/jira/browse/MYFACES-3106
> Resources not loaded when using a dynamic ui:inlclude and rendered via ajax
>
> - https://issues.apache.org/jira/browse/MYFACES-3367
> Detect when to update head or body target when content has been updated
> dynamically
>
> - https://issues.apache.org/jira/browse/MYFACES-3659
> Conditional include of scripts and stylesheets
>
> The first thing to understand the problem is identify the possible use
> cases we have:
>
> - c:if, ui:include src="#{...}" or facelet tag that dynamically update the
> tree.
>
> For example:
>
> ...
> <f:ajax event="onclick" render="box"/>
> ...
>
> <div jsf:id="box">
> <c:if test="#{...}">
> <h:outputScript target="head" library="js" name="help.js" />
>
> ...
> </c:if>
> </div>
>
> Here we have a case when the components inside the c:if requires some
> javascript file,
> but the update comes from an ajax request. The algorithm just somehow put
> the
> h:outputScript on the head of the document, but the ajax does not trigger
> the head
> refresh, so the component was added to the tree, but the javascript file
> was not loaded
> and we finally have a broken panel.
>
> There are variants of the same example with h:outputStylesheet, or
> ui:include but the
> reason is the same.
>
> - Dynamic but the resource is added by a @ResourceDependency annotation.
>
> This is similar to the previous case, but the component is added
> indirectly when
> Application.createComponent(...) is executed.
>
> The important thing to keep in mind is there are 3 possible targets:
> "head", "body" and
> "form". But the only thing we can do with the current spec is just force a
> full update
> of each of these targets.
>
> So, in MyFaces there is a web config param called
> org.apache.myfaces.STRICT_JSF_2_REFRESH_TARGET_AJAX that when it is
> activated, and
> the previous situation happens, it just enable a flag so the target is
> updated with
> the ajax request. If the "head" needs to be updated, the whole "head" and
> the updated
> content is sent and so on.
>
> The processing is done on process partial render time, after the ajax
> request is
> rendered but before write the state token. Please note if the target is
> rendered before
> this code, the algorithm should be able to detect the condition and do not
> duplicate
> the response.
>
> The solution is not perfect because it force render the whole target, when
> we only
> need to render the html fragment of the added component (script/stylesheet/
> other js code). But there is no way to do it with the current API, because
> <head>
> or <body> could not have an id and without id, you can't insert.
> PartialResponseWriter
> contains these methods:
>
> startInsertBefore(String targetId)
> startInsertAfter(String targetId)
>
> But note these two are useless, because what we need is insert "inside" at
> the beginning
> or the end.
>
> For example:
>
> <head>
>
> <script .../>
> </head>
>
> <body>
> <script .../>
>
> </body>
>
> Please note there are some cases where the jsf third party library (for
> example
> primefaces) provides its own rules to to render the script at first,
> middle or last. But
> in this case it doesn't matter those rules, because what we really need is
> that the
> resource is added.
>
> The code that activates the flags to render head, body or form target is
> on
> h:outputScript and h:outputStylesheet, specifically in the listener
> attached to
> PostAddToViewEvent. This is the best place, because here is the place
> where
> UIViewRoot.addComponentResource is done.
>
>
>
> I think what's really important in this problem is provide the API where
> you can notify
> that the resource needs to be added. In MyFaces there is:
>
> class RequestViewContext {
>
> public boolean isRenderTarget(String target);
>
> public void setRenderTarget(String target, boolean value);
>
> }
>
> Which could evolve to something more complex, including the UIComponent
> instance and
> so on.
>
> I don't really like to do a list comparison, because it can introduce an
> unnecessary
> performance overhead. I would like an API that could have some methods to
> register the
> component for updated on the target and others to get the changes and do
> the necessary
> calculation in PartialViewContext.processPartial(...) (render response).
>
> regards,
>
> Leonardo Uribe
>
>
>
>
>
>
>
>
>