[Omnis-Newsletter] Omnis Technical Newsletter

omnis-news-admin@omnis.net omnis-news-admin@omnis.net
Wed, 29 May 2002 17:11:54 +0100


 May 29th, 2002

========================================

UNSUBSCRIBE OPTIONS: You have been sent this email because you have directly
signed up for, or expressed an interest in receiving a technical newsletter
when you downloaded an evaluation of Omnis Studio, or registered Omnis
Studio via a magazine promotion. If, however you feel you have received this
email in error you can unsubscribe as well as change your subscription
options at www.omnis.net/newsletter.

N.B.  If you subscribed by checking a box on one of our forms, you will not
have received a password. You will need to submit your email address at
www.omnis.net/newsletter and select the 'email me my password' option on the
next page in order to receive this.

========================================

WELCOME!

Welcome and thank you for subscribing to the Omnis Technical Newsletter.
Published fortnightly, it is intended for Omnis developers of all levels and
experience, for those people evaluating Omnis Studio, or for developers
moving from a similar tool. We think you'll find the content both
interesting and useful for your Omnis development needs and hopefully it
will help you become more productive in Omnis application design.

In the first article in this newsletter, Geir Fjaerli continues with his
exploration of 'Triple O' (Omnis Object Orientation) by delving into
messaging. In the second article, David Swain shows you how to build a file
structure diagram for a native Omnis Studio database.

You may find it easier to work through the exercises and examples in this
newsletter by printing it out before you begin.

CONTENTS:
-About the Authors
-Triple O - Object Oriented Omnis, Part 3, by Geir Fjaerli.
-XML for Omnis Studio
-Building a Structure Diagram, by David Swain
-Copyright and Unsubscribe details


========================================

About the Authors

The Omnis Technical Newsletter contains high quality content from two
leading and well respected Omnis developers, Geir Fjaerli and David Swain.

Geir Fjaerli is based in Norway and has been an Omnis professional developer
for many years as well as a Regional Sales Manager for Omnis. He is
currently working freelance again developing a range of products (in Omnis
of course) including his Prophet5 sales and customer relationship management
solution, now available for Mac OS X. Geir is a regular contributor to the
annual Omnis Developer conferences in the US and Europe, speaking about
Object-Oriented programming in Omnis and SQL development, amongst other
things.

David Swain is the founder and president of Polymath Business Systems, for
many years a leading provider of Omnis training. His expertise in Omnis
programming and his ability to make complex concepts understandable are
recognized throughout the worldwide Omnis community. His current schedule of
Omnis Studio training classes can be found on his web site at
http://www.polymath-bus-sys.com.


========================================

Triple O - Object Oriented Omnis, Part 3.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net

---------------------------------------------------------------------

In the last issue of the newsletter we looked at the concepts of classes and
instances. I suggest you reread that issue to recall the basic concepts.

That covered requirements 3, 4 and 5 of our OO definition. This definition
was formulated by Alan Kay, one of the Xerox front men, and had the
following 6 requirements:
1. Everything is an object
2. Objects communicate by messages
3. Objects are instances of a class
4. The class is the repository for object behaviour
5. Objects have their own memory
6. The class tree is an inheritance hierarchy.

We also briefly looked at the implications of the rather theoretical
requirement number one.

OBJECTS COMMUNICATE BY MESSAGES.

We should now be prepared to tackle Requirement two, which we postponed
earlier: "Objects communicate by messages."

First of all, what is a message? It may be defined as "The process of asking
an object to perform a specific action."

So how does this differ from your ordinary procedure call? It does so in two
distinct ways:
- A message is always given to a receiver. The receiver is an object, that
is an instance of a class.
- The action triggered by the message is not fixed, but depends on the class
of the receiver.

Remember the following scenario which we discussed in the first part: You
ask someone to get some bread. You don't know how he does it, and you don't
really care, as long as he comes back with bread. So one guy would go to the
kitchen and bake some bread, another would go to the shop and buy some. The
result is the same, as far as you're concerned. You have delegated the task
of getting bread, and do not worry about how it's done.

Basically a procedure call will be bound to a specific piece of code to be
executed at design time (or compile or link time in compiled languages),
whereas a message only locates its receiver at runtime.

This, you may argue, you could do much the same with a procedure call. Yes,
you could, and then it would be (kind of) a message.

To illustrate this, let us look at an example. The following was posted to
the Omnis Underground list by Leon Venter.

EMPLOYEE TYPES AND SALARIES.

Consider your application handles salaries for people working in your
organisation. They fall into a number of different employee categories, and
each is handled differently. Some are regular employees, who get a monthly
salary with tax deducted. Some are paid on commission. Then you may have
some consultants who get an hourly rate and pay their own taxes. So each
type needs a different set of rules for calculating the salary. In addition
each employee has a different salary level. So this example handles both and
how much.

