Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #101
See Also: Dr.Bob's Delphi Papers and Columns

This article was first published in the The Delphi Magazine issue #102 (February, 2004).

IntraWeb and ActiveForms
In this article, I'll demonstrate the use of two powerful web technologies: IntraWeb as well as ActiveForms, and especially focus on the fact that the sum of these two technologies can be greater than the sum of the parts when used (stand-)alone.

IntraWeb 5.1
One of the new enhancements of Delphi 7 was IntraWeb version 5.Realistically, that wasn't an improvement of Delphi 7 itself, but rather the inclusion of the third-party tool IntraWeb from AToZedSoftware.Early 2003, IntraWeb version 5.1 was released, with the latest subversion 5.1.30 being released in the summer of 2003.This was the last free upgrade for Delphi 7 developers, but is still good enough for me.So although IntraWeb is now at version 7, I must admit I never moved past 5.1 and still use the latest IntraWeb version 5.1.30 for my Delphi 7 web server applications.
Apart from being a developer and writer, I also give training using Delphi 7 and IntraWeb, and one of my clients had a problem that took me a little while to solve.The problem is a generic problem regarding web server applications, but has an elegant solution when using IntraWeb, so I decided to write an article about it.

The Browsers' Problem
The problem my client encountered was the fact that a web server application runs inside a web browser, and has very limited access to the resources available on the client machine.Specifically, the web server application has no way to access the local printers from the client workstation, nor does it have access to the local disk (in case you want to store the contents of a ClientDataSet for example).My client had a particular problem with barcode printers that had to be controlled directly from the client application, and it looked like the IntraWeb application was unable to do that.An IntraWeb application runs on the web server, and responds to actions in the browser by executing event handler code - at the web server.As such, the IntraWeb application itself doesn't see anything on the client machine.Fortunately, there is a way to overcome this limitation by integrating the IntraWeb application with an ActiveX control that has access to the client machine - in our case a Delphi ActiveForm.

The Solution
To cut a long story short and get to the point: let's build the IntraWeb and ActiveForm application so you can see how to allow IntraWeb web server applications (or weblications as they call it) access to your client machines.
Note that I'm using IntraWeb 5.1.30 for this example, although you can also use later versions of IntraWeb to play along.Start a new IntraWeb application, and make sure to include a Data Module since I want to use a database table as well.I've selected a standalone application and saved the project as IWX.dpr, but feel free to select any target you want to use.
On the IntraWeb Application Form, drop a TTable component and assign its DatabaseName to DBDEMOS and TableName to biolife.db.The idea is to send a report made out of the Category, Common Name, Length in centimeters, and Notes (memo) fields from the biolife table to the local printer, without using the browser's limited printing abilities (which only prints the client area of the browser, and certainly not the output I want).
Drop a TDataSource component and connect it to the TTable.Then, drop a TIWDBGrid and TIWDBNavigator on the IW Application Form, and connect their DataSource property to the TDataSource component.Finally, you may want to drop a TIWDBMemo component and connect it to the TDataSource and the Notes field.This should produce something like this:

IntraWeb Application Form

Note that I left some space at the bottom of the Application Form, since that's the area where I want to include a print and setup (printer) button using an integrated ActiveForm.

The ActiveForm
Let's now use Delphi to build the ActiveForm, so start a new ActiveX Library project (save it in IWAX42), then add an ActiveForm to this library.Both wizards can be found in the ActiveX tab of the Object Repository.Specify IWActiveFormX as name for the ActiveForm, and make sure to check the option to include version information.

New ActiveForm

Drop two buttons on the ActiveForm: one to display the printer setup dialog, and the other one to actually send some output to the printer. You also need a TPrintDialog and a TPrinterSetupDialog component, as well as a TRichEdit component (we can hide this control later, but for now it will be used as "preview" window to show what will be printed).
Set the Alignment of the TRichEdit to alRight, and the Anchors to include Left so the RichEdit control will be fixed in the designed position no matter how big the ActiveForm will be inside the browser.The ActiveForm should not be made too large, as it needs to fit at the bottom of the IntraWeb page.My small version of it can be seen in Figure 3.

ActiveForm Client Area

