Adding elements with th: annotation in custom processor

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

Adding elements with th: annotation in custom processor

pfeigl
Hi again, hopefully someone can answer this question, because it's currently driving me crazy.

I added a custom processor which allows me to define my:bbcode on textareas.
What I do there is, that I find the reference to the document <head> and insert some smallish javascript and CSS into it. This works fine, however the inserted code does support any th: annotations.

I need this to resolve URL's.

I tried it using head.addChild(new Text("<script th:src=....>")) and also tried with addChild(new Element(script)).

The first one does not throw an error, but simply outputs the th: attributes as they are.
The second one triggers an exception at the end of the rendering, which states "dialect prefix "th" is set as non-lenient".

I tried also setting the precedence of my processor to a very high and a very low level, just to make sure.
I also did setRecomputeProcessorsImmediately on the added element and the head, but neither helped.

So my final question is:
How can I achieve to add HTML into the document and get it processed by the standard processors?

Thanks,
Philipp
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

pfeigl
Hi, I'm really stuck on this and I would need some support if possible.

Thanks!
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

danielfernandez
Administrator
Hi!

The processors to be applied to each of the nodes in a parsed template are precomputed at the moment of parsing, which allows a much faster execution of cached templates.

So, if you are adding attributes/tags/whatever that need to be processed, you need to signal somehow to the template engine that you want a specific node (and its children) to have its applicable processors recomputed.