In a traditional procedural language you would probably build a list of
employees with their type and salary. Then you would have a procedure that
ran through the list for each employee it checked the type and branched to
the appropriate calculation routine. So your list procedure cannot be
generic, it has to know the types of employees, and it must be updated if
you introduce new types.

THE OBJECT ORIENTED APPROACH.

Consider instead an object-oriented approach. For each employee type you
create a class, an object class in Omnis Studio. Remember, the class is the
repository for object behaviour. And all employees of a given type behave
the same way as far as salaries are concerned.

Since they are mostly identical, they will all inherit from the same
superclass. We will return to the subject of inheritance later.

The different employee classes all hold a public method of the same name,
say $calcwages(). The actual calculation performed by this method will vary
in the different object classes, according to the salary calculation rules
for that type of employee.

Then for each employee, you create a new object - or instance of the class
corresponding to the employee type, holding the name, department as well as
salary and tax information for that individual. (This is the state of the
object.)

You create a new object by saying something like:

Do $root.$clib.$objects.oEmployeeType.$new() Returns ivEmployee

where oEmployeeType is the object class for the relevant type, and
ivEmployee is your object instance.
Now each employee object knows how to handle salaries for his/her type of
employee.

Remember, we said that the behaviour is defined in the class, and is equal
to all instances of that class. But since we've defined the instances from
different classes dynamically, they actually have different behaviour. As
you create your objects, you add them to the employee list, which is defined
with one column being ivEmployee:

Do empList.$add(ivEmployee)  ;; Plus other columns as needed.

It is quite OK to combine the object creation and the add to list, like
this:

Do empList.$add($clib.$objects.oEmployeeType.$new())

Obviously in many cases your employee objects will already exist in the
database, they were created when the employee was hired, and now you just
need to load them from the database into the list.

So now your list holds a number of employee objects, which are actually
different for each employee according to his/her type. Each object then
knows how to calculate the salary, and so the list doesn't need to know
anything about it. Instead it asks the objects to perform their own salary
calculation. The method would be something like:

Do empList.$sendall($ref.ivEmployee.$calcwages())

That is, the objects stored in the list are all instructed to calculate
their own salary. (Note: The $sendall is a built in loop mechanism in Studio
which roughly translates to "For all lines in list", so in this case it
instructs Omnis to perform the $calcwages method in the object in each of
the lines in the empList.)

This example introduces us to one of the most important concepts of object
orientation, namely polymorphism. Polymorphism means that a single function
can act differently depending on the value it acts upon. First let us look
closer on how messaging works.

PUBLIC METHODS.
A message invokes a public method in the receiver. Public methods are
described differently in different languages. A public method is a method
that is visible to methods outside the object/instance to which the method
belongs. Similarly, a private method is internal to the object and not
visible to the outside world.

This concept is called information hiding, and is important to building
components. By only exposing a given set of methods, or the interface to
your object, you prevent people from using them in the wrong way. More on
that in a little while.

In Omnis Studio a public method is prefixed with a dollar sign ($). This way
it integrates with the dot notation on which messaging builds.

Of other languages neither Smalltalk nor Apple's Object Pascal identifies
public methods. In fact all methods are public in Smalltalk. C++ and Delphi
use the keyword public to indicate a block of public methods, and a
corresponding private to indicate private ones. Java uses the same
principle, but uses the keyword in front of every methods or variable
definition.

MESSAGE SYNTAX.
We remember that a message was sent to a receiver. Our message therefore has
to name that receiver.

In Omnis Studio our message would be:

Do receiver.$message(parameters).

In Object Pascal, C++ and Java:

receiver.message (parameters),

and in Smalltalk and Objective C we would say:

receiver message: keyword parameter1.

It is interesting to note that the syntax and other properties varies among
the languages, in fact Delphi in many ways is more like C++, even if it
originates from the same Object Pascal as the Apple version. And Objective C
is clearly built on Smalltalk more than on C++. No matter the syntactical
differences, and the fact that the implementation varies widely, we see that
in one way or another, conceptually they all adhere to same basic
principles.

STATIC AND DYNAMIC BINDING.

One of the major areas where languages differ is how they create their
objects. This is known as binding, which is when a specific class is bound
to the variable, and the two basic approaches are known as static and
dynamic binding.

Static binding is found in languages like C++, and the two Object Pascal
versions. This means that the type of the receiver is stated in the variable
definition, and that the validity of the receiver is checked and memory
allocated by the compiler. You can still change the receiver at runtime, but
usually only to a subclass of the original class.

In dynamically typed languages such as Smalltalk and Objective C on the
other hand, a variable will take any object. Here you define your variable
by name only, without a specific type, and the variable will therefore
accept any object assignment dynamically at runtime. Obviously it then
becomes the responsibility of the developer to control that only object
types which are valid receivers for the messages sent can be selected. So
you won't allow your user to select a customer object for the employee
salary example above.

