Tech News Back Issues Issue: 091003

Introduction to the Omnis Web Client: Part 19, Graphs and the HTML interface

By Dr Caroline Wilkins
iB2B Systems Ltd

(Note the Web Client is now obsolete but you may find useful some of the techniques discussed here regarding web form design and web applications: you should now use the JavaScript Client to create web & mobile apps.)

During my session at the recent OzOmnis conference, someone asked me whether it was possible to incorporate Omnis graphs into an HTML/ultra-thin interface. It occured to me that this question may be of interest to others and therefor an ideal topic for the technical newletter.

In short, yes it is possible. Just as you can take snapshots of a graph object to include in a webclient application, you can save those snapshots to image files and either return just a JPG of the graph or reference the image files in the html page you return to a user.

Let's take a look at an example of creating a graph from some sample data, creating a graph using the Omnis graph component and then returning a JPEG snapshot of that graph to the user as a web resource...

You can download this demo library, demo database and the graph.htm page required for this tutorial from here: htmlgraphs.zip (libraries may need to be converted in Omnis).

1. Create a new library and a basic html interface
1.1. In Omnis Studio, create a new library and call it "htmlgraphs.lbs".
1.2. Create a new remote task within that library called rtGraph
1.3. In a text editor, create a new text file called graph.htm in a convenient localhost webserver directory (e.g. c:\Inetpub\wwwroot\omnistest\graph.htm) and put in the following content:

<HTML>
<HEAD>
<TITLE>Graph request</TITLE>
</HEAD>
<BODY>

<form method="GET" action="http://localhost/scripts/Omnisapi.dll">
<input type="hidden" name="OmnisClass" value="rtGraph">
<input type="hidden" name="OmnisLibrary" value="htmlgraphs">
<input type="hidden" name="OmnisServer" value="5912">
<input type="submit" name="submit" value="Fetch Graph">
</form>

</BODY>
</HTML>

If you have chosen the path suggested above, you should be able to test this file by opening your browser and selecting http://localhost/omnistest/graph.htm

This should give you a start page with just a button to click that will call the remote task rtGraph. No additional data is conveyed to the remote task at this stage of development, but it could easily be done so by adding additional hidden parameters or form input elements. This would enable you to build a graph with more user interaction. We may return to that idea in a later tutorial.

2. Testing the graph request interface
2.1. First, make sure that you have Omnisapi.dll in the scripts directory of your localhost webserver and that it is configured to execute. Also, make sure that the Omnis serverport option (Tools-Options) is set to 5912 or whatever port you have chosen in the example html form.
2.2. Set a breakpoint in rtGraph by typing "Breakpoint" as the first line of the construct. This will ensure that the debugger doesn't run through a breakpoint in an empty method. You can comment this out later on when you are happy that the remote task is being called from the html form.
2.3. Open up http://localhost/omnistest/graph.htm and click the button. This should cause the Omnis debugger to hit the breakpoint.

So thus far, we have given the user a way of opening a dialogue with our Omnis application. We are now ready to create a graph to send back to the user. We start by adding a database from which to assemble graph data...

3. Database
3.1. Copy the htmlgraphs.df1 database file to your disk, e.g. C:\Omnistest\htmlgraphs.df1.
3.2. Add the following code to the $construct of the Startup_Task so that the library connects and opens a session to the datafile you have created:

Set current session {HTMLgraphs}
Start session {dOMNIS}
If flag false
  OK message Configuration error {Unable to load the OMNISSQL DAM}
  Quit method
End If
Set hostname {C:\Omnistest\htmlgraphs.df1}
Logon to host (Omnis Character Set)
If flag false
  OK message Error opening session {[sys(131)]: [sys(132)]}
  Quit method
End If

Obviously, you will have to change the path to the df1 file above if you have placed it somewhere else.

3.3. Close and reopen the library to ensure that the library opens a session to the database correctly.
3.4. Create a schema of the Animal table by opening up the SQL Browser, drilling down to the Animal table and then dragging and dropping the table over into the HTMLgraphs library window.
3.5. Create a Table class called T_Animal

