Dual-Dual-Mode Apps

S.H. Parker (c) 1999

Internet Connect is unique in the RAD world in its ability to successfully create dual mode applications. Apps with the IC extensions run correctly on both the net and the Net.

Now, here's a question: Is it that your Windows apps also happen to run on the Internet or is that your Internet apps also happen to run in Windows?

Let's see if we can't really confuse the issue.

IC allows us to create "dual-dual-mode" applications. "Dual-dual-mode" apps not only run on both the net and the Net, but adopt the look and the feel "appropriate" to each medium.

Consider the following two screen shots:


Figure 1


Figure 2

which (of course) come from exactly the same application. In fact, they both show the same procedure. (The current version of the running app is at www.cwicweb.com/apps/c5launch.dll/download.exe.0.)

"It's Easy, Once You Know the Secret"

Regardless of the way you decide to implement the dual-mode look-and-feel (there are three ways which we shall discuss later), the task is composed of two logically distinct parts: the Windows part and the Web part.

It doesn't seem like a good use of your bandwidth to describe how to do the first, shown in Figure 1. So I won't.

Making the Internet part, shown in Figure 2, should be the greater challenge, at least for most of us.

The most important decision you have to make is "What parts of the native Windows app do we need to (or want to) keep?"

Comparing the finished product in Figures 1 and 2 with the same window in Screener:


Figure 3

