5

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;

// ---------------------------------------------------------------
TLama
  • 71,521
  • 15
  • 192
  • 348
Robert Oschler
  • 13,520
  • 17
  • 74
  • 209
  • I don't think it is wise to mix and match 10 year old technology with state of the art stuff and expect it to become a found and stable solution. In this particular case, TChromium does not support Delph 6. http://code.google.com/p/delphichromiumembedded/ – Jeroen Wiert Pluimers May 20 '12 at 09:13
  • @Jeroen, the [`TChromium`](http://code.google.com/p/delphichromiumembedded/) doesn't support Delphi 6 though (there is no package for it), but it doesn't mean it couldn't work there. I have Delphi 2009, which is also unsupported, but looking into source, there's nothing what could inhibit the usage there ;-) – TLama May 20 '12 at 09:51
  • @TLama if my mind serves me well enough, Delphi 7 introduced quite a few fixes relating to wrapping COM stuff. That could be a reason for not supporting Delphi 6. I'd recommend Robert to verify this assumption with the TChromium team. – Jeroen Wiert Pluimers May 20 '12 at 12:17
  • @TLama - I believe so. The three Delphi units I use related to Chromium are: cef, ceflib, ceffilescheme. Also, I have TChromium available in my component palette and can drop it on a form. – Robert Oschler May 20 '12 at 12:55

1 Answers1

3

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;
TLama
  • 71,521
  • 15
  • 192
  • 348
  • 1
    re: reinventing the wheel. Not on purpose, just using what I found when researching using Chromium with Delphi (see my reply to your comment on the main post). – Robert Oschler May 20 '12 at 12:58
  • So I take it that calling visit with a TCefDomVisitorOwn descendant implements the Visitor pattern? In other words, CEF will apply that pattern to all nodes in the DOM, handling the recursive descent through the node tree for you? That's very cool if so but I want to be absolutely sure so I'm asking for confirmation. – Robert Oschler May 20 '12 at 12:59
  • No, the iteration you have to do still by your own. The difference of using the `VisitDom` method is that it makes a copy (a snapshot) of the current DOM state. In my example the `TElementNameVisitor.visit` works with a copy of the document, not with the document itself (which can be changed at the time you iterate). I don't know if it's faster, it's just safer. – TLama May 20 '12 at 13:06
  • 1
    Forgot to notice, the `VisitDom` is asynchronous, that's why I used name `ProcessElementsByName` not `getElementsByName` because I would have to wait until the visitor finishes the `visit`. – TLama May 20 '12 at 13:17
  • Anyway, some time ago has been released [`Delphi Chromium Embedded 3`](http://code.google.com/p/dcef3/). – TLama Sep 23 '12 at 21:18
  • @TLama Is it possible to click this node if its a button? – user3060326 May 09 '14 at 21:03