If you run a SQL query in the Interactive Sql Browser, you will see I have entered a few animals into that table, along with data about their species (very loosely typed!) and the month of their registration and adoption. That gives us some data to play with for the purposes of graphing.

4. Get data method
4.1. Add a method to rtGraph called getData (right click in methods list, Insert New Method)
4.2. Create an instance variable called iDataList of type List
4.3. Create two local variables called Count and Species, both of type Character
4.4. Enter the following code in the getData method:

Set current session {HTMLgraphs}
Do iDataList.$define(Species,Count)
Perform SQL {select species,count(*) from Animals group by species}
Build list iDataList from select table (Clear list)

4.5. Add a line to the $construct method of rtGraph to call this method:

Do method getData Returns iDataList

You can now test this by clicking the button on the graph.htm page, stepping through rtGraph and into the getData method. You should find that, when you have stepped past the "Build list" line, iDataList contains a 4 line List (right click on iDataList and select "Variable iDataList" to view contents).

Now that we have a List of data to graph, we can move into the actual graphing part.

5. Build Graph method
5.1. Add a method to rtGraph called buildGraph
5.2. Create the following local variables

5.3. Add the following code to the buildGraph method:

Calculate graphObj.$columnheadings as
Calculate graphObj.$dataname as iDataList
Calculate graphObj.$maintitle as 'Species Population'
Calculate graphObj.$majortype as kGRbars
Calculate graphObj.$minortype as kGRsideBySide
Calculate graphObj.$footnote as ""
Calculate graphObj.$grouptitle as ""
Calculate graphObj.$seriestitle as ""
Calculate graphObj.$subtitle as ""
Calculate graphObj.$xaxistitle as "Species"
Calculate graphObj.$yaxistitle as "Number of animals"
Calculate graphObj.$y1axistitle as "Number of animals"
Calculate graphObj.$x1axistitle as ""
Calculate graphObj.$y2axistitle as ""
Calculate graphObj.$zaxistitle as ""
Calculate graphObj.$o1label as 'Datezzzzz'

Calculate graphObj.$depthmode as kGRdepth3D
Calculate graphObj.$depthimsangle as 45

Calculate mBackground as graphObj.$getobject("$background",0,0)
Calculate mBackground.$areaeffect as kGRwash
Calculate #50 as mBackground.$areaeffect
Calculate mBackground.$washtype as kGRabAsingle
Calculate mBackground.$washdir as kGRwashZoomRect
Calculate mBackground.$washstart as rgb(255,204,0)
Calculate mBackground.$washend as rgb(255,255,255)

Calculate mGraph as graphObj.$snapshot(480,300)

Do pictconvto("CS24",mGraph,"JPEG") Returns mJPG
Do MyFileObj.$createfile('C:\Inetpub\wwwroot\omnistest\graph.jpg') Returns lStatus
Do MyFileObj.$writefile(mJPG)
Do MyFileObj.$closefile()

Again, you may wish to use a directory other than C:\Inetpub\wwwroot\omnistest\graph.jpg to save the graph image that is created. Nevertheless, you should choose a localhost location if you want to simulate a website environment. You will also have to make sure that the path you return to the user for the URL of the graph that is created matches the location you have chosen to write to.

Note the last 4 lines in the buildGraph method. The graph object snapshot produced in the previous line creates an image of Omnis Picture format in the variable mGraph. This needs to be converted to a graphical format for use on a web page, so we use the "pictconvto" function to change the image to JPEG format. This is written to disk within the localhost directory structure where it can be served as a web resource.

6. Finishing off the $construct methods
6.1. Add code so that the $construct method now contains the following:

Breakpoint

Do method getData Returns iDataList
Do method buildGraph

Quit method 'http://localhost/omnistest/graph.jpg'

