Closed jvail closed 7 years ago
Short answer:
For sap.ui.core.HTML
controls, you can avoid DOM preservation, for XMLViews
you currently can't.
Long version: Your observations touch several aspects of UI5 rendering. So it might be best to give an overview about UI5 rendering mechanisms before explaining the preserve mechanism:
First of all, there's the default string-based rendering for all controls: when a control's state is changed via APIs (setting a property, adding / removing something to / from an aggregation), it invalidates itself. The framework collects such invalidated controls and registers them in a kind of "controls that need rendering" collection. In an asynchronous step (setTimeout), the RenderManager
is called for all these controls and executes the following steps
There's some additional handling for the initial rendering of controls. As no old DOM location is known initially, the RenderManager
can't replace the old with the new DOM. Instead, the parent control is involved (rendered) and will render its children in the right order and layout.
To allow controls to react on the creation / destruction of their DOM, the hooks onBeforeRendering
("string rendering is executed, any old DOM might be removed soon, so cleanup any DOM related stuff like event handler registration etc.") and onAfterRendering
("new DOM has been created, please adjust it if needed, e.g. register your event handlers") are available. For Views, the calls to those hooks are propagated to the controller to give views similar capabilities than controls. The hooks are called for each rendering, that's why they might be called multiple times for the same control / view.
A few controls (esp. sap.ui.core.HTML
, sap.ui.core.mvc.XMLView
) allow to embed pure HTML. The HTML
control is often used to integrate some 3rdparty, DOM based (rendering) libraries into UI5. Often, the HTML is heavily modified in onAfterRendering
(or some other, later point in time) and re-doing the DOM modifications in each UI5 re-rendering might be too expensive or otherwise hard to achieve with those libraries. Therefore, the HTML
control introduces the concept of DOM preservation:
sap-ui-preserve
area).The first step and the 2nd and 3rd step can happen at different points in time. Means: an HTML
control can be removed out of the control tree and its current DOM will be preserved. Several events later, the control can be moved back into the control tree, maybe at a different location and the preserved DOM will be restored in the new location (the mechanism is designed to work as long as the page lives, the preserved DOM is not stored in local storage or so). The DOM is only destroyed when the control is destroyed.
Besides the handling of the DOM, an HTML
control participate in the rendering like any other control. So onBeforeRendering
and onAfterRendering
are called as usual, although the DOM did not change. This makes sense, as the work of onAfterRendering
might not only depend on the control's DOM but also on the location where it is embedded (e.g. when dealing with layouting, available space etc.). The HTML control provides an event afterRendering
which even informs about whether this was the first or a later rendering.
The XMLView
also introduced the capability to add pure HTML around and inside UI5 controls (using the xhtml namespace). Therefore it participates in DOM preservation but in contrast to the HTML
control, it can't be switched off for the XMLView
(which I have to admit is a shortcoming).
You might ask (you indeed did), why DOM preservation is necessary at all. Why not leave the DOM alone, not touching it? The problem is with the first rendering variant, string based rendering. When some parent of the HTML
control is modified and doesn't implement DOM patching, then the whole control tree starting with that parent will be re-rendered as a string and the old DOM will be lost, also for the HTML
control.
What to do now?
Depending on what you want to achieve you might just accept the existing behavior (hopefully after getting a better understanding of it now), or you might move to the HTML control (as it allows you to use pure HTML but without preservation) or you might consider to write your own control (should be necessary only for very special use cases, but then please without DOM preservation, it's not a public feature for control development).
Last but not least: this is an explanation of the current state of the rendering. For sure there are other ways of implementing it. There've been experiments with a general DOM patching ( 26309b59934acb56a50155121221bc6c880fb1cf ) using the current renderers. And some other experiments with the use of virtual DOM. However, the huge amount of existing renderer code makes the introduction of new mechanisms quite challenging.
Well, I obviously violated your "a little bit" constraint a little bit, but hope this nevertheless helps to understand why DOM nodes might be moved around.
@codeworrior Many thanks for this thorough description.
<div data-sap-ui-preserve="__html0-container-2" id="__html0-container-2">...</div>
Is there a way to prevent changes to the value of the data-sap-ui-preserve
, because in an aggregation binding this results in loss of preserved content: control bound to data-sap-ui-preserve='...3' will be bound after rerendering(deletion of previous element of the aggregation for example) now to data-sap-ui-preserve='...2' which is big error(html content is basically lost)
Is there a way around this , am i doing something wrong?
I think you have to explain your scenario a bit more. Is the deletion of the previous item in the aggregation done on model level and the list binding updates the content
aggregation of your chart control? Then I could imagine that updateAggregation
changes the relationship between ID and content.
But if you just remove an item via aggregation API, IDs should not change.
I see. I fear, the aggregation binding's standard behavior doesn't work together with the HTML control's constraint reg. the ID / content ID.
There's no such mechanism to decouple data-sap-ui-preserve
and ID.
Is the model>idProp
stable over time for the 'same' chart? If the aggregation would create an HTML control with that ID, this should work out. But the standard updateAggregation
can't do that. Maybe using a factory function could help.
A factory function gets the binding context as 2nd parameter and could determine the idProp
before creating the HTML control with the expected ID. Well, but that's not straight forward.
The aggregation API is good enough for me. I tried doing it with factory, but the result is the same (the content of the core.HTML is the initially provided string, not the preserved DOM) factoryFN:
fnFactory: function (sId, oContext) { const oControl = this.byId("templateItem").clone(sId); try { oControl.setContent( new sap.ui.core.HTML(oContext.getObject("globalId"), { content: "div id='{local>globalId}' /div", }) ); } catch (e) { console.log(e); } return oControl; },
core.HTML DOM node after follow-up re-rendering:
Well, as I said, aggregation binding and HTML control don't work well together:
preferDOM
mechanism is designed to keep the DOM alive as long as the control instance exists AND as long as the content
property is not changedcontent
property is changed (e.g. via data binding), this will replace the existing DOM with the new value of the content
property. If DOM preservation is the goal, the content
property should only be set once and should not be bound.To demonstrate these problems' I've created a JSBin: https://jsbin.com/sewekisono/edit?html,output It implements a modified update strategy that keeps the existing DOM controls alive and reuse them based on the ID in the model. For a productive solution, the cleanup would have to be addressed as well (HTML controls that are no longer needed after deletion need to be deleted explicitly, the JSBin keeps them forever).
Thank you.
Whilst implementing your solution I learned that 0..1 aggregations do not receive the named methods, so I had to instead of
Nevertheless it worked. I still have one question, if you don't mind.
Is it ok to use sap.ui.getCore().applyChanges();
in prod? Because without it the async rendering step takes just a bit too long and my charts 'flicker' in the re-rendering process, which i was hoping to avoid in the first place.
I used sap.ui.getCore().applyChanges() in my JSBin to be sure that rendering already happened before I apply the dynamic HTML.
Using it in a productive app is in general not recommended as sync rendering is a kind of de-optimization. Async rendering collects updates and handles them at once, e.g. avoiding multiple rendering of the same control. Sync rendering - when triggered by multiple components not knowing each other - might cause such redundant rendering or other side effects.
But when you see benefit in your scenario and do this only after deletion of a chart, you can IMO use it, it is a public API.
I have quite a hard time to understand why the RenderManager moves a node to the so called "preserved area". Could you elaborate on this feature a little bit?
The problem is that I am doing some parts of the initialization in onAfterRendering of the controller. I expect that the view is only rendered once i.e. added to the DOM. Unfortunately onAfterRendering is often called twice (e.g. when navigating back): It is also called when a node is moved from the (hidden) "sap-ui-preserve" div to the top container if the view gets visible (upon navigation). So far so good (? - why is it "re-rendered" if it is already in the DOM. It is just invisible) Yet I can not find a rule when and why a node is moved to "sap-ui-preserve" and therefore later "re-rendered". Sometimes the node (the view) stays in the top container sometime s not. Is it possible to force a node NOT to be moved to "sap-ui-preserve"?
OpenUI5 version: 1.38.4
Browser/version (+device/version): Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36