Tech News Back Issues Issue: 092502
Introduction to the Omnis Web Client
The Omnis web client is a proprietary technology. It takes the form of either an ActiveX or Netscape plug-in on the web browser client and delivers a full software application within a single page of html. Web client can be used to create attractive and sophisticated graphical user interfaces that the client can use over the web to interact with a database running on a remote web server. The developer creates web client applications using very similar looking components and code to that which is used to create a thick-client desktop application. This means that an Omnis developer that has hitherto worked on non-web applications can easily deploy their skills creating web client applications.
The web client plug-in
order to use a web client application, the user must download and install
the Omnis web client. This is a one time operation that does not need
to be repeated for subsequent visits to any web client applications. The
Omnis web client plug-in is around 500k for Internet Explorer and Netscape.
The Sidebar is not included in the core group of DLL's
for the initial web client installation. When the user first visits a
web client application that uses this component, the "Formsbar.dll" is
automatically requested and downloaded from the web server. This is a
quick and seamless operation.
For reference, the core DLLs are as follows.
The Omnis Server
In order to host an Omnis Studio web client application, you must consider three elements: the web server (e.g. IIS/Apache), the Omnis Server and the database.
These elements may all be situated on a single machine or they may reside on seperate machines. Typically, the Omnis Server is installed on the same physical machine as the web server and the database is a seperate SQL RDBMS; in which case Omnis connects to the database across a network using middleware and Data Access Modules (DAMs).
The web client application is contained within an Omnis Studio library, which is loaded on the Omnis Server. Libraries are normally written on development machines and copied as files to the Omnis Server.
Libraries can be changed and updated as often as required. No compilation is required; it is a simple matter of swapping a library file. The advantage of this is that the web client application need only be changed on the Omnis Server. The next time the user logs on to the application, they will be working with the new version. This can give web client an advantage over thick-client solutions, which can be hard and expensive to upgrade.
Multiple libraries can be run within the same Omnis Server and, if separate licenses are used, multiple instances of Omnis can be run on the same machine, although this is not a standard or popular configuration. In practise, a dedicated server is normally required to host an Omnis Server. Dedicated servers are widely available at ISPs and, in the UK during the summer of 2002, typically cost around Â£100/month for a Linux server and Â£200/month for a Windows server. If the Omnis Server is incorporated into a corporate network, these costs do not directly apply.
I hope you had a great break for the past two months. (I was tempted to say "summer break", but we have a large constituency from south of the equator so that wouldn't be at all accurate!) Now it's time to get back to work.
In my last article, I discussed the basics of "Methods and Messages". This was done as an introduction to a series of articles on "Event Handling" that begins now. In this article I introduce some theory and explain some basic terms in event management. This will be followed by other articles on practical examples and more advanced topics on event handling.
Using computers would be pretty boring, and not 10% as useful as we find them today, if we couldn't interact with them. Not that they provide sparkling conversation, but they provide users with options during the course of their work rather than having the work be entirely linear and repetitive. Young people today take this for granted, but I began in this business when we not only had to punch 80 column cards and submit our programs for batch operation, but occasionally we actually had to rewire the computer for specific tasks! The technology has certainly come a looooong way in my lifetime! (I guess it didn't really exist much before I did anyway...)
In personal computers today, we usually interact with a program by performing "user-initiated events". That is, we perform some action using a peripheral device, like a keyboard or a mouse, that the program is designed to detect and respond to. In this way, non-programming end users can do meaningful work without needing to understand the inner workings and programming syntax of the underlying programming language.
In the "old days", this interactivity was rather limited and still very linear, in that the user came to a point where a choice must be made among a small number of options in order to continue working. As programming techniques evolved, more flexibility was added - first by allowing the user more keyboard actions (which had to be memorized), but then by offering additional input devices and programming constructs like dropdown menus, pushbuttons, toolbars and other devices. But what makes work simpler for the user creates more interesting work for the programmer...
And Now, A Message From Your User
In the last issue of Omnis Tech News, I discussed how objects within an Omnis Studio program interact with one another through "messaging". An event is nothing more than a "user-initiated message" that indicates to the program that it should perform some action in response. Consider the following illustration:
Here the user clicks on a "control" object (in a toolbar in this example). This sends a "click" message to the event handler for that object. There may be many types of events that are "meaningful" to that event method, but only the code for a click is executed. In this case, another method ($next at the "instance of the class" level) is invoked. This is just one of a variety of actions the user can perform with this window.
Methods are Methods, But
Event handling methods are special in a number of ways. The most significant of these is that we can't (or at least shouldn't) call them from other methods in the manner discussed in the previous article. Yes, event handling methods are "public" methods in the general sense, but the way they are constructed (if written properly) they would never execute any code lines since we can't pass an "event" when we invoke a method.
Event methods are made up of blocks fo code preceded by "On" commands. Each "On" command line indicates one or a number of events that cause its associated block of code to be executed. There is an implied "OR" between multiple events in a single "On" command. Here is an example of an "On" command that reacts to a click or a double-click event:
In the Method Editor, Omnis Studio only offers us "event variables" that are valid for the object to which the $event method belongs.We just select the appropriate event(s) from the list provided.
Direct Event Handling Methods
Any GUI object (a window, menu, toolbar and remote form class or the component object of such a class) that can contain methods can have a method for trapping and reacting to events that happen directly to it. The name given to this method must be "$event". In operation, if a detectable event is perpetrated on an object, Omnis Studio executes the $event method of that object to see whether the programmer gave that event meaning and purpose.
The trick here is to understand what eventsw apply to which types of objects. For example, we can detect "before" and "after" events for entry fields (or any field object that can receive the focus), but "OK" and "close" events happen directly to a window (and are not directly detectable at the field level).
What "Events" Invoke an Event Handler?
Occasionally questions come up (in classes or on the Omnis Underground listserver) about how to detect events for certain things. Comments like "I can't detect a double-click on the status bar", "I can't detect whether the Shift (or Control, or Function) key is held down when the user selects a menu item.", etc., are common among new Omnis Studio programmers. Let's address these two most common misconceptions in order:
First, we can only detect events that happen to objects that can contain methods. Since a method must be used to handle the event, this only makes sense. Neither the status bar of a window nor the Omnis Studio help bar can contain methods in the current version of Omnis Studio (and I'm not suggesting an enhancement request here), so we can't possibly detect "events" that happen to them. Reports are also not interactive, so we can't perform "drill down" operations on them. (This is an enhancement request...) A method named "$event" in a report object has no special meaning.
Menu lines are a special case. The only event that a menu line can react to is being selected. For this reason, it is not necessary to use an "On evWhatever" command to begin the code block in a menu line's $event method.(Of course, we used to say something similar about pushbutton fields only reacting to clicks not too many years ago...). But let's examine how that menu line can be "selected". There are two basic methods: the user can open the menu with the mouse and release the mouse button over that line (or do the "sticky menus" second mouse click on the menu line) or the user can type the keyboard equivalent for that menu item. (Windows users also have the "alternate" keystroke string - a sort of hierarchical walk through the menu structure that is somewhat analogous to the "sticky menu" approach.) These two (or more) techniques are intended to be exactly equivalent in their effect on a program, so there is (or should be) no way to tell them apart. Since the Shift or Control/Command key can be a part of the keyboard equivalent to "physically" selecting a menu line, having separate functionality for that menu line if one of those keys is held down from that if it is not held down conflicts with the very nature of keystroke equivalents. For this reason it should not be (and isn't) allowed. We can, however, build in separate responses for modifier keys held down when a click is detected on a pushbutton or other similar control by testing whether #SHIFT, #CONTROL (#COMMAND) or #OPTION (#ALT) are non-zero.
To determine what events are available for a specific object, just go to the $event method for that object in the Method Editor, place an "On" command on a method line and examine the list of available events offered.
A Bit of Grammar
Usually when we use the term "object" in Omnis Studio, we use it in the "nominative" sense. That is, we are talking about a "type" of thing and giving it the name "object" as a general category. When we use this term in discussing events, we use it in the "objective" sense, as the item being acted upon by the (user causing the) event. (Most non-English speakers are familiar with the concept of "objective case". For English-only speakers, remember "the object of a preposition" from your "grammar school" days...the use of "him" vs. "he" in referring to a person...)
Omnis Studio gives us a special notational reference to the "current object" (the "object of the current event"). It is referred to by "$cobj", a very powerful reference as we shall see in the example article in the next issue. I only bring this up in an attempt to avoid confusion with "object classes and instances", "object variables", "component objects on a GUI class" and the general "object-oriented" notion of an "object". (There just aren't enough words for all possible meanings sometimes!)
Occasionally, just detecting that a type of event has occurred is not enough information to thoroughly define the event. Some events require additional information. For example, knowing that a click has occurred on a list display field may not be enough information to act on. We most likely also need to know which line of the list was clicked upon so we can perform an appropriate task.
Omnis Studio helps us out in this way by providing "event parameter variables" that contain further details describing the event. All events contain a variable named "pEventCode". This contains the name of the current event. Other event parameters can be viewed in the "Variables" pane of the Catalog by selecting the "Event parameters" item in the left column as shown in the screen shot below:
Since different events can have different parameters, this display is context sensitive down to the command line level. That is, the proper event parameters are shown for the "On" code block to which the currently selected command line belongs.
Event parameters are different from "Parameter" variables. Parameters variables are "local" in scope and apply to the entire method for one thing, and they are used to receive values or references passed in from a calling method. Parameter variables declared for an event handling method (in the Variables Pane of the Method Editor) will act as any other local variable. There is no way for the user to pass values or references to such variables.
Container Objects and Event Handling
We can not only detect and react to events that happen directly to an object in Omnis Studio, but we can also detect events that happen to any object contained in an object. For example, a window instance contains component objects, so we can detect and react to events that happen to any of the component objects at the window "level". Also, many types of component object can contain other objects as well. We can also react to events that happen to objects contained in a "container" object (like a tab pane) in the same way.
To do this, Omnis Studio also gives special significance to a method named "$control". This is an event handling method used to detect and react to events "passed up" to the object that directly happened to objects it contains. A container object can have both a $event method (used for events that happen directly to it) and a $control method. The total potential "path" that event handling can follow is the $event method of the original "object" of the event and the $control methods of every container in which that object is nested up to the task in which it resides. So in the example below, events that happen to the radio button named "End user" can potentially be handled by any of the containers in which it resides.
But there are limits to this to help streamline the process, as we shall see a bit later...
Resulting Actions and Redirection of Events
Part of the reason for detecting events is to determine whether to allow the event to take place in the "normal" way. An event handling method is a sort of "decision point" in a program in that a choice can be made as to whether to allow the normal result of the user action to occur or to trigger some other action instead.
The method is programmed to examine the event parameter values and values of other variables to make this determination. The simplest determination is whether or not to allow the event to proceed. The key thing to understand here is that the event has not actually taken place at the point in time when the event handling method is invoked. The event message variable that is "turned on" by the user action indicates the intent of the user to have this event occur. It is not an indication that the event has, in fact, occurred. Consider this simple example:
The user is entering data into an entry field and then presses the "Tab" key to move to the next field. The first event that is detectable is an "After" event for that entry field, indicating the user's intention to shift the focus somewhere else. The "After" event is accompanied by a parameter named "pNextCode" that indicates how the "After" was triggered. In this case, pNextCode contains "evTab". In the field's $event method, we can use the code block that follows a line "On evAfter" to perform data validation (among other things) to determine whether the shift of focus should be allowed. If the value typed into the field by the user is out of range or otherwise inappropriate, we can disallow the event and keep the focus where it is.
Quiting the Event Handler
To do this, we must "discard" the event. This is accomplished using the "Quit event handler" command and its "Discard event" option. Here is the syntax for that command:
Notice the second option named "Pass to next handler". We will deal with this later in this article.
The "Quit event handler" command quits the current method just like the "Quit method" command, but it differs in a couple of ways. "Quit method" has an optional calculation that generates a "return value". Event methods are not invoked by messaging from another method, so there is nowhere for a return value to be returned. "Quit event handler" has the two options shown above and is the only wayh to provide those actions.
In parallel with the options of "Quit event handler", we can also queue other events to occur. There is a series of "Queue" commands for this purpose. In the example above, perhaps certain values left in the field require that data entry be redirected in a different order (perhaps skipping over the next field or two). We could then queue a "set current field" event to force the focus in a different direction. Here is the syntax for that command.
Of course, we must execute this command before the "Quit event handler", so the concept of "queuing up" the action is quite appropriate.
Whether we discard the current event or not, we can queue other events to occur. Queued events occur in the order in which they are placed in the queue and after the result of the current event (if it hasn't been discarded) when the event handling method completes execution. Perhaps a diagram of this whole process will help in the explanation...
The Event Cycle
The flow of control of event handling begins at the upper left corner of the following diagram:
The operator performs some action and sets of the event handling mechanism. The event code for the intended action of the user is put into pEventCode and passed to the $event method of the object acted upon. This object can be referred to at any level of event handling methods by the notation shorthand "$cobj". If the $event method of that object exists and contains an "On" block that detects the current event code, that code block is executed and any resulting actions are placed on the event queue.
If no $event method exists, if no "On" block detects the current event code or if the "Pass to next handler" option of a "Quit event handler" command is executed, the $control method for the container of the current object is invoked (if it exists). If it contains an "On" block that detects the current event code, that block is executed in the same manner as a $event method. this continues until the event is handled without passing on the event handling responsibilities of until the task $control method is invoked. The resulting actions are then executed and then control returns to the user. A few initial events (like "evAfter") imply additional iterations of this cycle before control returns to the user, but we'll leave those until next time...
Well, that's enough for today. Next time we'll flesh this out with some examples.
|© 2002-2003 Copyright
of the text and images herein remains with the respective author. No part
of this newsletter may be reproduced, transmitted, stored in a retrieval
system or translated into any language in any form by any means without
the written permission of the author or Omnis Software.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7 is a trademark of Omnis Software Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.