Now, dynamic typing is obviously more powerful and flexible than static
typing, so why do languages use the latter at all? The answer - as so often
is the case - is speed and optimisation. A language like C was constructed
with this in mind, and C++ inherits its obsession with avoiding anything not
absolutely necessary. A statically bound object is allocated on the stack,
and is considered more efficient than having to handle runtime heap objects.
Having said that, in the real world today the difference is probably
minimal, and the advantages of dynamic typing more important.

BINDING IN OMNIS.

Omnis Studio version 2 (and later) offers both static and dynamic typing.
You may declare your variable statically by creating the variable of
appropriate scope in the variable pane, giving it the type Object and
selecting the desired object class as subtype. If you want a dynamic
assignment you define your variable of type object the same way, but you
leave your subtype open. Instead you use the $new() method to create the
instance dynamically: e.g.

Do objectclass.$new(params) Returns variable.

The addition of dynamic typing to Studio version 2 makes it a full-featured
object oriented environment.

STATIC BINDING IN OMNIS.

When you use static binding, the class of the object is defined at design
time. Note that the libraryname.classname in the variable assignment is a
text property, and is only evaluated at runtime, it is not tokenised. This
means that if you rename your library or object, or copy your object to
another library, the libname will be wrong. Always set a default name for
the library, so that Omnis is independent of the physical file name. Use
find and replace when renaming classes.

Omnis does not create the variable structure before it is needed, so your
object instance will be constructed when it is used the first time. This
means that it is possible to have notation fail when it assumes that the
instance exists before it does.

Since an object class lives "in" a variable, it is destructed when the
variable is cleared or goes out of scope. You can also close an object
instance. The object will however not get any $canclose or $destruct
messages. The reason for this is that since the object may be cleared
because of "outside" events, there is no guarantee that the environment for
a $destruct still exists. With other classes that are instantiated on the
heap, Omnis can delay the closing until the destruct is finished.

DYNAMIC BINDING IN OMNIS.

When you use dynamic binding, the class of the object is assigned at
runtime. Again the libraryname.classname in the notation is text, and is
only evaluated at runtime, it is therefore better to use $clib if the class
is in the same library.

With dynamic binding, the instance is constructed when $new is executed.
This obviously requires that the variable is within scope to work.

Once constructed there is no difference between a statically bound and a
dynamically bound object, and you may in fact override a statically assigned
object variable by executing a $new with a new class on the same variable.

POLYMORPHISM.

As we briefly mentioned, this ability to dynamically assign the type of a
receiver value is called polymorphism. The word is constructed from the
words for "many forms" in ancient Greek.

It allows the building of generic functions that will work independent of
the implementation of the actual runtime object. You won't care whether the
bread is baked or bought, you don't need to worry about how the salary is
calculated for a certain type of employee.

By constructing your app with polymorphic principles in mind, you will have
an application that is easily modified or expanded at a later stage.

And because the objects are independent classes with a defined interface,
their development is easily distributed among a team of developers, and can
just as easily be modified or replaced when the need occurs without breaking
the surrounding code.

VISIBILITY.

One major point in our discussion is how to enforce encapsulation. All of
the techniques above are of little value if some routine can simply access
the inner structure of an object instead of using the proper messages. Say
if someone builds a routine to handle consultants, and it hacks right into
the values of the consultant type of employee object. Doing so will have two
consequences: The code breaks if we change the class later, and the code is
hardwired to a certain class, and won't work with other types of employees.
No good either way.

So we clearly need to make sure the users - that is developers of
surrounding routines - do things the right way. We do this through managing
the visibility of our objects' variables and methods to the outside world.
The "user" of an object should not have access to its inner working, like
modifying its data structure or calling private methods.

This is more complicated than just visible or not visible. A variable or
method may be private to the instance, or globally or publicly available,
but it may also be private yet visible to subclasses, or even to siblings
(other instances of the object.)

The different OO languages we discussed have different approaches to this.
In Smalltalk, any variable is private, but methods are always public.
Private methods are so only by "gentlemen's agreement". The original Object
Pascal from Apple had no such control at all. Delphi adds C++ like control,
with Private, Protected and Public keywords.

Now even in C++ there is no guarantee that a programmer might not hack
around this, and it even offers "cheating" like the Friendly keyword, which
allows functions to access private fields. So in this respect, most
languages handle privacy like humans, that is on a certain degree of trust.
You may close the door, but decide not to draw the curtains. You lock your
doors, but a burglar might just break a window.

Omnis is no different. It offers strong mechanisms for visibility, through
variable scope and method naming. But you can notationally access the
internals of an object, and you can call any object method by just using Do
code method. (I shouldn't have said that. Don't do it!) So even with active
information hiding, proper encapsulation depends on doing things the right
way.

Ok, so that was an introduction to messaging. In the next issue we will look
at inheritance. Until then, take care! And remember, this intro only serves
as an appetizer. In programming there is no substitute for typing and
testing code. So try this stuff out.

Geir :)


========================================

XML for Omnis Studio

