To get a particular DOM node embedded in the current web document from a TChromium instance, using its ID, you use ICefDomDocument.getElementById(). But how do you find elements by the NAME attribute? Javascript has the document.getElementsByName() method and TWebBrowser (that wraps IE) has a similar call, but I can't figure out how to do this with TChromium. I need to find some DOM elements that have NAME attributes but no ID attributes. I searched the ceflib unit and did not see anything that would do it.
Side question. If anyone has a link to a TChromium "recipes" style site or document I could use it.
UPDATE: While waiting for an answer I have come up with the following code for doing getElementsbyName(). I'd like something faster than scanning the entire DOM tree. If you see something wrong in the code let me know:
type
TDynamicCefDomNodeArray = array of ICefDomNode;
// Given a Chromium document interface reference and a NAME attribute to search for,
// return an array of all DOM nodes whose NAME attribute matches the desired.
function getElementsByName(ADocument: ICefDomDocument; theName: string): TDynamicCefDomNodeArray;
// Get all the elements with a particular NAME attribute value and return
// an array of them.
procedure getElementsByName1(intfParentNode: ICefDomNode; theName: string; var aryResults: TDynamicCefDomNodeArray);
var
oldLen: integer;
intfChildNode: ICefDomNode;
theNameAttr: string;
begin
Result := nil;
intfChildNode := nil;
if Assigned(intfParentNode) then
begin
// Attributes are case insensitive.
theNameAttr := intfParentNode.GetElementAttribute('name');
if AnsiSameText(theNameAttr, theName) then
begin
// Name attribute match. Add it to the results array.
oldLen := Length(aryResults);
SetLength(aryResults, oldLen + 1);
aryResults[oldLen] := intfParentNode;
end; // if AnsiSameText(intfParentNode.Name, theName) then
// Does the parent node have children?
if intfParentNode.HasChildren then
begin
intfChildNode := intfParentNode.FirstChild;
// Scan them.
while Assigned(intfChildNode) do
begin
getElementsByName1(intfChildNode, theName, aryResults);
if Assigned(intfChildNode) then
intfChildNode := intfChildNode.NextSibling;
end;
end; // if intfParentNode.HasChildren then
end; // if Assigned(intfParentNode) then
end;
// ---------------------------------------------------------------
var
intfCefDomNode: ICefDomNode;
begin
intfCefDomNode := nil;
Result := nil;
if Assigned(ADocument) then
begin
// Check the header.
intfCefDomNode := ADocument.Document;
if Assigned(intfCefDomNode) then
begin
// Check the parent.
getElementsByName1(intfCefDomNode, theName, Result);
end; // if Assigned(intfCefDomNode) then
end; // if Assigned(ADocoument) then
end;
// ---------------------------------------------------------------
There is no function like JavaScript's getElementsByName
or MSHTML getElementsByName
built in the Chromium Embedded
nor its Delphi wrapper at this time. You can resolve this only by iterating over all DOM elements, e.g. by creating your own DOM visitor class like this:
Please note the VisitDom
procedure is asynchronous, so it returns immediately (actually before the DOM visitor finishes its visit
) and it works with a snapshot of the DOM at the time it's executed.
type
TElementNameVisitor = class(TCefDomVisitorOwn)
private
FName: string;
protected
procedure visit(const document: ICefDomDocument); override;
public
constructor Create(const AName: string); reintroduce;
end;
procedure ProcessElementsByName(const AFrame: ICefFrame; const AName: string);
var
Visitor: TElementNameVisitor;
begin
if Assigned(AFrame) then
begin
Visitor := TElementNameVisitor.Create(AName);
AFrame.VisitDom(Visitor);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ProcessElementsByName(Chromium1.Browser.MainFrame, 'NameAttributeValue');
end;
{ TDOMElementNameVisitor }
constructor TElementNameVisitor.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
procedure TElementNameVisitor.visit(const document: ICefDomDocument);
procedure ProcessNode(ANode: ICefDomNode);
var
Node: ICefDomNode;
begin
if Assigned(ANode) then
begin
Node := ANode.FirstChild;
while Assigned(Node) do
begin
if Node.GetElementAttribute('name') = FName then
begin
// do what you need with the Node here
ShowMessage(Node.GetElementAttribute('value'));
end;
ProcessNode(Node);
Node := Node.NextSibling;
end;
end;
end;
begin
ProcessNode(document.Body);
end;