Now, implement the two OnClick event handlers as shown in Listing 1:

  procedure TIWActiveFormX.btnSetupClick(Sender: TObject);
  begin
    PrinterSetupDialog1.Execute
  end;

  procedure TIWActiveFormX.btnPrintClick(Sender: TObject);
  begin
    if PrintDialog1.Execute then
      RichEdit1.Print('IntraWeb ActiveX Demo')
  end;
We can now use this ActiveForm as a link between the local resources like the local printer and our IntraWeb application, with the RichEdit component acting as the visual buffer. The only missing link is the interface between the IntraWeb application and the RichEdit buffer (you can expect an enduser to click on the Print button, but it's a bit much to also ask the enduser to copy-and-paste the required text inside the RichEdit prior to printing).
In order to allow the IntraWeb application to fire off events and call methods from the ActiveForm, we have to extend the ActiveForm's "interface" to the outside world, which is defined in the Type Library.Extending this Type Library can be done using the Type Library Editor (which can be found in the View menu, by the way).Start the Type Library Editor, and extend the IIWActiveFormX interface.As an example, we can add a method called "TalkToActiveForm" with one argument called Msg of type WideString, see Figure 4.
Of course you may add many more methods to the ActiveForm's interface, depending on your needs.This is just a simple example that passes one string from the IntraWeb form to the ActiveForm.

Type Library Editor

Click on the Refresh Implementation button to update the definition of the IIWActiveFormX interface and the TIWActiveFormX class, and implement the TalkToActiveForm method by copying the Msg argument to the RichEdit, as can be seen in Listing 2:

  procedure TIWActiveFormX.TalkToActiveForm(const Msg: WideString);
  begin
    RichEdit1.Text := Msg
  end;
Now, save the project and compile the ActiveForm. You do not need to register the ActiveX library on your development machine, since that will be done automatically when the ActiveForm is downloaded to the client.But before that happens, you must first deploy it to the location where the clients can get access to it (for example on the same web server that will host the IntraWeb application, although it can be an entirely different machine).Note that while the IntraWeb application will be executed on the web server, the ActiveForm will be downloaded and installed on the client (we'll get to that in a moment) and executed at the client side.This results in a combined solution with one part executing at the server and one part locally at the client.
In order to prepare the deployment of the ActiveForm, you need to start the Web Deployment Options dialog, and fill in some values to deploy the ActiveForm to the current project directory itself, as I've done in Figure 5.

Web Deployment Options

Close this dialog, and use Project | Web Deploy to "deploy" the ActiveForm. This action will give you a warning that the file is in use, since you try to deploy it to the current directory, and when you try to overwrite it you even get an error message - but you can ignore that message.In fact, you only need to do this deployment step once, since all we're after is the contents of the <object>-tag in the generated file IWAX42.htm that contains the classid and codebase information, produced by Delphi (as shown in the snippet of Listing 3).

  <OBJECT
    classid="clsid:015F65DE-9256-45C4-A354-3BEFA5BC7AC8"
    codebase="./IWAX42.ocx#version=1,0,0,0"
    align=center
    hspace=0
    vspace=0>
It's a good thing we only need to do this deployment step only once, by the way, since a bug in Delphi 7 will prevent you from deploying your ActiveForms again once you close and reopen your project. Yes, that's right: when your project is closed, you will lose the ability to call the Project | Web Deploy option.In fact, both Web Deploy and the Web Deployment Options menu entries will be disabled when you reopen the project.The only known fix consists of adding another ActiveForm to your project, and then removing that from your project again.This has the risk of corrupting your type library if you do this too often, so I try to avoid that as much as possible.Again, for the integration of the ActiveForm with IntraWeb, we only need the IWAX42.ocx file that is the result of the compilation, and - only once - the reference from the generated IWAX42.htm file.And for that the first deployment action is enough.
Anyway, with the classid and codebase information from the generated IWAX42.htm file, we only need to return to the IntraWeb project and drop a TIWActiveX control on the TIWAppForm.We can use the classid value for the ClassID property, including the clsid: part of the string, so that's clsid:015F65DE-9256-45C4-A354-3BEFA5BC7AC8 in my case.We also can use the codebase value for the CodeBase property of the TIWActiveX control.In that case, we should also prefix the codebase with the fully qualified URL of the place where the ActiveForm will be made available on the internet.For the example of this article, that's http://www.eBob42.com/cgi-bin/IWAX42.ocx#version=1,0,0,0.Note that I've included the version number as well, which is needed to ensure that a new version of the ActiveForm will be downloaded automatically (when the specified version in CodeBase is higher than the actual version in the IWAX42.ocx file that may already be available on the client machine - as result of an earlier download).
With the values for the classid and codebase specified, you can compile and run the IntraWeb application, and if everything worked out right you'll see the ActiveForm inside your IntraWeb application, as can be seen in Figure 6.

ActiveForm inside IntraWeb web page

ActiveForms and Security
If you try to use the CodeBase value that refers to the ActiveForm on my website - at http://www.eBob42.com - you most likely only get a red cross in the browser, and a message from Internet Explorer that your current security settings prohibit running ActiveX controls in the current webpage.This is caused by the fact that I didn't digitally sign my ActiveForm.In order to solve this problem, you need to use digitally code signing like authenticode from Microsoft (or codesign from verisign) to sign your ActiveForm.Alternately, you may lower the security setting for your internet connection, by making sure that unsigned ActiveX controls are not disabled (as is done by default), but merely "prompted" (so you will get a confirmation prompt if you try to download the IWAX42.ocx from my website).
Note that because we cannot deploy the ActiveForm after we reopen the project, the version information won't be increased automatically.As a result, we need to manually update the version information in the ActiveForm, using the Version Info tab of the Project Options dialog.And we also need to manually update the version information tag in the CodeBase property of the TIWActiveX component.In short: when the ActiveForm is changed, you need to make sure to update the version information, and also need to recompile the IntraWeb application.

Talking to the ActiveForm
Once you get the ActiveForm displayed in the IntraWeb web page, we still need to implement the actual communication with the ActiveForm, by calling the TalkToActiveForm method in JavaScript code that should be executed inside the browser, as opposed to Delphi event handler implementations that are executed at the server side.
As first way to do that, you can drop a TIWButton component, set the Caption to something like "Prepare for Printing" and assign one line of JavaScript code to the onClick event handler using the ScriptEvents property editor (please be aware that the component name IWACTIVEX1 is case-sensitive, but the TalkeToActiveForm method name is not):

Calling the TalkToActiveForm method

Note that you do not have to implement the TIWButton's actual OnClick event handler using Delphi code. In fact, you should explicitly not implement the OnClick event handler, so no round-trip traffic will happen between the client and the server when you click on this button.
When you recompile and run the IntraWeb application, you can see that a click on the new button will place the text "This is a test" inside the RichEdit (as the result of the call to TalkToActiveForm).This technique was enough to give my client a blueprint of the architecture they needed to enable their IntraWeb applications to connect to the local printers.However, I want to take things a little bit further now.

Talking Datasets
An even more flexible method is to recreate the onClick (JavaScript) event handler for the button on each OnRender event handler of the IntraWeb page (i.e.when we have navigated to another record for example), using the code from Listing 4.This will automatically place a new piece of JavaScript code in the onClick event handler for the button.

  procedure TIWForm1.IWAppFormRender(Sender: TObject);

    function Escape(Str: String): String;
    var
      i: Integer;
    begin
      for i:=1 to Length(Str) do
      begin
        if (Str[i] = #13) and (i < Length(Str)) and (Str[i+1] = #10) then
        begin
          Str[i] := '\';
          Str[i+1] := 'n'
        end;
        if Str[i] < #32 then Str[i] := #32;
        if Str[i] = '"' then Str[i] := ''''
      end;
      Result := Str
    end;

  begin
    IWButton1.ScriptEvents.Clear;
    with IWButton1.ScriptEvents.Add('onClick') do
    begin
      EventCode.Clear;
      EventCode.Add('IWACTIVEX1.TalkToActiveForm("' +
        'Category: ' + Table1.FieldByName('Category').AsString + '\n' +
        'Common Name: ' + Table1.FieldByName('Common_Name').AsString + '\n' +
        'Length (cm): ' + Table1.FieldByName('Length (cm)').AsString + '\n\n' +
        'Notes: ' + Escape(Table1.FieldByName('Notes').AsString) + '");');
    end
  end;
The generated JavaScript code will place the current value of the Category, Common_Name, Length (cm), and Notes field inside the RichEdit of the ActiveForm every time we click on the IWButton. Note that I had to write an Escape method in order to make sure the double-quotes and carriage return and line feeds inside the Notes fields are replaced by JavaScript friendly versions.Otherwise we'd get a JavaScript error when trying to load the IntraWeb page.
The result of this dynamic JavaScript event handler is that every time when you move from one record to another (or make another "trip" from the client to the server) the button will get a new onClick JavaScript scripting code behind itself that - when clicked upon - will pass the current values of the specified fields to the TalkToActiveForm method in order to load it into the RichEdit.
This feels close to what I wanted, but I'm not satisfied, yet.I wouldn't want my users to have to click on this "Prepare for Printing" button (on the IntraWeb form) before they could click on the actual Print button (on the ActiveForm).So I tried to find a way to automatically click on the prepare for printing button.Adding the JavaScript code from Listing 4 to the JavaScript or JavaScriptOnce property of the IntraWeb form does have the effect that the JavaScript code is executed when the page is loaded, but before the controls are initialised, which means that IWACTIVEX1 is unknown at that time.And since I couldn't find a way to add some code to the onLoad event of the IntraWeb form, I looked at some other script events of the IWButton component.The onLoad doesn't fire, but the onFocus does.And it even fires automatically if you point the ActiveControl property of the IntraWeb form to the IWButton control.In fact, we can do that inside the IWDBMemo control as well, so the final solution doesn't use an additional IWButton component but adds the JavaScript code directly to the IWDBMemo control and sets this control as ActiveControl of the IntraWeb form.
  procedure TIWForm1.IWAppFormRender(Sender: TObject);

    function Escape(Str: String): String;
    var
      i: Integer;
    begin
      for i:=1 to Length(Str) do
      begin
        if (Str[i] = #13) and (i < Length(Str)) and (Str[i+1] = #10) then
        begin
          Str[i] := '\';
          Str[i+1] := 'n'
        end;
        if Str[i] < #32 then Str[i] := #32;
        if Str[i] = '"' then Str[i] := ''''
      end;
      Result := Str
    end;

  begin
    IWDBMemo1.ScriptEvents.Clear;
    with IWDBMemo1.ScriptEvents.Add('onFocus') do
    begin
      EventCode.Clear;
      EventCode.Add('IWACTIVEX1.TalkToActiveForm("' +
        'My Category = ' + Table1.FieldByName('Category').AsString + '\n' +
        'Common Name = ' + Table1.FieldByName('Common_Name').AsString + '\n' +
        'Length (cm) = ' + Table1.FieldByName('Length (cm)').AsString + '\n\n' +
        'Notes: ' + Escape(Table1.FieldByName('Notes').AsString) + '");');
    end
  end;
Figure 8 shows the application in action (note that the Prepare for Printing button is still present but is no longer used or required, since I use the onFocus event of the TIWDBMemo control instead).

IntraWeb page with ActiveForm

The result is now exactly what I want: every time when I navigate from one record to another, the page is reloaded and the focus set to the IWDBMemo control which results in the TalkToActiveForm method being called to send the data to the ActiveForm, after which I can click on the Print or Setup buttons on the ActiveForm to print the contents of the RichEdit. Which, by the way, is still visible, but can now be made hidden if you want, since it's only purpose is to act as a placeholder.
The ActiveForm can use all local resources available to the client machine, not necessarily limited to the local printers (you can also use this technique to store data or settings for your IntraWeb application on your local machine for example).Remember that the ActiveForm is not limited to your browser, and that ActiveForms in general are considered a potential security threat since they execute freely on your client machine.However, for this specific purpose, there is a benefit to using ActiveForms, although my feeling is that the specific need (to allow a "trusted" ActiveForm to work with local resources) may only be required in an intranet or extranet environment, and is less desirable in a full internet environment.Nevertheless, the combined power of an ActiveForm and IntraWeb page is more than what either of these technique could accomplish on its own.

An updated version of this article, using Delphi 2007 and IntraWeb 9.0.x is incorporated in my Delphi 2007 for Win32 Web Development courseware manual, available for sale in PDF format (including updates and support for a period of 12 months) and soon also in printed format from Lulu.com


This webpage © 2008-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com).All Rights Reserved.