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

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

From: Bauke Scholtz <balusc_at_gmail.com>
Date: Fri, 22 Jul 2016 18:30:11 +0200

Hi,

I posted a comment on
https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1423 with my new
findings and thoughts.

In a nutshell:

1. No substantial changes in API/impl are necessary in order to discover
newly added component resources provided that they are all added via
UIViewRoot#addComponentResource().
2. I would like to (re)use the currently unsupported <update
id="javax.faces.ViewHead"> in jsf.js to perform adding of new component
resources.

Cheers, B


On Mon, Jul 4, 2016 at 8:06 PM, Leonardo Uribe <leonardo.uribe_at_irian.at>
wrote:

> Hi
>
> BS>> As to "render target", isn't PartialViewContext#getRenderIds()
> sufficient
> BS>> for the task? This returns a mutable collection of client IDs which
> need
> BS>> to be updated on current partial request.
>
> I have been thinking about this for some time to see what's missing from
> the
> spec perspective, and the answer is no, getRenderIds() is not enough to do
> the job (in my personal opinion).
>
> The reason is getRenderIds() relies on the fact that it should be a html
> tag
> with an id rendered on the client markup that can be updated. If there is
> no html tag with id set, an ajax update will not work. Please note
> getRenderIds() works for most cases, but for <head> and <body> tags, both
> does not allow an id to be set. It is true that there is a solution
> updating
> the whole content, but the central point is there is a better solution that
> could be done with the changes proposed previously in this thread.
>
> In summary, the current algorithm for ajax has these weaknesses (sorry if
> you found this too repetitive):
>
> 1. Resource components added as part of a partial update requires to
> add some html markup inside a <head> or <body> tag, and both tags does not
> have
> an id set, and the current API doesn't help to do it without replace
> the whole tag.
> 2. If a new component is added, there is no html tag we can use to update
> it.
>
> The first case we have already discussed.
>
> The second one is more theoretical but goes far beyond what we have
> discussed.
> Anyway, just for clarity sake I'm going to describe it fully and give a
> possible solution.
>
> Think about this fragment:
>
> <h:panelGroup id="content" layout="block">
> <c:if test="#{bean.chooseSomething}">
> <h:outputScript name="script2.js"/>
> </c:if>
> <h:commandButton value="Update" actionListener="#{bean.check}">
> <f:ajax />
> </h:commandButton>
> </h:panelGroup>
>
> Now imagine that when an ajax request is triggered, the code inside
> bean.check() changes chooseSomething from false to true. The lifecycle
> looks
> like this:
>
> - Lifecycle Execute (....) invoke bean.check, change chooseSomething to
> true.
> - Call to vdl.buildView() refresh the whole component tree =>
> h:outputScript
> added and PostAddToViewEvent is triggered.
> - f:ajax requires h:commandButton to be rendered, so a partial update over
> the
> commandButton is activated.
>
> What's wrong with this? A new component (h:outputScript) was added on a
> partial request. But the component never had the chance to update itself,
> so
> the script was never updated. Ok, you can replace f:ajax with:
>
> <f:ajax render="content"/>
>
> Problem solved, but is all that really necessary? What if chooseSomething
> does
> not change? why update the content in that case?.
>
> Please note every component addition triggers a PostAddToViewEvent. It is
> possible to use that event to detect the change. Since the component is a
> new
> one, there is no id on the client side, but probably an ancestor could
> have an
> id set. The problem is the current algorithm does not have a standard way
> to
> check whether it has an id set on the client or not.
>
> But if a method like UIComponent.isClientIdRendered(...) exist, we could
> create
> an algorithm that recursively find the parent that needs to be rendered
> and
> include it into the current ajax request adding it to getRenderIds(). In
> that
> sense, the algorithm could automatically find that "content" needs to be
> updated and activate the render automatically.
>
>
> Going back to the main topic, getRenderIds() relies on a logic that for
> each
> id there is an html markup counterpart. setRenderTargetComponent(...)
> and getTargetComponentsToRender(...) are different because the intention
> here
> is to say "... this (resource) component needs to be added for this
> target,
> the way how you want to update the client is up to you ...".
>
> regards,
>
> Leonardo
>
> 2016-07-01 13:01 GMT-05:00 Neil Griffin <neil.griffin_at_portletfaces.org>:
>
>> Kyle Stiemann will also be commenting on the following issue in JIRA:
>> https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1423
>>
>> Again, please click on the "watch" link in JIRA if you are interested.
>> Thanks.
>>
>> > On Jun 27, 2016, at 7:12 AM, Bauke Scholtz <balusc_at_gmail.com> wrote:
>> >
>> > 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
>> >>>>>>>>
>> >>>>>>>
>> >>>>>>
>> >>>>>
>> >>>>
>> >>>
>> >
>> >
>> >
>> >
>> >
>>
>>
>