You should now be able to test the whole interface and find that clicking the button on http://localhost/omnistest/graph.htm causes a graph to be drawn, using data from the sample database, and is returned to your web browser (http://localhost/omnistest/graph.jpg). It should look something like this:

 


 

Popup Menu Fields

By David Swain
Polymath Business Systems

In previous issues of Omnis Tech News, we have explored two ways to instantiate a menu class. There are still a few others. In this issue we will examine the consequences of deploying a menu using a Popup menu field on a window.

Popup menu field

Popup Menu Field Basics

The Popup menu field is one of two window field types that are designed to contain an instance of a class — in this case, a menu instance. The other is the Subwindow field, which will be detailed in some future article. (We could also stretch this concept and include the Screen Report Field window object as well, but it is much more than just a container...) The important thing to understand with either of these field types is that the field itself has properties and methods completely separate from the properties, variables and methods of the instance that it contains.

A common mistake I see Omnis developers make is confusing the Popup menu field with the menu instance it represents (similar to the concern I have for people who confuse an entry field with its associated variable or a list display field with its associated list variable), so I want to stress this here. The field and its associated menu instance are not the same. The field has its own properties (like Appearance and Text properties) and methods (up to 501 of them, including a $event method!) and the instance has its own properties (name, title, class type, etc.) and methods (up to 501 at the class level and the same potential number for each line). The primary purpose for using a Popup menu field on a window is to give the user access to the command lines of the menu instance - to expose a menu with a mouse click on a field. But there will be times when the properties or methods of the field itself also come into play.

General properties There aren't many properties for this field type, so let's take a quick look at the more important ones. We find the properties that distinguish a Popup menu field from other types in the "General" properties group. This field type has the usual properties for a window component object (name, ident, size and position properties, order, enabled, active and visible, etc.). It can even have a tooltip and a contextmenu of its very own! Instead of a "dataname" property to specify the variable the field represents, a Popup menu field has a menuname property to specify which menu class it represents - this is the distinguishing feature at design time.
Appearance properties The "Appearance" properties are also pretty basic and should generally be left at their default settings unless there is a good reason to not follow normal interface standards for this control type. If you have dragged the #STYLES system table from the Component Library into the one you're working on, there is a fieldstyle named "CrtlPopupMenu" already defined for all platforms that is very handy.
Action properties The "Action" properties are quite basic. Ignoring drag-and-drop properties, there are only the "special" event types to set. Since I have not been able to get this field type to accept the focus, the keyevents property is useless to me here (but still included in the basic set for window fields). But since this field type is mouse-driven, the mouseevents and rmouseevents properties could prove to be of value. There is also the possibility that we might want ot track status changes for this field type in some situations, so the statusevents property could come in handy as well.
Text properties Be aware that the "Text" properties (as with the others) only apply to the Popup menu field and not to the menu instance it contains at runtime. This means we can only use these properties to influence the text of the "label" that appears within the field. The title property value from the associated menu class appears on the Popup menu field as a label once the window on which the field resides is instantiated. While in design mode, the name of the menu class associated with the field (menuname property value of the field) appears there instead.

There are also some runtime properties we will examine and use later.

A Popup menu field has a built-in $redraw method. As with any other field, this can be overridden if necessary.

A Popup menu field can also have a $event method like any other field. This is completely separate from the $event methods of the menu lines in the menu instance contained by the field and it can respond to a wider range of event types. Since the field is used to expose the menu within it, events involving the field are trapped before those of any menu lines of that menu.

In its simplest uses, the Popup menu field type is no bother at all and much of this concern for detail doesn't matter. But when using a Popup menu field in more creative ways, there are some additional things we must understand to get it to work as we would like.

The Menu Class