oXML is an external component that allows you to manipulate and create XML
documents in Omnis Studio. XML is becoming the standard format for data
storage and information exchange across the whole B2B landscape. XML has
already revolutionized several business sectors, including content
management, information publishing, and news syndication, and its adoption
looks set to continue throughout all areas of business, academic, and
scientific computing.

The oXML component is available for all Windows, Unix, and Mac OS platforms
and is compatible with Omnis Studio 3.1 or later. The oXML component
addresses the most basic XML requirement, namely the ability to parse and
extract information from an XML document, and to generate new XML documents.
It allows you to access XML documents using a standard set of methods
provided by the DOM API.

To read more about this new component, including pages about XML and its
benefits, please go to: http://www.omnis.net/oxml


========================================

Building a Structure Diagram
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com

------------------------------------------------------------------

In the last article we created an organization chart for a simple
hierarchical structure. This is a structure where each subordinate node
points upward to only one node. This time we'll try something a little more
complex, but also perhaps a little more useful: a File Structure diagram for
a native Omnis Studio database - with the structural information retrieved
directly from our library.

(Don't worry, SQL programmers, we'll discuss how you might include
information like this in your Schema classes to yield similar functionality.
In the meantime, you may pick up a few pointers about manipulating lists,
which is the primary focus of this article anyway!)

In a structure diagram like we are about to construct, there are three types
of Files represented:

"Standalone" Files that are not related to other Files in any direct way
"Subordinate" Files that contain Connection information and are part of a
Connection hierarchy
"Top Level" Files that are Connected Files for some Subordinate Files in the
hierarchy, but that do not contain Connection information themselves because
they are not "upwardly connected" to any other Files in the structure.

We will first need to determine into which of these categories each of the
Files in our library belongs and then deal with these sub-groups
accordingly.

First let's do a quick review...

What We've Done So Far

