Introduction
On some occasions when using a TWebBrowser I’ve needed to use Delphi to call JavaScript functions contained in the current document.
This is quite easy to do. We’ll first examine the techniques then we’ll look at a case study that changes the font in an HTML document.
Finally, thanks to contributions from Christian Sciberras, we will look at how to get the return value from a JavaScript function called from Delphi.
Overview of Solution
The window object associated with an HTML document exposes a method – execScript – that enables JavaScript to be called. The first parameter of this method takes a string containing the required function call (complete with actual parameters). The method’s second parameter specifies the script language being used – in our case 'JavaScript'.
So, how do we get hold of the window object that exposes the required method? We simply use the parentWindow property of the web browser control’s document object. We need to ensure that a document object is available by first loading the required document into the web browser and waiting for it to finish loading.
Implementing the Solution
Let us assume we have a TWebBrowser control on a form into which an HTML document has been loaded. Assume also that the HTML document defines a JavaScript function named foo() that takes a string and an integer parameter. Assuming the document is fully loaded, we can call foo() with a method similar to this:
Let’s look at what’s going on here. We first check that a document is available and store a reference to in Doc. We then attempt to get a reference to the document’s parent window object and store it in HTMLWindow. Once we have a valid window object we call its execScript method, passing the call to foo(). The parameters to foo() must be literal values – we can’t pass variables. String literals must be enclosed by double or single quotes.
He didn't say "hello" to a JavaScript function. If we delimit the string with double quotes we pass the it as "He didn't say \"hello\"".'He didn\'t say "hello"'.If the JavaScript function contains errors or doesn’t exist an exception will be raised. We may wish to handle such exceptions before returning.
Case Study
In this case study we will develop a small application that displays an HTML document in a browser window. The document will contain a Javascript function – SetFont() – that can change document’s default font. The application will display a combo box containing a list of all installed screen fonts. When the user selects a font from the combo box the font used in the web browser will be changed accordingly. We do this by calling SetFont() from Delphi.
Firstly, create an HTML document with any content you choose and include the following JavaScript function in the document’s <head> section:
This is the code that changes the font.
Now create a new delphi application and drop a TComboBox and a TWebBrowser on the main form. We will load the HTML document when the form is first shown. We will also load the screen fonts into the combo box at the same time. To do this create an OnShow event handler for the form with the following code:
Note that we disabled the combo box to prevent a font from being selected. We do this because to set a font we need to access the browser’s Document object – and this object is only available when the HTML document is fully loaded. TWebBrowser triggers an OnDocumentComplete event when the document has finished loading. Therefore we handle that event and enable the combo box:
We now come to the code that actually calls the JavaScript function. When the user selects a new font the combo box triggers its OnChange event. We handle this event by calling the JavaScript SetFont() function with the name of the selected font, as follows:
This method is very similar to that described in the previous section. SetFont() is called with the name of the font selected in the combo box. If an exception is raised when the JavaScript is called we display a message.
We have now developed all the code. All that remains is to add the following uses clause to the implementation section of the unit to enable the program to compile:
Getting the Return Value
All this is very well, but how can we grab the return value of a JavaScript function called from Delphi? The short answer is that we can’t, because the execScript() function doesn’t ever return a useful value. Here’s what the Microsoft SDK Help says about it (my emphasis):
- Syntax
HRESULT execScript( BSTR code, BSTR language, VARIANT *pvarRet );- Parameters
- code
- [in] BSTR that specifies the code to be executed.
- language
- [in] BSTR that specifies the language in which the code is executed. The language defaults to Microsoft JScript.
- pvarRet
- [out, retval] Address of a VARIANT of type VT_EMPTY. This method always returns VT_EMPTY.
- Return Value
- Returns S_OK if successful, or an error value otherwise.
Because Delphi uses the safecall convention for the definition of execScript in the MSHTML unit, the return value from execScript is that of the pvarRet parameter (flagged [retval]). (Delphi uses the documented return value to generate an exception if it is not S_OK). Notice that the help entry for pvarRet says it always returns VT_EMPTY. This means that execScript on Delphi always returns and empty variant, not the JavaScript function return value that we may have expected.
However, Christian Sciberras has come up with a clever work-around, providing you can modify the document’s HTML and the JavaScript source code.
Taking our original JavaScript foo() function, let us assume it returns a value we want to capture.
First of all modify your HTML document’s body so that it contains a hidden input field with an id attribute of ‘result’, i.e.:
Assume that foo() was originally coded like this:
We modify it to write its result into the hidden input field’s value attribute like so:
Notice we’ve retained the return statement so that any other code that calls foo() still works. It remains to grab the value of the hidden input field from Delphi. Listing 10 shows a generic function written by Christian that can get a named attribute from an HTML tag with a specified id.
The function first gets a reference to the <body> tag in the loaded HTML document. It then gets hold of a collection of all the contained tags named TagName and searches the collection looking for the tag with the required id (TagId). When the correct tag is found the function returns the value of its TagAtrrib attribute. If the tag or attribute is not found the empty string is returned.
Finally we just need to adapt the code from Listing 1 that calls foo() to gets its return value. Listing 11 shows the revised code.
There are only three changes to the code:
- CallFoo is changed from a procedure to a function that returns a string value.
- A default return value of the empty string is set in case of error.
- The return value of the function is set by calling the GetElementIdValue function from Listing 10.
Demo code
Demo code to accompany this article is available for download (see below). The demo includes the following:
- An implementation of the case study.
- A demo by Christian Sciberras that illustrates getting the return value a JavaScript function call. This demo lets you enter code that will be evaluated and displayed in a “console” window.
The source code was tested using Delphi 7 Professional.
license.txt included in the download for license details.This article is copyright © Peter Johnson 2005-2009