This is done by setting "setRecomputeProcessorsImmediately(true)" on the nodes you are modifying (the ones to which you are adding "th:*" attributes.

See the javadoc here:

http://www.thymeleaf.org/apidocs/thymeleaf/2.0.11/org/thymeleaf/dom/Node.html#setRecomputeProcessorsImmediately(boolean)

Hope this helps!

Regards,
Daniel.
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

pfeigl
Hmm thanks for the answer, however I tried this already.
I debugged myself a little more into thymeleaf itself now and I think I found the problem, but have no real solution for it.

What I tried was to add something into <head />. However head has been processed already at the point, where I process the element (which is obviously in body, which is again a sibbling of head).

Thus while the loop runs over all childs of the document and processes them, head is already done processing and while I can add something into the DOM, it is not going to be processed at all, because the iterator is already past that point.

I created a very basic sample to confirm my observerations and it turned out to be exactly that way:

<div id="before"></div>
<div my:attr=""></div>
<div id="after"></div>

Now when running the processor for my:attr and adding HTML into "before", it is not evaluated.
Adding something into the current node or one of the next sibblings (in this case "after"), they are evaluated.

I tried setting setRecomputeProcessorsImmediately on any concerned node, but that didn't really change anything.

Do you think, there is a way to get this problem addressed? Can I re-trigger the whole processing for the <head> node a second time easily?

Thanks,
Philipp
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

Emanuel
Administrator
I've also found that Thymeleaf behaves much like you've discovered, sort of a 'forwards only' through the document such that you can't really reprocess elements once you've passed them.  I did manage to find some ways to get Thymeleaf to reprocess previously-processed elements though.

One trick is to emulate the behaviour of the th:substituteby attribute.  That one works by adding child elements to the element being processed, then using the node.extractChild() method to remove the currently-processing element, bringing that element's children up a level in the DOM.  Thymeleaf spots such a change on the element (since it is processing in that area already) and reprocesses it.

Say we have the 3 divs like in your post above.  Then, in the code of your my:attr processor, there's this:

element.removeAttribute(attributeName);

Element newdiv = new Element("div");
newdiv.setAttribute("id", "before-new");
newdiv.setAttribute("th:text", "'test'");
Element before = element.getParent().getElementChildren().get(0);
before.addChild(newdiv);
before.getParent().extractChild(before);

return ProcessorResult.OK;

The above will add a new element <div id="before-new th:text="'test'"></div> to the DOM, as a child of the <div id="before"></div> element.  It'll then use the extractChild method to pull "before" out of the DOM, promoting "before-new" into the same level as the div with my:attr in it.  Because these elements all share a parent that is undergoing processing, Thymeleaf will notice a change to the processors for its children and reprocess "before-new" (resulting in putting "test" into the div because of the th:text attribute).

Now this only works in some situations.  It's hard to explain, but I think the extractChild() method only seems to notify the immediate parent that a change has taken place, so the parent has to also be within the scope at which the processing is currently at.  So the example above will work because "before" notified its parent, which is also the parent of the element with the "my:attr" attribute which is in mid-processing.  The following won't work:

<div id="parent-of-before-and-myattr">
  <div id="parent-of-before">
    <div id="before"></div>
  </div>
  <div my:attr=""></div>
</div>

If you tried that trick on "before", it wouldn't work because the immediate parent of "before" is "parent-of-before", which has already been processed.

To make this work you'd have to use the extract trick over the entire "parent-of-before-and-myattr" element, probably cloning the element so that you copy everything you need.  And if you want to add new elements to the <head> of the document, then you need to find the 'nearest' common parent of the <head> element and the element currently being processed, which is the <html> tag!

Here's something that'll add a <script th:text="'var test = null;'"/> to the <head> element from your "my:attr" processor which is somewhere in <body>, by cloning and extracting the <html> element:

element.removeAttribute(attributeName);

Element html = arguments.getDocument().getFirstElementChild();
Element htmlclone = (Element)html.cloneNode(null, false);

Element newscript = new Element("script");
newscript.setAttribute("th:text", "'var test = null;'");
Element head = htmlclone.getFirstElementChild();
head.addChild(newscript);

html.clearChildren();
html.addChild(htmlclone);
html.getParent().extractChild(html);

return ProcessingResult.OK;
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

pfeigl
Thanks for the detailed tricks, I'm useing those now, even while I don't really like them too much, because it actually means to re-run through the whole DOM again. Anyways, thats fine for now.

I also invested alot time today debugging into thymeleaf itself. There is a while which iterates over all children of a node until all of them are processed. In there for testing, I added a check not only whether the child has been processed but also, whether setRecomputeProcessorsImmediately is not set to true (actually to be honest, this is what I would expect from this property anyways).

This way I could achieve to get also nodes recomputed, if they had already been processed (again in my opinion, that's how it should work anyways ;) )
However after applying this change, I ran into multiple different problems (where some of them might either be the wanted behavior or just defects).
1. removeAttribute does not actually remove the attribute from elements, it just shifts all upcoming elements one to the left side and in addition does attributesLen--
Later, when an element is re-processed (through my applied change), it will not be able to identify the correct value for the attribute, because it iterates based on attributesLen (which has been decremented), but the old attribute is still there, if it was the last one (example: <link rel="" th:href="">, when the left-shift from remove is now applied, the th:href is still there.

I got this problem addressed by effectively removing the attribute from the Attributes[] array.
However I ran into the 2nd problem than:

2. processors applied to an element are never dropped, also not after execution. I have the feeling, that this is somehow intended, but it gives you various problems. For example my stylesheet link had the th:href processed and also removeAttribute has been called (with my changes) and really removed the attribute.
Later on re-processing the element, the processor was again called for the element, but the th:href attribute was no longer there - so an exception was raised at some point.


So long story short, at this point, I actually gave up now, it costs me too much time, but perhaps it helps daniel to get real re-processing of already processed elements done.

I also saw, there was an local "IdentityCounter<ProcessorAndContext> alreadyExecuted", which kept track of which processors have been applied already. It might be chance to make this not local to the current process action, but known to the "whole document processing". This way processors would not be applied twice.

Allright, I'll leave it from here. In the end - to be honest - the fact that setting setRecomputeProcessorsImmediately(true) on already processed elements does not work is a defect in my eyes, should I log this?

Regards,
Philipp
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

danielfernandez
Administrator
Hi,

Philipp - Thanks a lot for all the time you took debugging Thymeleaf. Your comments are really useful!

As for the behaviours you describe -- they are intended. Currently Thymeleaf does not have a way to execute a second-pass on the tree of nodes, it's only one traversal of the node tree executing all the processors and that's it. When I told you about this flag I didn't realise you were talking about already-processed nodes.

The "recomputeProcessorsImmediately" only makes sense for either the specific node that is being processed or any nodes that still haven't. The most common case is the former, for example, when you are executing a "th:attr" attribute, which might be adding more processable attributes to the node it lives in.

The behaviour you say you'd expect from that "recomputeProcessorsImmediately" (marking already-processed nodes for re-processing) is something I already had as a task for 2.1 --I defined it as a flag called "dirty"--. Most of the tasks I have for 2.1 are still at my tadalist --I still haven't migrated them to github-- so I have just added this one to https://github.com/thymeleaf/thymeleaf/issues/42 so that you can follow it and comment on it if you wish.

Your comment about removing executed processors from the list in case a second-pass is executed is very useful, and in fact it is something I will have to care about when implementing this feature.

Regards,
Daniel.
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

pfeigl
Great to hear you are actually working on this! I signed up for the ticket over at github and am eager to see how it turns out in the future!

Thanks two you both for providing a future and current solution to the problem :)
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

Priya
In reply to this post by pfeigl
Hi ..
I'm using thymeleaf and springs
Can anyone let me know how to get the value of the checked boxes in my controller.
any example for same?
Reply | Threaded
Open this post in threaded view
|

Re: Adding elements with th: annotation in custom processor

Emanuel
Administrator
Your question seems unrelated to this thread.  Please post a new topic with your question and we might be able to help there.