To this point we have created a technology for making box-and-label node
objects for our diagram and for connecting them with lines, as well as a
context menu for changing some appearance properties of the boxes. Much of
this technology we have stored in a "template" window class. (If you haven't
yet read the previous two articles in this series, you will need to do so
before continuing with this article. Back issues of all Omnis Tech News
issues are available from http://www.omnis.net/newsletter)

What we haven't done is work out a way of distributing these box-and-label
pairs on some sort of display grid in a logical manner to display the
organization structure. This is a problem we will have to solve this time.
(In the last article, we began this process by distributing the boxes down a
single column and then let the user drag them into position. We need to
expand our "grid" into a second dimension.)

Our first task, though, is to extract information from our library about the
File Classes it contains and about the relationship information contained in
those Files Classes. We then need to get this information into lists similar
to the ones we worked with in the last article for managing the nodes and
connecting lines of our diagram.

The Example Library

If you want to follow along and deal with EXACTLY the File structure I used
to create this demo, it will be necessary to add some File Classes to the
library we built over the previous two articles. If you built the windows
for those articles into an existing library, it might be a good idea to
create a new library and copy the classes created in the previous two
articles to it ("orgchartTemplate" and "orgchartDisplay" Window classes as
well as the "appearanceMenu", "reshapeMenu" and "effectsMenu" Menu classes).
Now add the following File classes:

File Name           Connected to

Dataset file
Flags file
List file
System file
User file
Mail list file
Member file
Product file
Invoice file        Member file
List link file      Member file, Mail list file
Line item file      Invoice file, Product file

For our purposes in this exercise, we only need the Connection information
to exist in these File classes. We have no need for data-bound variables
(what Omnis historically calls "fields") in these File classes since we
won't be using them for actual data storage (unless you want to, of course).

Instead of this structure, you are welcome to use your own Connections-based
native Omnis structure if you have one. Just copy the classes mentioned
above into your library and continue with the exercise. You'll be rewarded
with a Connection Structure Diagram of your application by the end of this
article.

Basic File Information

When our data repository is a native Omnis datafile using a special facility
called "Connections", the relationship information for the tables in our
database is included in the File Classes directly. Many-to-one relationship
information is always stored in the "Many" File (whether we are using
Connections or strictly relational techniques) where each record "points" to
its "parent" record(s) in the File(s) above it in the relationship
hierarchy. This means that some of the Files in the hierarchical part of our
diagram (the "Top Level" ones) do not "know" that they are related to other
Files. The fact that they are involved in a File hierarchy is stored in the
Connection data of the File Classes that point to them, so we have do to do
a little detective work to gather this information for the "Top Level"
Files.

By the way, if you are not familiar with native Omnis data storage
techniques (and we're NOT talking about the OmnisSQL prototyping tool here),
setting Connections can be done in lieu of creating foreign key fields to
establish File relationships - and it offers some advantages over using
strictly relational techniques. But that's not our issue here. In this
example we are taking advantage of the fact that we can extract the
connection information needed for our structure diagram directly and simply
from File Classes if Connections are used.

The names of all Connected Files for a given File Class are stored in the
"$conns" group for that File Class. As with any other object group in Omnis
Studio, we can use the "$makelist()" method of the group to create a list of
the members of the group. We can also use the "$count()" method of this
group to determine which Files contain Connection information (the
"Subordinate" Files).

Let's begin the process of creating a file structure diagram by building a
list of File classes and the number of Connections they contain. We need a
new Window class in which to build our diagram, so we'll make one named
"fileDiagram". We will need a list variable named "fileList" that (for now)
contains two columns: "filename" and "connCount". We will define the
instance variables we need for this new window as follows:

fileList            List
filename            Character
connCount           Short integer

On this window we should also place a list field to display the contents we
are about to build so we can easily examine our progress. We will use a
"Headed List" field. Set the following property values for this field:

name                fileList
dataname            fileList
calculation         con(fileName,kTab,connCount)
designcols          2
columnnames         File,Conns

We can populate this list variable from the "$files" group of our library
using its "$makelist()" method in the "$construct" method of the window
class like this:

Calculate fileList as
$clib.$files.$makelist($ref.$name,$ref.$conns.$count())

This generates the list content we desire, but it does not include the
column names we need to display the contents in our headed list field, so we
must also set column names using the "$redefine" method for our list
variable:

Do fileList.$redefine(fileName,connCount)

Alternatively, we could just as well define the list first and then append
the lines created using the "$makelist()" method like this:

Do fileList.$define(fileName,connCount)
Do fileList.$merge($clib.$files.$makelist($ref.$name,$ref.$conns.$count()))

Eventually we will also need another column in this list named "level"
(Short integer type) that will be used to set up the grid on which our
box-and-label node images will be placed. If we use this second technique,
we can include this column in our original definition of the list variable
and let the row-by-row values be assigned from the current value of the
"level" instance variable as each line is appended. (We will default this
value to "0" simply by not touching it.) Our "$construct" method will then
read:

Do fileList.$define(fileName,connCount,level)
Do fileList.$merge($clib.$files.$makelist($ref.$name,$ref.$conns.$count()))

We should also change the following properties of our list display field:

calculation         con(fileName,kTab,connCount,kTab,level)
designcols          3
columnnames         File,Conns,Level

Go ahead and open the text instance of this window class and view the
results in our list display field. With the test structure given above, we
should see a list containing the following lines:

Dataset file        0           0
Flags file          0           0
Invoice file        1           0
Line item file      2           0
List file           0           0
List link file      2           0
Mail list file      0           0
Member file         0           0
Product file        0           0
System file         0           0
User file           0           0

(If you have not provided a column for the "level" column in the list
display field, these values will not appear.)

This shows that we have three "Subordinate" Files in our structure, since
there are three File classes listed with non-zero values in the "connCount"
column. If a File Class contains no Connection information, that still does
not mean it is not part of a Connection hierarchy. It may be one of the "Top
Level" Files of the hierarchy.

Connection Information

To determine which are the "Top Level" Files (and to begin building the list
that will eventually contain the connecting line information), we can
populate a second list based on the contents of the first. This will be a
list to contain information on the Connections themselves. For this we will
need a second list variable of instance scope and additional instance
variables for defining the necessary columns. For consistency with our
previous "organization chart" examples, we will use some variable names we
have seen before:

connList            List
filename            Character (already defined)
connName            Character
level               Short integer (already defined)
connLineID          Character
connFromID          Character
connToID            Character

The third line of the "$construct" method for our window class will be to
define the list that will contain this information:

Do connList.$define(fileName,connName,level,connLineID,connFromID,connToID)

The first three columns of this list are used to sort out which Files are
the "Top Level" Files and how to distribute all the hierarchically related
Files on our display grid. The other three columns are used to manage the
connecting line information of our chart.

We should also place another headed list field on our window class to
monitor the values contained in this list as we build our technique for
dealing with this additional data. We will only be interested in the first
three columns of the associated list variable, so our properties for this
field will be as follows:

name                connList
dataname            connList
calculation         (We can actually leave this empty!)
designcols          3 (indicates we will view the first three defined
columns)
columnnames         File,Conn File,Level

Now we must populate this list. Add the following lines to the "$construct"
method of our window class:

For fileList.$line from 1 to fileList.$linecount step 1
    If fileList.connCount
        Do
$clib.$files.[fileList.fileName].$conns.$appendlist(connList,fileList.fileNa
me,$ref.$name)
    End If
End For

This block of code examines each line in "fileList" to determine whether the
File listed there contains Connections. If it does ("fileList.connCount" is
non-zero), that File is added to "connList" once for each Connected File to
which it points. Notice how the "$appendlist()" method is used to populate
the first two columns of "connList" while the other columns remain empty or
zero. (Actually, they take on the value of their associated variable - the
one from which they were defined - by default.) Instantiate the test window
instance to see the result if you like.

But this is only the first step in determining which Files are "Top Level"
Files. These Files are distinguished by being represented in the second
column of the resulting list but not in the first. So let's identify Files
that meet these criteria, add them to our connection list and update their
"level" value in both lists.

For this process, we need to introduce a new variable, but this time at the
"local' level of scope. We use the "local" scope because we don't need
access to this variable outside the confines of this method. The variable
is:

testValue           Character

and it is used to hold a comparison value outside the list being examined
that is derived from a value inside that list. Here is the code:

For connList.$line from 1 to connList.$linecount step 1
    Calculate testValue as connList.connName
    If not(totc(connList,fileName=testValue))
        Do connList.$add(testValue,'',1)
        Do fileList.$sendall($ref.level.$assign(1),$ref.fileName=testValue)
    End If
End For

We step through each line of "connList" and set the value of "testValue"
using the current value of "connName" from that line of the list. We then
use this value to determine whether that value also occurs in the "filename"
column. The "totc()" function evaluates the expression "filename(within the
list)=testValue(outside the list)" on a line-by-line basis. If the sum of
these evaluations is zero, the Connected File named on that line of the list
is not a "Subordinate" File, so it must be a "Top Level" File. If this is
so, we add a new line to "connList" containing the name of this File (with
no Connected File named) and mark it as level "1". We also mark its
corresponding line in "fileList" as level "1" (which we will use later to
distribute box-and-label pairs on our display grid.

N.B. The $linecount value is fixed when the "For" command is first executed,
so adding lines to this list will not confuse the loop counter in this case.

Again, try this out to see how it works before we go any further. If you are
using the structure given above for this exercise, the second headed list
field should display the following once this code has been executed:

Invoice file        Member file         0
Line item file      Invoice file        0
Line item file      Product file        0
List link file      Mail list file      0
List link file      Member file         0
Mail list file                          1
Member file                             1
Product file                            1

The lines corresponding to the indented "Top Level" Files in "fileList" will
also appear with a "1" in the "level" column. Now we need to determine at
which "level" each of the other Files in the hierarchy should appear.

Hierarchical Levels

Working from the top level down, we can determine the hierarchical level
implied by each Connection for the Connected File that contains it. From the
information in "connList" we can also know when we're finished with this
process because each line of that list will have a non-zero value in the
"level" column.

To help in this process, we need another local variable to serve as a
container for the level at which our method is currently working. Sure, we
could reuse the "testValue" variable defined earlier (Omnis Studio can use a
Character variable containing only numerals as though it were a number with
no problems), but we'll create another one for clarity:

thisLevel           Short integer (assumes less than 256 hierarchical
levels - a safe bet!)

See if you can follow this block of code, then I'll explain it:

Calculate thisLevel as 1
While totc(connList,level=0)
    Do connList.$search($ref.level=thisLevel)
    Do
connList.$sendall($cinst.$setnextlevel($ref.fileName,thisLevel),$ref.$select
ed)
    Calculate thisLevel as thisLevel+1
End While

First, we initialize the level pointer "thisLevel" to "1", which is the
level we have already established for the "Top Level" Files in "connList".
While there are still Files in the hierarchy that have not been assigned a
non-zero level number, we:

1) select all lines in "connList" whose "level" value matches that of our
"level pointer" variable
2) send a "$setnextlevel()" message to the window instance for those
selected lines, passing the name of each File at the current level and the
current level number
3) increment the value of the "level pointer" variable when all lines at
this level have been processed