We don't really do anything different in preparing a menu class for the simplest of uses with a Popup menu field than we do in preparing one for use as an embedded menu. I am assuming here that the point of putting a menu in a field of a window is that it performs actions for that window, among which might be invoking public methods of that window instance. If this is the case, then the $event methods of the menu lines will generally be quite short and will point to the public methods of the window instance that houses the Popup menu field by using "$cwind" or possibly "$topwind" (since the window must be the topmost to receive a click on the field in the first place!). In any case, a reference to an item belonging to the window on which the Popup menu field resides from within the menu instance should be addressed through $cwind because $cinst refers to the menu instance and not to the window in that environment.

typical menu method

Menus placed in Popup menu fields on a window act almost like menus embedded in that window instance's menu bar. One difference is in how the shortcut keys of the placed menu are activated...

Shortcut Key Priority

Shortcut keys for the lines of a menu deployed in a Popup menu field take precedence over those of either embedded menus or installed menus - but only if the Popup menu field has been "popped up" (or "dropped" as the case may be). At least on the platforms I've tested (and with the library's canfocusbuttons property value set to kFocus for good measure), this requires clicking the Popup menu field with the mouse - so there isn't much point in using a shortcut key here. (I have not tested this on Windows platforms or in version 3.3.x.) If the menu instance in a Popup menu field is not exposed, shortcut keystrokes seem only to apply to embedded and installed menus.

Trouble In Paradise

