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

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

From: Bauke Scholtz <balusc_at_gmail.com>
Date: Tue, 21 Jun 2016 21:34:23 +0200

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