amply demonstrates this (ghastly, isn't it?).

There's no special trick involved in getting the window and the page to look the way they do -- just lots of Hide and Unhide statements in the INIT method after the window is opened. Stay tuned.

Perhaps it is logically easier to work backwards: What standard Windows components are not consistent with the Internet look-and-feel? And the answer to that question is much simpler: most Windows components don't look Web-ish (rhymes with "nebish," most apt).

List boxes are #1 on the "it don't look Web-ish" list. No matter how functional, IC Java browses boxes violate the look-and-feel criterion. HTML (non-Java) list boxes also do not look especially Web-ish. Even if you think that they do, their functionality is severely restricted. "Bye bye" browse boxes.

In fact, for the sample app, all that remains from the original window are the three Group structures along the top, the Previous Page button, the Next Page button and the Close button (whose text is dynamically changed using a property assignment when running on the Web).

Let's Build a Page

The page, clearly, starts off with a graphic. And, since you do not see it design mode, it must be placed with something like:

Target.Writeln('<<img src="/cwicweb.gif">')

either immediately after the HTML <body> tag is opened or immediately before the HTML is generated for the first Group/control.

The boxed area at the bottom of Figure 2 is (all that is left of) the window from Screener. In fact, it is the window. The inner boxes are just the Groups at the top. Had I turned off the Boxed() attribute and set the page border to zero, even this much might not have been obvious.

In short, to achieve a Web-ish look-and-feel, entry fields, images, buttons, check boxes, radio buttons and file drops are, in fact, just about all that we can keep.

So, the real issue is:

Making an HTML List

The only sort of "list" (in the sense that Clarion developers understand "list") supported by HTML is the <select> structure. This is what Clarion generates if you override the standard Java list class with its non-Java equivalent. Clearly, the "list" area in Figure 2 is not an HTML <select> structure.

Being no HTML expert (in fact, I'm not even a big fan of the Internet), I fired up my handy-dandy-everyone-should-have-one visual HTML editor and drew a <table> that looked like what I wanted:


Figure 4

Notice that I created a multi-line <table>. This ensures that when I view the HTML source, I would be able to find the pattern for placing my database fields.

My HTML editor created the following source (this is the portion of it not already supplied by IC):


Figure 5

which I promptly copied and pasted into a text file in my working directory.

The first four lines add a blank line, set up the alignment needed to fit under the image correctly and begin the HTML table. The next five lines, which repeat as expected, are where the database fields go. And the final two lines are the required structure terminators.

So to create the table we want, we will Target.Writeln the first four lines immediately after the image. Then we can loop through the file, replacing the marker strings with the actual database references. For example:

Set(FIL:ID_Key)
Loop
  If NOT Access:Files.Next()
    DisplayString = Clip(DisplayString) & ('<<tr>')
    DisplayString = Clip(DisplayString) & ('<<td width="15%">' &|
                    Format(FIL:FILEDATE,@D8) & '<</td>')
    DisplayString = Clip(DisplayString) & ('<<td width="55%">' &|
                    Clip(FIL:Description) & '</td>')
    DisplayString = Clip(DisplayString) & ('<<td width="30%">' &|
                    Clip(FIL:Author) & '<</td>')
    DisplayString = Clip(DisplayString) & ('<</tr>')
  End
End

(Clearly, we are going to Target.Writeln(DisplayString) after the entire <table> structure is completely assembled.)

Links

In the second cell of the <table>, there is a hyperlink created from database fields. Like the creation of the table row, this is done on the fly:

DisplayString = Clip(DisplayString) & ('<<td width="55%">' &|
                   '<<a href="http://' & Clip(FIL:URLPath) &|
                Clip(FIL:FileName) & Clip(FIL:FileType)'">'&|
                Clip(FIL:Description) & '<</a><</td>')

Straightforward stuff, this. (Yes, you could easily have your HTML editor provide this boilerplate along with the table structure.)

The call could equally well have been to another app containing the update form:

'<<a href="www.par2.com/cws/cwlaunch.dll/MyForm.exe.0?' &|
        Clip(FIL:ID) & '">'

or, even, could have called that form in another browser windows:

'<<a href="www.par2.com/cws/cwlaunch.dll/MyForm.exe.0?' &|
     Clip(FIL:ID) & '" target="_blank">'

These snippets pass a unique record identifier as a parameter using the HTML convention of a question mark to separate command line arguments from the actual command ("+" separates arguments). These can be picked up in the form app and, if multiple parameters are passed, parsed in the receiver. This allows us to fetch the correct record and set up the form for Change. If omitted, your form could be setup to Add, if you wish to allow Adds. Otherwise, you could just terminate the second app ('course, you shouldn't have kept the Insert button in this case).

There are several FAQs available on how to do this and on how to exit from the second window in this scenario. Check out the knowledge bases.

If you want to stay in the same app, you'll need to examine the code generated by the classes and duplicate the dynamic call at runtime. It's not rocket science, but there is a class override required, either an entirely new class or a new class method (Ok, maybe it is rocket science).

The Next Big Decision

The next big decision is almost entirely aesthetic. But given the primacy of aesthetics on the Web, if you do not make this decision, your <table>, so carefully crafted, will never display. If you do not choose well, the resulting page will be not only unattractive, but border on unuseable.

That decision is where to place the code, the Target.Writeln statement, which causes the <table> to appear. And, you have only two realistic choices. You can place the "list" above the (remaining) Windows controls or you can place it below them.

Some preliminary experiments to insert additional <table> cells which would align the buttons, etc., vertically on the left have, so far, borne no fruit. However, I suspect this may be due to the horizontal orientation of these controls in the window, which orientation is not overridden in the HTML generated by the broker. Or, I just haven't found the right embeds and/or points at which to break up the HTML. Perhaps a combination ...

To place your HTML above the standard window items, do your Target.Writeln in the "Internet, after the opening <BODY> tag" embed (this places all of your HTML before the <table> created by the IC window structure). To place the HTML after the buttons, use the "Internet, before the closing </BODY> tag" embed (this places your HTML just before the default <table> is terminated).

To keep your HTML inside the default <table>, use your (remaining) control embeds. Use the Before Generating HTML for the first control and the After Generating HTML for the last control. Note that these controls must always be displayed; any control not displayed will not generate HTML and, therefore, its embeds will also be ignored.

In this app, however, I had no choice. For aesthetic reasons, if I had tried to place the standard window controls anywhere but where they were, the page would have ended up looking ... most odd. Either the buttons would be above the graphic or between the graphic and the "list." Trust me, it didn't look very good at all.

Had the graphic not had the arch on the left side, perhaps other options would have been open. But ...

Another decision you may wish to make is to replace the buttons entirely. An image with a Region over it becomes an image map. Therefore, code in the Region's Accepted embed will execute on a mouse click. That is, except for the fact that it doesn't depress, an image-region combo acts like a button (standard HTML behavior).

Previous/Next Buttons

You must first understand that Previous and Next buttons are not necessary. You may neither need nor want them.

Without them, you will create a single, comprehensive list. Very Web-ish. The only limitation you may face is the size of your "DisplayString" variable. The variable must be big enough to contain all of the characters required for both the HTML and your data (10,000 characters, for example, is enough for 33 lines using the code above). But, in this case, you may not want to use a variable, but Target.Writeln directly to the page.

If you do want Previous and Next buttons, there is no pat formula for implementing them. You need to understand that you are emulating the queue management that browse boxes (specifically, ACCEPT) do for you automatically in Windows. (Perhaps the closest thing to this that the typical Clarion developer is likely to have done is the Wizard option on a Form. Take a look at the FAQ in the on-line docs on this subject, you'll get the basic idea.)

The things that do not vary are: (1) You need to know how many records you want to display on a single page. You may use either a variable or a constant; there is no motivating reason to make this user changeable as I do (and, trust me, there are many reasons not to do so). (2) You will increment and decrement your counter in the Accepted code for the buttons (increment in Next, decrement in Previous -- just like in a Form Wizard). (3) You will need to revise your file Loop so that it only loops RecordsPerPage times. That's the easy part.

The hard part is knowing where you are in the file. To do this you must have some kind of pointer/counter/indicator accessible to you that tells you your relative position in the file (similar to knowing the page of a Form wizard the user is on). Neither the built-in Pointer() nor Position() functions are sufficient to this purpose. A unique, numeric key value is probably best.

This gets very complicated if you filter the file at runtime. If a filter is in place, then looping RecordsPerPage times may very well not find RecordsPerPage number of qualifying records.

Complication on complication: if you wish to provide alternate sort orders, in order to track your relative position in the file, you will need unique numeric keys that also provides the required sorting.

You could avoid all of these issues by using a setup window/page to capture filters and sort orders. You would then provide a button, the standard Close button will do, to return to that page. Design call. Very Web-ish (and very good for your mental health -- highly recommended).

A queue could be loaded at IPL or procedure initialization containing all the fields needed for alternate sort orders. This has the advantage of allowing very rapid sorting and filtering. Queues also have built in, useable pointers that allow retrieval of the required records:

Get(queue,label)
FIL:ID = value
Access:Files.Fetch(FIL:ID_Key)

But queues have a very substantial downside. If you do not Free() the queue at every conceivable place your program might exit and account for all methods of exiting, you will eventually exhaust memory and your server will suffer terminal senile dementia, rolling over quite dead. You will not receive an error message on the server when this happens. Further, I do not know what will happen to the memory used by the queue if your users simply abandon the app and it times out (can't say I'm all that anxious to find out either). Yes, very important issues here.

Techniques of Coding

There are three ways of actually implementing the dual-personality look.

(1) You can create and maintain two applications. Your call.

(2) You can embed both personalities in a single procedure. This is what I have done in the example we're using here. My call.

At runtime, determine your environment and Hide/Unhide the relevant controls.

Alternately, you can click "Disable if launched from browser" for all Windows-specific controls and default all Web controls to Hide. The runtime libraries will manage the Windows controls for you and all you then need to do is

If WebServer.Active
  Unhide(? ...
  Enable(? ...
End

for your Web-specific controls.

Give strong consideration to using Groups on your windows. This way, a single statement can affect several controls.

(3) Use a "wedge" procedure.

In this scenario, you leave your Windows procedure entirely alone and create a second procedure for use on the Web. Copy the Windows procedure and delete controls you won't need on the Web. Then add your Web-specific controls and HTML generation code.

The advantage is that your embeds are much easier to follow. But, you now need a third procedure (or an embed) to determine which of the two procedures to call.

Wedges are a classic Clarion technique. A wedge procedure replaces the procedure you would normally call. The wedge tests a condition and differentially calls other procedures.

Let's say, for example, that your main procedure is a browse. You would change Main so that it used the Source template. It would contain only:

If WebServer.Active
  WebBrowse
Else
  WindowsBrowse
End

You can also use your Frame's ThisWindow.Ask method to determine the environment and respond accordingly:

! [Priority 4000]
If WebServer.Active
  JavaCoverWindow
  BrowseFAQs
  ThisWindow.Kill
Else
  Start(BrowseFAQs,50000)
End

In this case, the wedge by-passes the Frame when running on the Web and, indeed, never allows it to be seen.

Summary

Five months ago, I started with a simple essay on Target.Writeln. I never really intended to make a series out of it. But you know how one thing leads to another. We have covered an awful lot of material.

We have established the basics you need to customize your Clarion apps for the Web, including removing the Java, should you so desire, using Tony Goldstein's template modifications (www.cwsuperpage.com). In July, we discussed using the Target.WriteLN method to create substitute reports and display forms. In August, we explored non-Java (HTML) browse boxes. In September, we saw how to make IC apps look more like "standard" Web pages. And, in October, we investigated image display. Now, you can remove every shred of evidence that you user is actually in a Windows application!

With all this, you should now be equipped to do just about anything you want with Internet Connect. You can do anything, at least, that doesn't require hacking the templates and/or classes. For that, you're on your own.