This scheme works just fine for simple placements of a Popup menu field on a window instance. But what if the Popup menu is intended as a component of a subwindow that carries its own encapsulated functionality to the master windows on which it is placed? If the methods the lines of our menu must invoke reside within the subwindow, then we have a bit of a problem! If we use "$cinst" to point to an item, the menu instance itself is specified. If we use "$cwind", the main window (the one that contains the subwindow) is specified. There is no simple notational solution to this problem in the current version of Omnis Studio (I'm using version 3.2.1 here).But a little planning is all that is needed - planning that underscores the need to understand the difference between the field and the instance it contains.

Our job is to get all these elements (fields and the instances embedded within them) talking amongst themselves in a meaningful and productive way - so the first thing we must do is to introduce them to each other. In order for our menu to be able to point to the window instance within which its Popup menu field directly resides, we have to "teach" it the "name" of that window instance. This requires a cooperative effort between the Popup menu field and the menu instance inside it. Also, the menu class must be specifically written for this use (as you will see).

The Instance in the Field

Most of us are born into a pre-existing world. There are resources available to us as soon as we emerge. The menu instance that exists in a Popup menu field is born first and then its world is built around it. When a window is instantiated, it is "built from the inside out". The window class is used as a guide in this construction so that everything is done in the proper order, but the instance itself is created one field at a time in order order within each container, with the innermost level of contained fields leading the way. In the case of fields that contain instances (Subwindow and Popup menu fields), the instance to be contained by the field is created first. So as far as the "creation history" of a Popup menu field is concerned, the menu instance is created, then the Popup menu field is constructed "around" it, and finally the window instance is constructed and drawn to the screen (after any additional containers within the window are constructed).

This process also indicates the order in which the $construct methods of these elements execute. The reason this is important to know is that our menu instance can't know about its surroundings on construction because the surroundings don't yet exist. On the other hand, it might be too much to ask the main window instance to manage all of the items inside it in its own $construct method. Perhaps we can build our menu class and the Popup menu field that goes with it in such a way that they can explore their environment together as the need arises.

Messages can be sent to the menu instance from elsewhere within the window because the Popup menu field has a runtime property named $menuinst that points to the menu instance. Resolving this reference (following it with parentheses) gives us direct access to the menu instance.

The menu instance itself doesn't know where it is. But we can give it an instance variable of Item reference type where a reference to the window that directly contains the associated Popup menu field can be kept. If we can manage to somehow populate this variable with a valid notational reference, the menu instance can then invoke public methods of that window instance even if it is used as a subwindow. To do this, we need the help of the Popup menu field. (We can also do a similar thing to inform the menu instance of the Popup menu field itself, in case that menu instance needs to execute some method of the field.)

The Field Containing the Instance

Every field has a runtime property named $container that points to the object that contains it. A Popup menu field is no exception. This property contains just the value we need to populate the instance variable in our menu mentioned in the previous section. The only problems we have to solve are when and how to pass that information to the menu instance.

Let's first consider the "When?" issue. We already know that we can't pass this value during the field's $construct method because the container instance doesn't exist yet. On the other hand, we don't need it from the very beginning. We only need to have the value by the time the menu instance needs to execute one of its lines. We could perform a "just in time" update of that value by trapping the On evMousedown event of the Popup menu field, which is the precursor of a menu line selection for its associated menu instance. This is done in the $event method of the Popup menu field. It also requires that mouseevents be switched on for the Popup menu field if the property is not already switched on for the entire library.

Now we just need a "How?". The usual way to pass a value to an instance variable from outside that instance is through a parameter of an "accessor" method. Let's build a little experiment to demonstrate all of this.

Subwindow Experiment

Just to recap, we are going to create a window containing a subwindow which in turn contains a Popup menu field. We want the menu instance inside the Popup menu field to be able to invoke public methods of either the main window or the subwindow.

What we have to do is to tell the menu instance when the mouse is first held down onto the Popup menu field what the Popup menu field's container is. This notational path will then reside in an instance variable in the menu instance that other methods can use to access items within that (sub)window instance. Here we go:

  1. Create two new window classes.
  2. Name the first window class "testWindow" and place a Subwindow field into it.
  3. Name the second window "subwindowToTest" and place a Popup menu field on it.
  4. Put the value "subwindowToTest" also in the classname property of the Subwindow field on the first window.
  5. Give each window a class method named "$testmethod".
  6. Put this command line in the $testmethod method of the first window:

    OK message {main window listening}
     
  7. Put this command line in the $testmethod method of the second window:

    OK message {subwindow listening}
     
  8. Create a menu class named "testPopupMenu".
  9. Give this menu class an instance variable named "containerRef".
  10. Give the menu class a class method named "$getcontainer".
  11. Create a parameter variable of Field reference type for this method and name it "pContainerRef".
  12. Give this method the following line of code:

    Set reference containerRef to pContainerRef
     
  13. On the second window, give the menuname property of the Popup menu field the value "testPopupMenu" and make sure the mouseevents property value of the field is set to kTrue (if it isn't already kTrue at the library level).
  14. Put the following line of code in the $event method of the Popup menu field:

    On evMouseDown
    Do $cobj.$menuinst().$getcontainer($cobj.$container.$ref())

     
  15. Create two menu lines in the menu class and name them "Call Main Window" and "Call Subwindow".
  16. In the $event method of the "Call Main Window" line, place the following command lines:

    On evClick
    Do $cwind.$testmethod()

     
  17. In the $event method of the "Call Subwindow" line, place the following command lines:

    On evClick
    Do containerRef.$testmethod()
     

Now open a test instance of the main window and test the use of the menu instance in the Popup menu field. Notice that the menu instance now iknows how to specify methods in either the main window or the subwindow within which the Popup menu field resides.

Who's Your Daddy?

We had to "teach" the menu instance what its "parent" container is so it can properly communicate with it. The trick is to populate containerRef with the proper Item reference value. To do this, we passed the proper reference (known to the Popup menu field, but not the menu instance inside it) to the public $getcontainer method of the menu instance through a Field reference parameter named pContainerRef. Since we did this on the mousedown event, the value is available to any method the user might select from the menu by the time that method executes.

Ideally it would have been nice to send this reference to the $construct method of the menu instance, but the order of construction makes that impractical. So we're stuck having to send this reference each time the user mouses down on the Popup menu field.

Not To Be Confused With...

Just so you are aware, there are other uses of the phrase "Popup menu" in Omnis Studio. There is a method command named Popup menu as well as a related command named Popup menu from list. These operate in an entirely different way from the Popup menu field described in this article and we will deal with them next time.

 

 
© 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.

Search Omnis Developer Resources

 

Hit enter to search

X