Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
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:
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.
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.
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).
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.
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.
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):
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.
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).
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