In each iteration, we send a message that sets the next level value for
Subordinate Files of each of the Files, but what is the mystery method that
does the work? Here is the code for the "$setnextlevel()" method of the
window class (yes, please create this method with the given parameter
variables):

parameters: currFile (Character), currLevel (Short integer)

Do connList.$sendall($ref.level.$assign(currLevel+1),$ref.connName=currFile)

This one-line method sets the value of the "level" column to the next
highest integer number for each line where the "connName" column value
equals the value of "fileName" passed to this method. We could have managed
to do this in the same line of code that called this method, but breaking
this out as a separate method makes the whole thing easier to read (and
explain!).

Structure Level Conflicts

If we open the test window instance to view the results of our handiwork
thus far, we may make an interesting observation. Occasionally a File will
exist that is connected to Files at different hierarchical levels. In our
example database structure, the "Line Item File" is connected to the
"Invoice File" (which is a "level 2" File) and to the "Product File" (which
is a "level 1" File). These Connections imply that "Line Item File" is both
"level 3" and "level 2".

For purposes of our diagram, let's agree that when this occurs the File
involved should be placed at the highest level number it achieves. To do
this, we will make another pass through the Files containing Connection
information in "fileList" and set their "level" values based upon the
maximum "level" number for that File in "connList". The contents of
"fileList" will then be used to lay out the box-and-label diagram for our
File structure, while the contents of "connList" will be used to create and
manage any connecting lines for the diagram.

