1 What happens
I have 20+ forms in my project and on them 200+ controls. Every now and then, the carefully created rendering order of the controls gets scrambled. Even if the controls are perfectly ordered in the DFM file and the ChildOrder values are carefully set, it sometimes happens that controls are rendered in the wrong order into the html. If e.g. some controls are aligned alTop to each other, the bug may flip the vertical visual presentation in the browser, making a control with say ChildOrder 5 visually appear on top of the control with ChildOrder 4, which of course should be the other way around, i.e. they are vertically flipped. I was able to trace this problem down to procedure WEBLib.Controls.pas
→ TControl.Loaded
2 Why it happens
In WEBLib.Controls.pas
→ TControl.Loaded
the framework re‑orders children like this:
if (Controls[i].ChildOrder < ChildContainer.childNodes.Length) then
begin
ChildContainer.insertBefore(Controls[i].Container,
ChildContainer.childNodes[Controls[i].ChildOrder]);
end;
When the control must move forward (ChildOrder > current index
) the node is first removed, all later nodes shift –1, and the reference childNodes[ChildOrder]
is now one position too early. The control re‑inserts at ChildOrder – 1
, overtaking its neighbour.
3 Minimal patch (no API change)
Add a one‑slot compensation before calling insertBefore
:
{ inside the loop }
TargetIdx := C.ChildOrder;
TargetNode := C.Container;
// fast path: want a slot beyond current list -> simple append
if TargetIdx >= NodeLen then
begin
ChildContainer.appendChild(TargetNode);
C.Loaded;
Continue;
end;
// slow path: node may need compensation if moving forward
asm
// Find current index of TargetNode inside NodeList
CurIdx = Array.prototype.indexOf.call(NodeList, TargetNode);
// If it moves forward, compensate for the removal
if (CurIdx !== -1 && TargetIdx > CurIdx) { TargetIdx++; }
end;
if CurIdx=-1 then; // compiler honey
// fetch reference node for insertBefore (or nil if append)
if TargetIdx < NodeLen then
RefNode := NodeList.item(TargetIdx)
else
RefNode := nil; // should not happen after the ++ above
if Assigned(RefNode) then
ChildContainer.insertBefore(TargetNode, RefNode)
else
ChildContainer.appendChild(TargetNode);
(Full patched TControl.Loaded
attached separately; the key is adjusting TargetIdx
before insertBefore
.)
TControl.Loaded.pas (3.0 KB)
Credits go to ChatGPT in helping analyzing and fixing this edge case. The provided fix did it for me, but I did not check for any side effects. Also, I was not able to reproduce this problem in a small demonstration project, but the following screen shots of my project demonstrate the issue.
This is how the unpatched Loaded procedure renders my stacked panels. As you can see, the panels with ChildOrders 3 and 4 are wrongly rendered in the order 4-3 (see TWebPanel.Name displayed to the right of the panels).
When applying the fix, the panels are rendered in the correct 3-4 order.