I have a TWebForm with multple edits and labels to handle user input. I want muliple of this form in a TWebPanel. The user has to be able to dynamically add them by clicking a button but I cannot get it to work properly.
I have the following:
type
TForm1 = class(TWebForm)
WebGridPanel2: TWebGridPanel;
...
end;
procedure TAnotherForm.WebSpeedButton1Click(Sender: TObject);
var
Form1: TForm1;
begin
Form1:= TForm1.Create(self);
Form1.Parent := panelGods;
Form1.Align := alTop;
Form1.Visible := True;
end;
This adds a item to the DOM tree but it is not visible. CreateNew removes all other content and I can't have that either.
Any example or other thread to look at for guidence?
I want to add more than one form. The example only shows how to add one form.
In the example, TForm.CreateNew(elementid, proc) is used. This seems to delete any other content in the panel I specify.
Here are some examples that does not work and why:
Ignoring the fact that I create the list in the procedure, pretent that is part of a class. HostPanel is a TWebPanel.
This will just replace any content in HostPanel.
procedure TForm3.WebButton1Click(Sender: TObject);
var
Forms: TList<TSubForm>;
SubForm: TSubForm;
begin
Forms := Tlist<TSubForm>.Create;
Forms.Add(TSubForm.CreateNew(HostPanel.ElementID, nil));
end;
This will replace any content as well as fail with the error message FMessage: 'Duplicate component name: "WebPanel1".[1]
procedure TForm3.WebButton1Click(Sender: TObject);
var
Forms: TList<TSubForm>;
SubForm: TSubForm;
begin
Forms := Tlist<TSubForm>.Create;
Forms.Add(TSubForm.CreateNew(HostPanel.ElementID, nil));
Forms.Add(TSubForm.CreateNew(HostPanel.ElementID, nil));
end;
This example does not append SubForm to the specified panel nor does it show the component.
procedure TForm3.WebButton1Click(Sender: TObject);
var
Forms: TList<TSubForm>;
SubForm: TSubForm;
begin
Forms := Tlist<TSubForm>.Create;
SubForm := TSubForm.Create(Self);
SubForm.Parent := HostPanel;
SubForm.Align := alTop;
SubForm.Width := HostPanel.Width;
SubForm.Height := 100;
SubForm.Show;
Forms.Add(SubForm);
end;
WebPanel1 is the first field of TSubForm, the second being WebLabel1.The full error message is (the project is named TMSHostPanel): this.Create$1@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:3435:23 this.CreateFmt@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:3441:12 createClass/c.$create@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:337:19 this.ValidateRename@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:15317:188 this.SetName@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:15297:21 this.SetName@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:37396:38 this.LoadDFMValues@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:56699:24 this.CreateNewForm@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:45868:13 this.DoFormLoad@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:45524:12 cb@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:261:28 EventListener.handleEvent*this.LoadForm@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:46320:21 this.CreateNew$5@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:44851:26 this.CreateNew$4@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:44840:12 createClass/c.$create@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:337:19 this.WebButton1Click@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:57448:34 cb@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:245:19 this.Click@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:35159:61 this.Click@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:48430:45 this.HandleDoClick@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:34638:31 cb@http://localhost:8000/TMSHostPanel/TMSHostPanel.js:241:26↩︎
A form (or a control), can only be mapped onto 1 HTML element. When you bind it, it would replace the content of the HTML element it is bound to. If you want to add multiple forms, you should as a minimum insert multiple WebHTMLDiv components in the panel and bind forms to these WebHTMLDiv elements.
There is a 1:1 relationship between a control and a HTML element with an ID linked to Control.ElementID
You cannot link multiple controls (and thus also forms) to the same ElementID
Here is an image of what I want to achieve. I want to create each row by clicking the button, the design is specifed in another unit (I am using a TWebForm at the moment).
Should I first insert a div in WebPanel1 and then .CreateNew on that div? That confuses me because obvioulsy I can create it manually with the GUI builder.
If you're interested, there was a blog miniseries written up that covered some of what you're doing, a small part of a much larger project. In the blog post, TWebHTMLDivs are used to hold dynamically loaded forms.
In a project I'm working on now, I use a TWebPageControl to host the forms on their own pages so I can easily switch between them. Lots of options!
After being able to move seamlessly between Delphi and JavaScript, and being able to use CSS for nearly everything style-related, this is probably my next favorite TMS WEB core feature. Being able to load up an instance of a form whenever and wherever I want inside the app is a really powerful tool.
Thanks Bruno for your help, and thanks Andrew for the guide. It looks like exactly what we need. But I realize that we might need to do some major reworking of our code to do this properly, time which I don't have now but will have in the future.
So for now, I solved this by using JavaScript. If anyone else ends up in this thread, I would recommend them to read Andrews blog post. If you still need a quick and dirty solution:
TL;DR: Copy node with JS.
My setup was a div with the first (and only) child being a TWebPanel with all the layout etc I wanted. I gave the div an elementId to find it via JavaScript.
<script type="text/javascript" defer>
// Save the node you want to copy in window context to make it global.
// As specified, In my case this was the first child of #YOUR_CONTINER_ID
window.originalDOMNode = document.getElementById('YOUR_CONTINER_ID').children[0];
// IF YOU NEED TO DELETE THE NODE VIA BUTTON
// If you need to be able to delete the node from DOM.
// I used a class instead of id to find it because id should be unique and I used a button to which I add a click event.
// The button is a child of originalDOMNode.
window.button = document.getElementsByClassName('CLASS')[0];
// Add a click event to the button
button.addEventListener('click', function() {
// In my case, there should always be atleast one, so I am counting the occurrence of the class on the button.
if (document.getElementsByClassName('CLASS').length > 1) {
// 'this' will be the button, parentElement traverses up until the correct node, removing it removes all descendants as well.
this.parentElement.parentElement.parentElement.parentElement.parentElement.remove();
}
});
</script>
Add a button somewhere else in your form in the GUI builder and add an OnClick event. This event need to run JavaScript with asm end; tags.
procedure TWebForm.YourButton1Click(Sender: TObject);
begin
asm
// Clone the original node.
var clonedNode = window.originalDOMNode .cloneNode(true);
// Add it to the DOM tree as a child to your container node.
document.getElementById('YOUR_ID').appendChild(clonedDiv);
// IF DELETION WAS REQUIRED
// clonedNode is the node just created. Find the button (node to hold click event).
var clonedButton = clonedDiv.querySelector('CLASS');
// Add the click event.
// This code is the same as in the HTML file.
clonedButton.addEventListener('click', function() {
if (document.getElementsByClassName('CLASS').length > 1) {
this.parentElement.parentElement.parentElement.parentElement.parentElement.remove();
}
});
end;
end;
The click event for deletion (or any other use case) with this setup needs to be specified twice. Once in the HTML file and once in the buttons OnClick event in Delphi. This is because the original node will not have the event attached because it is added after copying. And all cloned nodes will not have the event because cloning does not copy attached event listeners.