To determine the highest hierarchical level at which each File containing
Connection information resides, we will invoke another public method of our
window class as follows:

Do fileList.$sendall($cinst.$setfilemax($ref.fileName),$ref.connCount>0)

This sends the "fileName" value of each such File to the "$setfilemax()"
method of our window class. That method contains one parameter variable
("currFile" of Character type) and one local variable ("localConnList" of
List type). The latter is necessary because the "maxc()" function we wish to
use to determine our maximum "level" value does not allow expressions
involving #LSEL to extract maximum values from just selected lines, so we
must clone "connList" and remove unselected lines to derive our desired
maximum values. The code for "$setfilemax()" looks like this:

Calculate localConnList as connList
Do localConnList.$search($ref.fileName=currFile)
Do localConnList.$remove(kListKeepSelected)
Calculate fileList.level as maxc(localConnList,level)

Previous Article Flashback

This is a good time to copy methods from the "orgchartDisplay" window class
we created last time into our new window class. We want to copy the
following window class methods for now:

$control (which should also bring with it the instance variables "origX" and
"origY"
$linereset (which will bring its parameter and local variables)
createLines (which also brings its local variable)

We need to modify "createLines" slightly to read as follows:

Begin reversible block
    Set current list connList
End reversible block
For connList.$line from 1 to connList.$linecount step 1
    If connList.level>1
        Do $cinst.$bobjs.$add(kLine,100,100,100,100,kFalse,kFalse) Returns
newItem
        Calculate connList.connLineID as newItem.$ident
        Calculate connList.connFromID as con('box',connList.fileName)
        Calculate connList.connToID as con('box',connList.connName)
        Calculate newItem.$backpattern as 15
        Do $cinst.$linereset(connList)
    End If
End For

The new lines simply populate the "connFromID" and "connToID" columns of
"connList" with appropriate "box" names. (In the previous example these were
already included in the original "connList".)

We will copy a bit more code soon, but first we must detail how our display
grid is going to work.

Parameterizing The Display Grid

We will lay out the box-and-label pairs representing each File on a
level-by-level basis on a rectangular grid, beginning with the "Standalone"
Files and ending with the "Subordinate" Files at the highest hierarchical
level. So the "Standalone" Files will be at the top of the diagram.

In the last article we set up the following variables to define box size and
distribution and label size and position relative to the companion box.
Let's do so again. This will let us tweak the presentation by changing
variable values instead of putting hard-and-fast numeric values into our
code. Create the following variables as local variables of the "$construct"
method with the associated data types and initial values given:

boxHeight           Number (Long integer)              46
boxWidth            Number (Long integer)             120
labelHeight         Number (Long integer)              19
labelWidth          Number (Long integer)             108
labelLeft           Number (Long integer)               5
labelTop            Number (Long integer)              13
colWidth            Number (Long integer)             140
rowHeight           Number (Long integer)              60
maxColumns          Number (Short integer)              4
hOffset             Number (Long integer)              20
vOffset             Number (Long integer)             180

The window class I am using has a "width" property value of "590". With the
"colWidth" and "hOffset" values given above, this allows "4" columns to fit
on the window without any horizontal scrolling required. The "labelLeft" and
"labelTop" properties are offsets that position the given label dimensions
in the center of the given box dimensions. Since I placed the two headed
list fields side by side at the top of this window, the "vOffset" value was
chosen to assure that the first row of boxes is placed below the lower edges
of the list fields.

We will create our box-and-label pairs within a pair of nested loops in the
"$construct" method of the window. The outer loop will be used to switch
from level to level within "fileList" (using "thisLevel" again as the level
counter) while the inner loop steps through all Files at the current level,
creating the box and label at the proper position and setting property
values as we did last time. Here are the loops with the object creation code
removed:

For thisLevel from 0 to maxc(fileList,level) step 1
    Do fileList.$search($ref.level=thisLevel)
    Do fileList.$first(kTrue,kFalse)
    Calculate count as 0
    Repeat
        ;  create box
        ;  create label
        Calculate count as count+1
        Do fileList.$next($ref.$line,kTrue,kFalse) Returns #F
    Until flag false
    Calculate vOffset as vOffset+(1+int((count-1)/maxColumns))*rowHeight
End For

For each level, beginning with the "Standalone" Files and continuing through
the lowest level of "Subordinate" File, we select the Files at the current
level and step through them one at a time. For each File, we create a
box-and-label pair. When all these have been created for the current level,
we reset the "vOffset" value to begin at the next row.

The code that replaces the "create box" comment line begins with the command
that creates and positions the basic box:

Do
$cinst.$objs.$add(kBobj,vOffset+int(count/maxColumns)*rowHeight,hOffset+mod(
count,maxColumns)*colWidth,boxHeight,boxWidth,kFalse,kFalse) Returns newItem

This uses a simple algorithm for setting the "top" and "left" property
values of each successive box in the group in such a way that they are
distributed across the columns of our grid and onto additional rows if
necessary. This is then followed by additional method lines that set other
properties of the box as we did last time:

Calculate newItem.$name as con('box',fileList.fileName)
Calculate newItem.$dragmode as kDragObject
Calculate newItem.$dragrange as kRangeWindow
Calculate methodList as
$clib().$windows.orgchartTemplate.$objs.box02.$methods.$makelist($ref.$name)
For methodList.$line from 1 to methodList.$linecount step 1
Do newItem.$methods.$add(methodList.C1) Returns newMethod
Calculate newMethod as
$clib().$windows.orgchartTemplate.$objs.box02.$methods.[con('//',methodList.
C1,'//')]
End For

We substitute code for the "create label" comment line in a similar manner.
The line that creates the basic "label" object is:

Do
$cinst.$objs.$add(kEntry,vOffset+labelTop+int(count/maxColumns)*rowHeight,hO
ffset+labelLeft+mod(count,maxColumns)*colWidth,labelHeight,labelWidth,kFalse
,kTrue) Returns newItem

This is basically the same algorithm as used above for the "box" object, but
includes the offsets to center the "label" inside the "box". This is then
followed by the code that completes the details of the label as before:

Calculate newItem.$name as con('label',fileList.fileName)
Calculate newItem.$effect as kBorderNone
Calculate newItem.$align as kCenterJst
Calculate newItem.$dataname as con('fileList.',fileList.$line,'.fileName')
Calculate newItem.$dragmode as kDragObject
Calculate newItem.$dragrange as kRangeWindow
Do newItem.$methods.$add('$event') Returns newMethod
Do newMethod.$lvardefs.$add('boxRef',kItemref,0,0,kFalse) Returns newVar
Calculate newVar.$objinitval as
"$cinst.$objs.[con('box',mid($cfield.$name,6,100))]"
Calculate newMethod.$methodtext as
$clib().$windows.orgchartTemplate.$objs.label02.$methods.//$event//.$methodt
ext

The entire double loop construct is then followed by the command that is
used to tie together the boxes involved in the hierarchical part of the
diagram with connecting lines:

Do method createLines

This completes the "$construct" method of the "fileDiagram" window class.
The window class itself will most likely require at least vertical scroll
bars for even a modest File structure. It can be made wider to accommodate
more columns per level within its viewable area.

The resulting diagram will still not be perfect. There may well be the
occasional crossing of connecting lines behind some "box" object or other
anomalies requiring some final tweaking by the user - but that's why we made
the "boxes" draggable in the first place!

Options

More complicated algorithms that follow each branch of the hierarchy instead
of simply distributing "boxes" by "level" may meet with more success in some
cases, but these can also break down with increasing complexity of the File
structure. In fact, many realistic File structures could use a third
dimension to display them to advantage!

We can also add other options to the "appearanceMenu" Menu class, like
opening a window to display the variables defined for a File Class, etc. We
could also track the positions to which the user drags each "box", save this
information in the database and use it to rebuild the diagram at some later
time.

We are only limited by our imaginations!

SQL Alternatives

If we use Omnis Studio as a front end to a SQL database, we will have Schema
Classes instead of File Classes in our library. Connections do not exist in
this environment. Relationships are still stored in the "Many" table, but
they are stored as "foreign key columns" that have nothing special to
distinguish them. So we have to make them distinguishable!

There are two simple ways of storing relationship information in a Schema
Class in such a way that we can extract it for charting purposes as we did
the Connection information in this article.

1) We can use the "Description" slot for a column description to store some
standard annotation indicating that the column is a foreign key and to which
Schema Class it points.
2) We can use the "description" property of the Schema Class itself to store
a comma-delimited list of Schema names to which foreign keys within it
point.

Either of these techniques will require a little more work than the
Connection example used for this article, but both are viable and each
offers relative advantages.

Final Words

I am just returning from presenting Omnis Studio classes on the West Coast
of the United States and wanted to make sure this article reached Raining
Data in time for publication. Rather than recap all the variables and
methods used in this example here, I will post a complete library containing
this example on my web site (http://www.polymath-bus-sys.com/) by Thursday,
30 May, 2002.

I hope you were able to follow the example exercise and that it is of some
use to you!


========================================

I hope you've found this issue of the Omnis Tech Newsletter both interesting
and informative. Please send me your comments and feedback, and include
suggestions for future articles if you like. We would like to hear from
you...

Regards,
--Andrew Smith.
Omnis Technical Newsletter Editor
Email: editor@omnis.net

========================================

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 Raining Data.
(c) Copyright Raining Data, Inc., and its licensors 2002. All rights
reserved.
Omnis(r) is a registered trademark and Omnis 7(tm), and Omnis Studio are
trademarks of Raining Data UK Ltd. Other products mentioned are trademarks
or registered trademarks of their corporations.

========================================

To unsubscribe from this newsletter or change your subscription options,
please go to:
www.omnis.net/newsletter