Creating sibling div elements at runtime

I'm having trouble creating sibling div elements at runtime and I'm not sure if it's me or a bug. The idea is to have multiple div elements, each of which hosts a form with the form visibility controlled by setting the Visible property of each div. This works fine if I create the divs at design time - I get a number of sibling div elements and they behave as expected re visibility.

I've attached a test project with an attempt at runtime creation. My first attempt used result.Parent := FormsDiv in CreateForm to set the new div parent, but this resulted in a hierarchy of div elements, rather than siblings. I then tried Javascript but I get a curious effect where the first form div moves from a child of "forms_div" to a sibling then back again during the second call to CreateForm - you can follow this in the debugger. The result is then the same hierarchy of div elements, rather than a group of siblings.

Using 2.1 beta.

Thanks, Bob

Forms Test 2.zip (8.8 KB)

I'm not sure how you expect this to work.
You set WebHTMLDiv.ElementID = parentDivId but the ID's you use are not in your main HTML file, so this mapping onto a HTML fails because the element with that ID doesn't exist.
If you want to use WebHTMLDiv.ElementID to map onto a template HTML element, make sure to add that element to your template.

Hi Bruno - I don't want to map it onto a template HTML element - I want to create everything at runtime. It works fine for the first form - the runtime-created "form1_parent" WebHTMLDiv is correctly created as a child of the pre-existing "forms_div" WebHTMLDiv and the form (Form1) is displayed correctly.

The Form2 parent div ("form2_parent") is also created as a child of "forms_div" and displayed - but it ends up being the parent of Form1 and I want them to be siblings. If I click on the "Form 1" button again then neither form is visible because the Form2 parent div is hidden and Form1 is it's child. If I click on "Form 2" again then Form2 becomes visible since it's the first child.

The console output shows that, after creating "form1_parent", the "forms_div" has a single child element at the point CreateForm is called for the second time, but after the call to result.ElementID := parentDivId the childElementCount = 0. This in turn means that I can't select the correct Javascript call.

For the type of app I'm building it would be impossible to pre-define all of the required div containers.

Thanks, Bob

If you do not want to map the WebHTMLDiv onto a HTML element, you should NOT use ElementID.
ElementID purpose is exactly to map it onto a HTML element in the template.

This shows how you can create two WebHTMLDiv's as sibbling elements in the BODY, give it your own custom ID (instead of the auto created ID) and then manage its visibility from another button. You could then use these created WebHTMLDiv's to host your forms.

unit Unit1;

interface

uses
  System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
  WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.WebCtrls;

type
  TForm1 = class(TWebForm)
    WebButton1: TWebButton;
    WebButton2: TWebButton;
    procedure WebButton1Click(Sender: TObject);
    procedure WebButton2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    dv1,dv2: TWebHTMLDiv;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.WebButton1Click(Sender: TObject);
begin
  dv1 := TWebHTMLDiv.Create(Self);
  dv1.Parent := Self;
  dv1.ElementHandle.setAttribute('id','mydiv1');
  dv1.ElementHandle.innerHTML := 'This is DIV 1';
  dv1.Top := 100;

  dv2 := TWebHTMLDiv.Create(Self);
  dv2.Parent := Self;
  dv2.ElementHandle.setAttribute('id','mydiv2');
  dv2.ElementHandle.innerHTML := 'This is DIV 2';
  dv2.Top := 200;

end;

procedure TForm1.WebButton2Click(Sender: TObject);
begin
  if dv1.Visible then
  begin
    dv1.Visible := false;
    dv2.Visible := true;
  end
  else
  begin
    dv1.Visible := true;
    dv2.Visible := false;
  end;

end;

end.

That's not at all clear from the documentation. I'd have thought a more logical pattern would be:

  1. No ElementID specified: WebCore generates a unique one - current behaviour.
  2. A matching ID is present in the HTML: Link as in the current behaviour.
  3. ElementID is specified, no matching element in HTML: Use for runtime-created elements.

This is especially true given that setting ElementID at runtime does in fact set the expected id in the DOM. If situation 3 is encountered why not just set the attribute internally for the user when they set ElementID, rather than have to use ElementHandle?

Thanks, Bob

I have explained the current documented behavior explained in https://download.tmssoftware.com/download/manuals/TMSWEBCoreDevGuide.pdf
page 80.
So, please use the behavior available that I have also demonstrated with sample code.

Thanks Bruno - not wanting to argue but... :-) I've been aware of this documentation but the fact that situation 3 does not apply and should not be used is not mentioned. Anyway, I'll use SetAttribute for now - thanks.

Cheers, Bob

Everyone should also keep in mind there are many ways to do things! I use the ElementID all the time and have absolutely no use for templates. I didn't get a chance to look at your code, but you can certainly create elements of all kinds at runtime, mapped to proper Delphi TWebHTMLDiv components, according to whatever structure you like. Sometimes a bit of work to puzzle through, but certainly doable.

Thanks Andrew - yes I've been successfully setting ElementID at runtime up until now and it's worked a treat. I tend to create all my controls at runtime and it seemed perfectly logical. However in this situation the setting of ElementID appears to have messed up the DOM hierarchy.

Cheers, Bob

Shouldn't really happen, but if it does, can't really argue that point :smirk: Main troubles that I come across are ...

  1. ElementID must be unique in the page as a whole.
  2. I try to assign ElementID to everything on the Delphi side where feasible. I don't much like the autoassigned versions and run into trouble with that approach when using other JS libraries.
  3. In pure JS/HTML, creating elements dynamically usually means creating them and using appendChild to move them about. In Delphi, the same logic applies but it is implemented by assigning certain properties.

I might have a look at your code, maybe tomorrow, as I'm curious what you're up to here. I did write up something that might be related awhile back, not the dynamic part but the multi-form part, if you've not run across it in your travels already. Form management to speedup app - #9 by AndrewSimard

Thanks Andrew - yes I'm familiar with your excellent blog and forum posts - they've been very, very useful. Re your point 2, this sounds like what Bruno says shouldn't be done.

If you get a chance to look at the code, I add the first runtime div to the main parent div using appendChild. For the second div I first try and check if there's an existing child with the intention of using insertAdjacentElement to create a sibling. If you use the debugger and inspect the DOM at each step as the second form/div is added, you should see that the first runtime div becomes a sibling of the parent div, then eventually a child of the second div! This appears to be caused by the setting of ElementID.

Cheers, Bob

I will take a look. I recall one of the earlier blog posts involved creating hundreds of components all at runtime. And I messed around quite a bit with whether things were children or siblings. Been awhile though, so I'll have to be a little more awake before I try and look at someone else's code :slight_smile:

(Sometimes my blog posts start out with a topic in mind but end up veering off into something else entirely, sort of like the end of this post - there's a live link to the so-called "Sample Project" where you can add things like TWebHTMLDiv components to the work area, or to a TWebHTMLDiv already on the work area, that kind of thing...)

This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.