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

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

From: Bauke Scholtz <balusc_at_gmail.com>
Date: Mon, 27 Jun 2016 13:12:44 +0200

As to "render target", isn't PartialViewContext#getRenderIds() sufficient
for the task? This returns a mutable collection of client IDs which need to
be updated on current partial request.

Cheers, B


On Mon, Jun 27, 2016 at 11:56 AM, Leonardo Uribe <leonardo.uribe_at_irian.at>
wrote:

> Hi
>
> 2016-06-24 10:35 GMT+02:00 Bauke Scholtz <balusc_at_gmail.com>:
>
>> encodePartial() looks OK.
>>
>> I only don't understand how RequestViewContext, setRenderTarget() and getComponentsToRender()
>> are useful as compared to
>> UIViewRoot#addComponentResource()/getComponentResources().
>>
>>
> I haven't found good names for those ones but I'll explain the intention
> of them.
>
> setRenderTarget(...) is used to indicate a component inside a specified
> target needs to be updated on the client in the current partial request. In
> comparison, addComponentResource(...) is used to add the component to the
> tree.
>
> I know both methods look similar but they are not the same. The reason is
> addComponentResource(...) is used to affect the component tree, so it will
> be called each time the component tree is build (initial state, delta,
> ...), and setRenderTarget(...) is used as a flag to indicate how the
> current partial request must be rendered. Add a resource could cause the
> flag to be activated, but not every call to addComponentResource(...) cause
> the flag to be activated.
>
> Maybe a better name could be setRenderTargetComponent(...)
>
> getComponentsToRender(...) returns the list of the components bound to an
> specific target that needs to be rendered on the current ajax request. in
> comparison UIViewRoot.getComponentResources(...) return all components
> bound to an specific target in the view.
>
> Maybe a better name could be getTargetComponentsToRender(...)
>
> These two methods are in fact more bound to UIViewRoot than to
> FacesContext, but the data they manage is "transient", which means the data
> is discarded at the end of the request.
>
>
>> I also don't understand "You can't use an eval block because the
>> encoding does not work correctly". Which encoding exactly is
>> problematic? (there's at least character encoding, html encoding and
>> javascript encoding involved). Why couldn't that part be fixed instead?
>>
>>
> If you have a block like this:
>
> <h:outputScript target="head">
> script2 = function(){
> alert("script2");
> }
> </h:outputScript>
>
> you could try to do in code something like this:
>
> prwriter.startEval(....);
> renderChildren(...);
> prwriter.endEval();
>
> I tried but it doesn't work because you need to encode it properly, but
> since there is a ResponseWriter outside of your control, you can't
> encapsulate the script without do some nasty hacks over
> ResponseWriter/PartialResponseWriter.
>
> In this case eval(...) does not work, so we need to attach the node (well,
> if we want to fix this use case, which I think we should).
>
> regards,
>
> Leonardo Uribe
>
>
>> Cheers, B
>>
>> On Thu, Jun 23, 2016 at 2:45 PM, Leonardo Uribe <leonardo.uribe_at_irian.at>
>> wrote:
>>
>>> 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
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>
>>
>