Finding HTML elements by ID in a TWebBrowser document #56

It's often useful to be able to find HTML elements using the elements' ID attribute, because it is unique in a HTML document.

JavaScript provides a built-in method to do that – document.getElementById. Unfortunately there isn't a similar method available from MSHTML for use with HTML documents loaded into a TWebBrowser. So we need to provide our own. Here's the code:

uses
  MSHTML, SysUtils, Variants;

function GetElementById(const Doc: IDispatch; const Id: string): IDispatch;
var
  Document: IHTMLDocument2;     // IHTMLDocument2 interface of Doc
  Body: IHTMLElement2;          // document body element
  Tags: IHTMLElementCollection; // all tags in document body
  Tag: IHTMLElement;            // a tag in document body
  I: Integer;                   // loops thru tags in document body
begin
  Result := nil;
  // Check for valid document: require IHTMLDocument2 interface to it
  if not Supports(Doc, IHTMLDocument2, Document) then
    raise Exception.Create('Invalid HTML document');
  // Check for valid body element: require IHTMLElement2 interface to it
  if not Supports(Document.body, IHTMLElement2, Body) then
    raise Exception.Create('Can''t find <body> element');
  // Get all tags in body element ('*' => any tag name)
  Tags := Body.getElementsByTagName('*');
  // Scan through all tags in body
  for I := 0 to Pred(Tags.length) do
  begin
    // Get reference to a tag
    Tag := Tags.item(I, EmptyParam) as IHTMLElement;
    // Check tag's id and return it if id matches
    if AnsiSameText(Tag.id, Id) then
    begin
      Result := Tag;
      Break;
    end;
  end;
end;

The routine takes a reference to the IDispatch interface of the web browser control's Document object. It returns either the IDisptach interface of the tag with the required Id or nil if no tag with the required id exists. The return value can be cast to the required interface of the tag, for e.g. IHTMLElement or IHTMLElement2. The comments in the code should explain what's happening.

Example

Let's try this out. First create a small HTML document containing at least one element that has an id attribute. Here's an example that we'll name Test.html and store in the same directory as the test application:

<?xml version="1.0"?>
<!DOCTYPE html
        PUBLIC "//W3C//DTD XHTML 1.0 Strict//EN"
        "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="https://www.w3.org/1999/xhtml/" xml:lang="en" lang="en">
  <head>
    <title>Tip#36 Test</title>
  </head>
  <body>
     <p>Paragraph with no id</p>
     <p id="myid">Paragraph with id = myid</p>
  </body>
</html>

Now create a new Delphi application and drop a TWebBrowser and a TButton on the form. Add the following OnShow event handler to the form to load the test document into the browser:

procedure TForm1.FormShow(Sender: TObject);
begin
  WebBrowser1.Navigate(
    'file:///' + ExtractFilePath(ParamStr(0)) + 'test.html'
  );
end;

Next, add an OnClick event handler for the button:

procedure TForm1.Button1Click(Sender: TObject);
var
  Elem: IHTMLElement;
begin
  Elem := GetElementById(WebBrowser1.Document, 'myid') as IHTMLElement;
  if Assigned(Elem) then
    ShowMessage(
      'Tag name = <' + Elem.tagName + '>'#10 +
      'Tag id = ' + Elem.id + #10 +
      'Tag innerHTML = "' + Elem.innerHTML + '"'
    );
end;

This exercises our GetElementById routine by finding the paragraph element with id = "myid" and displays some information about the element in a message box.

One use for this code is to change the HTML displayed by a block level tag. To illustrate this drop another button on the form and give it the following OnClick event handler:

procedure TForm1.Button2Click(Sender: TObject);
var
  Elem: IHTMLElement;
begin
  Elem := GetElementById(WebBrowser1.Document, 'myid') as IHTMLElement;
  if Assigned(Elem) then
    Elem.innerHTML := 'My new text';
end;

Run the application the click Button1 and note the inner HTML displayed in the message box. Now click Button2 and watch the text of the second paragraph change. If you click Button1 you'll see the inner HTML has changed.

There's lots more you can do with GetElementById to make your documents more dynamic. For example you can change the image displayed by an image element. Experiment!

Author: Peter Johnson
Contributor: Peter Johnson
Added: 2007/10/23
Last updated: 2007/10/29