[Omnis-Newsletter] Omnis Technical Newsletter

omnis-news-admin@omnis.net omnis-news-admin@omnis.net
Wed, 15 May 2002 16:58:03 +0100


 May 15th, 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 the
second part of his 'Triple O' exploration of the object oriented features in
Omnis Studio. Geir's excellent introduction to OO is very useful for
developers moving from Omnis 7 to Studio which will help the conversion
process. In the second article, David Swain shows you how to create an
organization chart dynamically using the templates developed in the previous
issue.

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 2, by Geir Fjaerli.
-Get Omnis Studio FREE OF CHARGE
-Building Charts: Lurking with Lists, 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 2.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net

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

In the last issue of the newsletter we started an introduction to object
orientation. I said that OO is basically all about building components, and
in that it is similar to building with Lego bricks.

We looked at the history of OO, and some of the basic concepts. These
concepts as we noted are rooted in real life metaphors, and based on natural
science, including the works of Linnae and Darwin. The root of this is
classification, we order everything into species and individuals, or classes
and instances as we call them in OO terminology.

I recommend that you quickly reread the first part before starting on this
one.

We ended up with a formal definition of OO to measure Omnis Studio against.
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

Note: I had the order wrong in the last issue, sorry about that.

While even this definition is subject to argument, it is as good as any, and
we shall use it to explain OO in more detail. As we move into the theory, we
shall also look at the implementation of it, in Omnis as well as other "OO"
tools.

Note that the following will make references to Omnis 7, to explain the
differences between Studio and the old version. If you are new to Omnis, you
may simply disregard these references. I shall also be comparing to the
implementations in other tools like Smalltalk, Java and C++.

Let us look at the implications of each requirement, and how Studio fits.
Since we need to define what an object is, we will start with the 3rd
requirement, leaving the first two until we have the knowledge needed to
investigate that subject.

OBJECTS ARE INSTANCES OF A CLASS.

This third requirement presents us with one of the foundations of
object-oriented languages, the split between the class and the instance.

Basically, the class is the definition of the object, while the instance is
the runtime object. Or put another way: The class is what you design, the
instance is what you use. This makes sense if you refer to the natural
model, you have the make and model of a car, say Volvo V70, and then you
have the car you drive around in which is one of many cars of that model.

So the concept of classes and instances closely resembles the real world
from which it is modelled.

When you see an object (an item of a certain type), you expect it to behave
in a certain way. So when you see a door, you expect to be able to push the
handle and the door open. Now the actual door may be a glass door with brass
handles, or a wooden door or a plastic one, but being a door, you know its
purpose.

See a dog, and you expect it to have four legs and bark. It may be a small
dog or large, young or old, but the type is the same.

The same way there are a number of different makes of cars, and thousands of
cars of each make and model, but they all have the same basic functionality.
So no matter the car, if you can drive one, you probably can handle most
others. There may be differences between various models, they specialise
certain parts of the car, but they are all subtypes of the same basic
concept called car, and inherits its characteristics.

In Omnis 7 we called the classes formats, with the exception of open windows
you could not access the instance. (Though Omnis offered some level of 4GL
control.)

SOME ARE, SOME ARE NOT?

The ability to instantiate classes is true for most but not all class types
in Studio. And I would already like to introduce a little rule of thumb: "If
it can be instantiated, use it, if it can not, don't!"

Basically, in OO theory the word class is reserved for elements that can be
instantiated. But in Omnis they have opted to use the word class about all
types of formats, even though some of them are simply definition formats and
not classes in the OO sense of the word. Personally I wish they hadn't, but
we shall have to live with and use the chosen expressions.

The classes that can be instantiated include windows, reports, menus,
toolbars, tasks, objects, tables, and remote tasks and forms. We'll look
closer at instances in a moment to see that there are differences between
them. Note that the following assumes you have followed out my programming
tutorial in previous issues, and so we shall not be discussing every class
type in detail.

A couple of class types cannot be instantiated in their own right; these are
the schema and query classes. Schemas and queries are simply representations
of database tables or view definitions, and are instantiated through table
classes. Some of you may have observed that in new Studio versions you don't
need to define a table, you can define your list or row variable directly
from the schema. Still this statement is correct, it simply means that Omnis
uses a built in generic table class to create the instance if you haven't
defined one.

Then we're left with three classes that have no place in an object-oriented
application. Two of them are familiar oldies: The file and search classes
are effectively identical to the Omnis 7 file formats and search formats.
They build on the Current Record Buffer (CRB), which is a single global
buffer. The third is the code class. This is a global code repository that
facilitates conversion of common Omnis 7 application styles, when we were
using menus as containers for subroutines. More on the Dos and Don'ts later.

THE CLASS IS THE REPOSITORY FOR OBJECT BEHAVIOUR.

So we have a class and we have an instance. The next two requirements
identify the responsibilities of each. Requirement 4 says: "The class is the
repository for object behaviour."

Now the object behaviour means what the object does for a living, this
behaviour is implemented as methods in the class. In this sense a method
equals a procedure in Omnis 7. Since there is one class for all instances of
say a certain window, this means that all instances will have a common
behaviour.

Again, this is modelled after the "real" world. It is what allows us to
assume the behaviour of any object or creature before we see it. Go into a
shop and look at a stereo system, you can assume that it will play sound, it
doesn't have to be turned on and playing at the time for us to assume it
will play music. Reading the specs will tell you if it is what you are
after. Obviously you will eventually want to try it out before buying, just
as you will want to test a window to make sure it performs as desired in
your application.

There is one important difference between classes and instances in
programming and in real world examples. In programming, you can quite safely
assume that all instances of a given class perform exactly the same under
the same conditions. That is not true for say cars, one due to inaccuracies
of manufacturing as well as the wear and tear of everyday use. The basic
behaviour will be the same for all of them though.

One thing is the same: Just as two drivers will handle the same type of car
very differently, two users will handle the same application very
differently. And the requirements and expectations of the users is a vital
part of this. Fitness for purpose depends on the user.

Now Studio (as did Omnis 7) allows you to modify or create methods using
notation at runtime. Omnis facilitates this by creating a temporary method
in the instance. This is obviously a very powerful feature, especially if
you're building developer tools in Omnis, but it is not recommended for
normal use. Especially since you cannot debug code that doesn't exist.  We
shall see later that object orientation makes this a lot less needed anyway.

So there is our class. Now we move onto the instance, or the object.
Remember that according to req. no. 3 the object is an instance, and so the
two terms are interchangeable.

OBJECTS HAVE THEIR OWN MEMORY.

The memory of the object is often referred to as the State of the object. It
is stored in the instance. This means that each instance holds its own set
of values for the variables defined in the class.

This enables each instance to have unique values, and therefore enables the
application to work with multiple instances, or what is known as a Multiple
Document Interface (MDI). For instance I could open customer windows for two
or more customers completely independent of each other.

The variables used to hold the state of the object are called Instance
Variables in Omnis Studio. The instance variable is only visible to a single
instance of a class. (Directly or inherited in subclasses of the class.)

This by the way does not imply that the corresponding Class Variables are
stored in the class. All variables are memory structures. Instead the term
class variable denotes the scope of the variable, and means that the values
of class variables are visible too all instances of the class. In fact you
do need to have an instance open to access it, even if it retains its values
when all instances are closed. Similar structures are available in other
languages, e.g. C++.

INSTANCES

So in summary: An object is simply an instance of a class at runtime. It
holds its own set of values, also known as state, and shares the methods of
the class with any other instance of the same class.

The process of creating an instance is called construction, and the method
that performs it is called a constructor. Constructing an instance is e.g.
opening a window, installing a menu or toolbar etc. There is also a
corresponding destruction of the instance, when the window is closed, the
menu or toolbar removed etc.

INSTANCE CREATION IN OMNIS

In Omnis as a 4GL, most of the task of construction and destruction is done
for you. This is unlike say C++, where you would be faced with the task of
allocating memory for your object. Still Omnis will let you perform your own
initialisation tasks. This is done through special messages. When an
instance is created, it receives a $construct message, and you can respond
to this message by putting your initialisation code in the class method of
the same name. Similarly the instance will receive a $destruct message when
closed.

$construct is run after Omnis has completed constructing the instance,
including all controls, so you can safely assume that the instance is
complete. In the case of visual objects like windows, menus and toolbars
Omnis will not draw the instance on screen until $construct is finished, so
you can set initial values etc without having to worry about unwanted
flicker. Note that some commands will force a redraw, say if you have an
Enter data in $construct.

$destruct similarly is the last thing run before Omnis starts destructing th
e instance, and can be used for tidying up after you. In some languages the
object has to do this, including freeing used memory etc. Omnis however does
that for you, so you will find less of a need to have a $destruct. Please
note that Object and Table classes do not receive a $destruct, we'll see why
they are different soon.

EVERYTHING IS AN OBJECT

So onto the first requirement: "Everything is an object". This is maybe the
most theoretical of all the rules, only Smalltalk and maybe some of the
academic languages come close to this. Because "everything" taken literally
would have to mean things like variables, or even operators in comparison,
adding a level of complexity to both implementation and execution which
would not be desirable. As everyone appreciates, in the real world "faster
is better". So most tool vendors will compromise to make things practical.
Also since many OO languages are built on top of procedural ones, the
foundation will be the latter.

So most tools satisfy this rule only down to a certain level. In Studio that
would be the format level, to use the Omnis 7 expression. That is windows,
reports, toolbars, menus etc. The Object classes in Studio are special in
that they can be instantiated at any scope through variables. We will come
back to that later.

THE TERM "OBJECT" IN OMNIS STUDIO

Before we do that, we must mention one little source of confusion: In the OO
definition, "Everything is an object." So an object can be any type of
thing, as long as it meets our basic requirement, "objects are instances of
a class."

In Studio however, we have what is known as the Object Class, which is a
specific type of class. This of course is a contradiction in terms, since an
object is an instance, and not a class. Hopefully we shall be able to
separate the two. In most discussions the above requirements apply to the
other instantiable class types as well, including windows, reports etc. More
on that as we go along.

Also, we frequently talk about window and report objects, meaning fields and
buttons, when discussing Omnis. These are not based on classes or otherwise
meet our requirements, and therefore they are not objects in the
object-oriented definition of the word. A better term is "controls". The
lack of field level objects in Studio is due to the amount of worked
involved in its implementation, and would also impact the performance of
Studio. It would have several advantages though, especially as it would
allows fields to inherit behaviour and attributes. (More on inheritance
later.)

STUDIO OBJECT CLASS VS. OTHER CLASSES

We just said that object classes are different from the others. How is that?
Well, the major difference is that Object classes are instantiated as
variables. This in fact is not something special to Omnis, this is the
normal form of instantiation of functions in most other tools, such as
Smalltalk, C++, Java, or whatever.

Anyway, this means that the only way to access the object is through the
variable, and that in theory it is only visible to methods within the scope
of the variable. So if your object is instantiated in a local var, it will
only be visible to the executing method.

The same is true for Table classes. They are instantiated in variables of
type list or row. (A row is a list with only one line.)

Other classes are instantiated "on the heap" and accessed by name through
groups of instances. So you have a $iwindows for all open windows, a $imenus
for installed menus, etc. There is no parallel $iobjects or $itables, the
object/table class instance can only be accessed through the variable it is
bound to.

You can have "local" instances of other classes as well. Normally a window,
menu or toolbar would belong to the $windows, $menus or $toolbars groups
respectively. But a subwindow instance would belong to the group of object
on the windows, called $objs. Similarly a toolbar in a particular window
would belong to that window instance.

This difference is also the reason why object class instances do not get any
$destruct message. Since you have no direct control over when a variable is
cleared, and cannot stop it happening, having another method (your
$destruct) suddenly kicking in in the middle of the object being cleared may
have unpredictable consequences. Since its reliability is one of Omnis'
advantages (you cannot easily program Omnis to crash like other languages),
that may be a sound trade off.

So, having laid the groundwork for how objects are created, next issue is
how they communicate. We shall devote the next part to messaging.

Until then, take care!


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

Get Omnis Studio FREE OF CHARGE

You can get your hands on a copy of Omnis Studio 3.01 Standard Edition FREE
OF CHARGE if you pick up a copy of either the June issue of MacFormat
(#117), or the July issue of PC Plus (#191). Both these magazines feature
Omnis Studio 3.01 Standard Edition on the cover CD/DVD. In addition, we are
offering readers a special discount price to upgrade to Studio 3.1 or 3.2
(when it ships mid-2002) for the Standard Edition only.

As well as featuring the software, MacFormat has a 2-page masterclass
tutorial showing you how to build a web application that displays a book
catalogue on the web.

Both these magazines are published in the UK, but they have subscribers in
all parts of the world, especially PC Plus which enjoys a circulation of
over 100,000. With these promotions, Omnis Studio is getting a lot of good
exposure, especially in the web application development market. More
promotions are planned in the near future, so watch your news stands!

For further details about the MacFormat CD/DVD, and to subscribe, please go
to:
http://www.macformat.co.uk

For further details about the contents of the PC Plus CD/DVD, and to
subscribe, please go to:
http://www.pcplus.co.uk/article.asp?id=32281


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

Building Charts: Lurking with Lists
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com

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

Programming in Omnis Studio is a lot like gourmet cooking. There's the
preparation stage where all the ingredients are made ready and put into nice
containers for easy access, the initial cooking of sauces and such, the
cooking of the major dishes from many of the items prepared earlier, and the
final assembly and presentation of the meal (with a few intermediate
tastings to be sure things are going in the right direction!). The advantage
Omnis Studio programming has over cooking is that we can always remove an
ingredient if we find we've used the wrong thing - or too much of the right
thing!

In the last article, we began creating a collection of technologies that we
can use over and over again (kind of like a "sourdough starter") for making
charts within our applications. This time we're going to refine that
technology slightly (actually replacing an ingredient or two) and then
create a new window to build an actual organization chart dynamically using
some of the bits from our "template" window as building blocks. In the next
article, we'll take this even a step further and build a window that charts
the File structure of a native Omnis Studio application (with some hints as
to how you might also do this using Schema classes).

Slight Change In Direction

The main purpose of the previous article was to introduce Shape Fields, so
we used them for all the major elements of our proposed organization chart:
boxes, labels and lines. We already discussed that Entry fields might be
better used for modifiable labels in that article and we will learn in this
one that background lines are ultimately more suitable for our purposes than
Shape Fields posing as "lines". But there were still some useful things to
learn from those exercises and we will continue on for a bit longer using
"line" Shape Fields for that reason.

For charts involving more than two boxes connected by a single line, we need
to make a few modifications to the techniques introduced in the last
article. The first is to change the name of our window class to
"orgchartTemplate". This will be more appropriate for the eventual use of
this window.

We also need to do a few experiments with a slightly more complex structure,
so let's add a third box and label (named "box03" and "label03") and a
second line (named "line02") to the window. This is best done by
"drag-duplicating" the existing objects to retain their methods and special
properties. (Use box and label "02" as we want to have Entry field labels.)
If you are not familiar with this technique, select the objects to be
duplicated (either drag a rectangle that touches them all or click on one
and then Shift-click others to add to the selection), hold down the
Option/Control (Mac/Win) key and click one of the selected objects and drag
to a new position while continuing to hold the Option/Control key down.
(Release the mouse button before the modifier key when you are ready to
"drop" the duplicates.) Arrange the items on the window class as follows:

Box and label "01" at the top and in the center of the window
Box and label "02" below and to the left of "01"
Box and label "03" below and to the right of "01" (at the same vertical
position as "02")
Line01 connecting boxes "01" and "02"
Line02 connecting boxes "01" and "03"

This should yield a configuration looking somewhat like a pyramid without a
base - a simple example of a hierarchical structure diagram.

Companion References

The more complex our diagram becomes, the more we need a "generic" technique
for handling the movement of the "companion" to the box or label the user
drags. To help with this, let's agree on a simple naming convention. When we
dynamically create a new box-and-label set to represent record from the
database, let's name the box "box*" and the label "label*", where the "*"
represents some unique identifier for that record. This gives our box and
label names a common string, so knowing one of these names (the one dragged,
let's say) gives us the name of the other (the one we need to
programmatically move) with very little effort.

In the "$event" method for a box, we can create a local variable of "Item
reference" type named "labelRef". We can set the initial value calculation
for this variable to"

$cinst.$objs.[con('label',mid($cfield.$name,4,100))]

That is, we concatenate the string "label" with characters from the name of
the current (box) field beginning with the fourth character (skipping past
the string "box") and taking as many characters as remain. ("100" was chosen
as an arbitrarily large number which would not be exceeded by the longest
name I am ever likely to give a field. If you use longer names for fields,
feel free to increase this to "1000" or more.) We can then use

On evWillDrop
    Process event and continue
    Calculate labelRef.$left as labelRef.$left-(origX-$cobj.$left)
    Calculate labelRef.$top as labelRef.$top-(origY-$cobj.$top)

for moving the companion label instead of the code we used last time where
the exact name of the companion field was hard-coded. In the same way, we
can adjust the "$event" method of each label field with a local variable
named "boxRef" that has an initial value calculation:

$cinst.$objs.[con('box',mid($cfield.$name,6,100))]

(notice that "label" is 5 characters, so we must skip past those) and that
deal with the "evWillDrop" event like this:

On evWillDrop
    Process event and continue
    Calculate boxRef.$left as boxRef.$left-(origX-$cobj.$left)
    Calculate boxRef.$top as boxRef.$top-(origY-$cobj.$top)

We also need to deal with the connecting line(s) generically. Since there
can potential be more than one, the simplest thing to do would be to
"broadcast" a message to all objects on the window to execute the "$reset"
method:

Do objRef.$sendall($ref.$reset())

After all, only fields containing such a method can even respond. But as our
diagram gets larger, this becomes increasingly less efficient. Perhaps this
is a good time to get to the main purpose of this article: using lists to
manage the relationships among our boxes.

Using Lists To Manage Structure(d) Information

Lists are used as repositories for the structured information required by a
variety of Omnis Studio components. Sidebars, Icon Arrays and Graphs all
fundamentally represent the contents of list variables, so why not use list
variables to control the relationships among components like the ones we are
exploring to create a complex chart?

This will help us in a couple of ways, the most important of which is that
with dynamically created boxes and lines, we have nowhere to store the
relationship information. Remember, we stored that in the "text" property of
the design version of our line in the window _class_ last time. When we
begin building our chart dynamically, there won't be a design-level
counterpart to any of our components - and the "text" property is not
available to us for these lines of ANY kind within a window instance.

The structure of this list is very simple. Each line of the list corresponds
to a "line" we want to draw between two boxes. There are three basic pieces
of information we need for each line: some unique information that
identifies the "line" in question, some unique information that identifies
the box from which the line is drawn, and equivalent information for the box
to which the line is drawn.

To create this list, then, we need four variables: the list itself and
variables for the three columns. Since we need to access this from a number
of places within our window instance, we need to make each of these instance
variables. Let's use the following names and properties for our list to
manage the connecting line information:

connList      List
connLineID    Character    10
connFromID    Character    50
connToID      Character    50

I chose "50" as an arbitrarily long length for a name that might be
associated with a box. In our current example, this is definitely more than
adequate, but other uses of this technology may have longer key values.
Since we are more "in control" of the identifiers assigned to our lines, I
have set that to a much smaller maximum length.

In the "$construct" method of our template window class (where we have
created all our components in design mode), place the following lines of
code:

Do connList.$define(connLineID,connFromID,connToID)
Do connList.$add('line01','box02','box01')
Do connList.$add('line02','box03','box01')

This yields a list of our lines that contains the information we were
storing in the "text" property of the "line" objects at the class level. We
can now use this information in a couple of ways. We can:

1) know which lines connect to a specific box (e.g., the one we are about to
drop) and construct a "$sendall" method line that involves only those lines
2) construct a method line within the "$reset" method of each line that
makes it "self-aware" (i.e., it knows which boxes it connects)

We can accomplish the first chore with this line placed in the "$event"
method of each box object in place of the line that read "Do
objRef.$sendall($ref.$reset())":

Do
connList.$sendall(objRef.[$ref.connLineID].$reset(),$ref.connFromID=$cfield.
$name|$ref.connToID=$cfield.$name)

Let's break this down a bit. There are two parts to a "$sendall" message:
the message to be sent and an optional set of search criteria to limit the
scope for the sending of that message. The "$sendall" is applied here to the
group of lines of our connection list. (Remember that last time we created
the instance variable "objRef" as an "Item reference" variable initialized
to "$cinst.$objs".)

The most important thing to learn from this exercise is that "$sendall"
specifies a message to be sent FOR each member of the group to which it is
applied, not necessarily TO each member of that group. (That is, "$sendall"
is a one-line "For each member of the group" loop.) Notice that we are not
sending a message to lines of this list. Rather, we are sending a message to
certain objects on our window instance BASED UPON information gathered from
each line of the list (the identifier of our "line" objects).

The second parameter of this "$sendall" is used to only send the message for
list lines where the current box field's name appears in either the
"connFromID" or "connToID" column. So all lines of "connList" are scanned,
but only those where these criteria are met cause the message to be sent.

This is fine for dragging our box objects, but what about the labels? We can
do something very similar, but we must specify the name of the "companion"
box object rather than that of the current field. Fortunately, we already
set up a variable named "boxRef" to help us, so the line used in the
"$event" method of our label objects becomes:

Do
connList.$sendall(objRef.[$ref.connLineID].$reset(),$ref.connFromID=boxRef.$
name|$ref.connToID=boxRef.$name)

Self-Aware "Line" Objects

In the "$reset" method of our "line" objects, each line needs to know which
boxes it must connect. Having established this, it must poll each box to
determine the box's midpoint horizontal and vertical coordinates and use
this information to set its own "top", "left", "height" and "width" property
values.

We can do this by having the "line" object find its line in the connection
list and use that information to get the names of the "From" and "To" boxes.
We can then call the "$getcenterx" and "$getcentery" methods of those boxes
as before to set the new line coordinates.

In the last article, we established two local variables in the "$reset"
method of our "line" object named "obj1Ref" and "obj2Ref" to hold the names
of the two boxes. We derived these names from information stored in the
"text" property of the "line" object itself (but in its "design" version in
the window class). Now we need to do this a bit differently. Since the box
names we require are on the line of the connection list associated with the
identifier for our "line" object, we can establish another local variable
named "lineNumber" of type "Number - Long integer" and then populate it by
doing a quick search of the connection list for the line containing the name
of our "line" object. We can then use this line number to retrieve the
values of the "connFromID" and "connToID" columns on that line into our
"obj1Ref" and "obj2Ref" variables.

Here is the completed code for our generic "$reset" method:

Do
connList.$search($ref.connLineID=$cfield.$name,kTrue,kFalse,kFalse,kFalse)
Returns lineNumber
Calculate obj1ref as con('objRef.',connList.[lineNumber].connFromID)
Calculate obj2ref as con('objRef.',connList.[lineNumber].connToID)
Calculate $cfield.$left as [obj1ref].$getcenterx()
Calculate $cfield.$top as [obj1ref].$getcentery()
Calculate $cfield.$width as [obj2ref].$getcenterx()-[obj1ref].$getcenterx()
Calculate $cfield.$height as [obj2ref].$getcentery()-[obj1ref].$getcentery()

The search criterion simply seeks lines where the "connLineID" column value
equals the current "line" object's name. (There can be only one if we did
everything right.) The additional parameters of "$search" instruct Omnis to
start at the first line of the list, not to search in reverse order of the
list lines, not to select matching lines and not to deselect unmatching
lines.

These last two are important. We're not looking to "select" lines in this
list. We are  hoping to set a specific line as the "current" line (a totally
separate attribute). When done in this manner, the "$search" method returns
the line number of the first line in the list to meet the search criterion.
We chose to have that value applied to the "lineNumber" variable. We then
use that value to retrieve the appropriate "connFromID" and "connToID"
values from the list.

Having made all these changes to the code we created for the previous
article, we can now instantiate our window and drag boxes and labels
wherever we wish with the companion object and appropriate line end
following.

Back To Background Lines

Well, not quite. If you play with this a bit, you will soon notice that
there are certain places to which we can't move our boxes or labels. Since
our "line" objects are foreground objects, their entire area (the rectangle
bounded by their "top", "left", "height" and "width" properties) gets in the
way of our drag operations, disallowing dropping. Even setting the
"dropmode" property of our "line" objects to "kAcceptAll" does nothing to
help us, since technically we can't drop one object onto another object that
isn't a "container". So it is tedious at best to move box-and-label "01"
closer to the other two, since the two lines take up all the area below the
midpoint of "box01".

Although "line-shaped" Shape Fields were very instructive in our last
article, they are not the best choice for the entire project. We are better
off with actual "line" background objects for our connecting lines because,
being "background objects", they don't get in our way! But what do we lose
by making this change?

The only thing we lose is the ability to have a "$reset" method in the
"line" object itself. Omnis Studio is not "object oriented" enough (this is
NOT a complaint) to allow methods in background object (except in reports,
but that's another article!), so we have to manage them another way. My
choice is to use a public method at the Class level in our window class
named "$linereset". (Yes, an argument could be made for creating a "helper"
Object class to manage our lines, but I don't want to complicate this
example any more than instructionally necessary - and I only need one
method, after all.) We can copy the method lines from one of our "$reset"
methods as a starting point, but we will have to make some adjustments for
the new scope of this method.

Rather than replace the "line" objects we already have in our template
window with background line objects, let's just treat them as though they no
longer can contain methods. this simulation will help us develop the
technology we eventually want to export to the "real" organization charting
window.

Create a class method named "$linereset". Copy the lines from one of our
"$reset" methods and paste them into "$linereset". Now let's see what we
have to change to make this work.

In the "$reset" method we created above, we used information about the
object containing the method to locate a line in the connection list, store
that line number in a variable and use that information to retrieve the
identifiers of the boxes we want the line object to connect. In "$linereset"
we no longer have the line object information available to us through
"$cfield" because we're now operating in a "class" method. Not a problem
though. Since we're beginning this process in the "$event" method of a "box"
or "label" object and performing a "$sendall" for a number of lines in our
connection list, why not just pass the line information from the connection
list into "$linereset" as a row variable? This will actually streamline our
process a bit as we don't have to repeatedly poll the connection list for
more information.

Create a parameter for "$linereset" and name it "lineParams". Make it a row
variable. if we pass this information correctly, this row variable should
have the same column structure as our connection list, so we can assume we
have the same column names for "lineParams" in "$linereset" as for
"connList" and access them directly. We can remove the first line of this
method completely since we no longer have to find a line number - and once
we've replaced all references to it, we can remove the local variable
"lineNumber" as well.

Without detailing this further, here is the resulting code for "$linereset"
(you should be able to follow this without explanation):

Calculate obj1ref as con('objRef.',lineParams.connFromID)
Calculate obj2ref as con('objRef.',lineParams.connToID)
Calculate objRef.[lineParams.connLineID].$left as [obj1ref].$getcenterx()
Calculate objRef.[lineParams.connLineID].$top as [obj1ref].$getcentery()
Calculate objRef.[lineParams.connLineID].$width as
[obj2ref].$getcenterx()-[obj1ref].$getcenterx()
Calculate objRef.[lineParams.connLineID].$height as
[obj2ref].$getcentery()-[obj1ref].$getcentery()

But how must we change the method line that invokes line reset to both call
this method and to pass the appropriate line of the connection list? It's
almost too simple! Here's the code for performing this operation from the
"$event" method of one of the "box" fields:

Do
connList.$sendall($cinst.$linereset($ref()),$ref.connFromID=$cfield.$name|$r
ef.connToID=$cfield.$name)

Notice that we are sending the same message for each qualifying line in
"connList", but that we are sending slightly different information each
time. The "$ref()" parameter of the "$linereset()" method call refers to the
current line of "connList". Adding the parentheses resolves this to the
actual contents of the line rather than leaving it as a reference.

The full version of "$event" for a "box" object is now:

On evWillDrop
    Process event and continue
    Calculate labelRef.$left as labelRef.$left-(origX-$cobj.$left)
    Calculate labelRef.$top as labelRef.$top-(origY-$cobj.$top)
    Do
connList.$sendall($cinst.$linereset($ref()),$ref.connFromID=$cfield.$name|$r
ef.connToID=$cfield.$name)

The equivalent code line for this event in one of our "label" objects is:

On evWillDrop
    Process event and continue
    Calculate boxRef.$left as boxRef.$left-(origX-$cobj.$left)
    Calculate boxRef.$top as boxRef.$top-(origY-$cobj.$top)
    Do
connList.$sendall($cinst.$linereset($ref()),$ref.connFromID=boxRef.$name|$re
f.connToID=boxRef.$name)

If you've made all these changes, go ahead and test the result by
instantiating the window. If it isn't working, go back and check very
carefully that you have created all the necessary instance, local and
parameter variables and have properly referenced them within your code.

The "Real" OrgChartDisplay Window

Once we have the technologies in our template window working as desired, we
can now begin constructing the "orgchartDisplay" window class. Create a new
window class with the following property values (others at your own
discretion):

name                orgchartDisplay
modelessdata        kTrue
growbox             kTrue
horzscroll          kTrue
vertscroll          kTrue

Make sure the window class contains at least a "$construct" method, then
copy the "$control" and "$linereset" method from the "orgchartTemplate"
window class and paste them into the "orgchartDisplay" window class. This
should also create three instance variables for the new window class
(objRef, origX and origY) and the local and parameter variables of the
"$linereset method".

At this point, someone is bound to bring up, "Why not just use inheritance
and make this new window a sub-class of the one we've been working on?" A
fair question. There are three answers:

1) I'm trying to demonstrate other things and this is complicated enough
(There will be such an example soon...)
2) We didn't properly set up our template window (or its methods) for use as
a superclass
3) We would end up inheriting all those fields on the template window in
addition to the methods - and we can't override or get rid of those fields.
I still want them so we can copy methods from them (one of the things I'm
demonstrating with this exercise), so we'll leave inheritance for another
time.

For the rest of the exercise, we'll look at how to dynamically create boxes
and labels mapped to data and lines to connect them.

Building The Node List

The first thing we need to do is to build a list of the nodes in our
organization chart. For demonstration purposes, we'll just add some lines to
a list to simulate the many kinds of data access Omnis Studio and mvDesigner
programmers might use to populate such a list. For this article, we'll deal
with a simple hierarchy - one where each node only connects to one node
above it in the diagram (much like Omnis Studio notation).

Our list will look much like a list we might create to populate a Tree List
object. Each line must contain a node identifier, some labeling text for the
display, and information that uniquely points to the node above it in the
hierarchy (which will be empty for the top level node). Let's also include a
column for the "level" of a node within the hierarchy. This means that we
will need a list variable and four variables for defining the columns of
that list. They should all be of instance scope so we can work with them
from anywhere within our window instance. Create the following instance
variables in the "orgchartDisplay" window class:

nodeList            List
nodeID              Character
nodeName            Character
nodePointer         Character
nodeLevel           Number (Long integer)

To define this list, begin the "$construct" method of our window with the
following method lines:

Begin reversible block
    Set current list nodeList
End reversible block
Define list {nodeID,nodeName,nodePointer,nodeLevel}

We will also want to establish our connection list as we did in our template
window class, so copy this line of code from the "$construct" method of
"orgchartTemplate" and append it to the "$construct" method of
"orgchartDisplay":

Do connList.$define(connLineID,connFromID,connToID)

Notice that this saved us the trouble of defining the four variables
involved. Their definitions were also copied into the instance variable set
of our new class.

The next thing we must do is populate our node list with some test data. I
have chosen to do this in a private method subroutine at the class level so
it does not disturb the flow of the $construct method. Create a new class
method named "populateNodeList" and give it the following lines:

Add line to list {('0001','President','',1)}
Add line to list {('0002','VP Sales','0001',2)}
Add line to list {('0003','Sales Asst 1','0002',3)}
Add line to list {('0004','Sales Asst 2','0002',3)}
Add line to list {('0005','VP Eng','0001',2)}
Add line to list {('0006','Eng Asst 1','0005',3)}
Add line to list {('0007','Eng Asst 2','0005',3)}
Add line to list {('0008','VP Finance','0001',2)}
Add line to list {('0009','Fin Asst 1','0008',3)}
Add line to list {('0010','Fin Asst 2','0008',3)}

This will quickly create a symmetrical hierarchy of ten nodes on three
hierarchical levels - enough to test our dynamic chart building technology.
now append the following line to the "$construct" method:

Do method populateNodeList

At this point we would normally determine various boundaries on our node set
and set up some kind of algorithm for distributing these nodes on a grid,
but that's a whole study in itself and this is already a long article. So
for now, let's just leave an empty comment line here in "$construct" as a
placeholder. Since we are creating the boxes and labels to be draggable,
let's focus on their creation and accept that we will have to drag them into
position once they are created.

If you like, open a test instance of this window and verify that the node
list has been populated and that the connection list has been defined by
examining them using the "Instance" set of the "Variables" pane of the
Catalog.

Dynamically Creating Objects Based On Node Information

Each line of "nodeList" represents a "box-and-label" pair that must be
constructed. Each box and each label must not only be brought into
existence, but must be given various property values and methods. These
methods, in turn, may need local or parameter variables and most certainly
will need method lines.

In anticipation of our eventual grid of boxes, it's prudent to set up some
variables to define box size and distribution and label size and position
relative to the companion box. This way we can tweak the presentation by
changing variable values (or even passing parameters into the window
instance on construct) instead of putting hard-and-fast numeric values into
our code. For this purpose, 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)               6
labelTop            Number (Long integer)              13
colWidth            Number (Long integer)             180
rowHeight           Number (Long integer)              85
maxColumns          Number (Short integer)              6
hOffset             Number (Long integer)              25
vOffset             Number (Long integer)              35

Now we're ready to create the code that will dynamically build boxes and
labels based on the node information contained in "nodeList".

In doing this, we must be careful about the order in which the objects are
created. We cannot change the "order" property value of an object at
runtime - and it is assigned to dynamically created objects as they come
into being. Objects with higher order values display in front of objects
with lower order numbers. For this reason, we must always create our label
objects AFTER their companion box objects.

Since we need a box and a label for each object in our node list, we can use
a "For each line in list" loop. So append the following two command lines
(with plenty of empty lines between them!) to our "$construct" method:

For each line in list from 1 to #LN step 1
End For

It is not necessary to provide any parameters for the "For..." command.
These are the defaults that are assumed if the parameter list is left empty.
now we will work inside this loop to build the lines needed to create our
objects.

The first line we need is a line that creates a "box-shaped" Shape Field
object. From the Component Store as shipped, the default "shape" value of a
Shape Field is "kRect3D" and the default "effect" value is "kInset". These
are ideal for our use, so we will count on those values. The syntax for
adding a foreground object to a window instance is:

Do $cinst.$objs.$add(type,top,left,height,width,invisible,disabled)

Notice how the last two parameters are exactly the OPPOSITE of the
properties they represent ($visible and $enabled). Otherwise, all these
parameters a pretty obvious. For the "type" we must use "kBobj" to indicate
a Shape Field. We want them all to be visible and enabled, so the last two
parameters will both be set to "kFalse". All we need to do is make some
decisions about how we want to "arrange" our boxes in this first try so we
know how to set the position and size coordinates.

We have set up variables for the basic height and width of our "box" objects
and we have also set up variables to give us offsets from the top and left
edges of our window. We have even defined a rudimentary grid with column
width and row height. We won't use all of these yet, but for now let's agree
to place each "box" created under the previous one, using the vertical
offset and the row height to set spacing. If we do this, our "top"
coordinate value will be derived from this expression:

vOffset+(#L-1)*rowHeight

Our "left" coordinate will be simply the value of "hOffset" (for now, at
least) and our "height" and "width" values will be "boxHeight" and
"boxWidth" respectively. The line that creates our "box" objects (the first
line within our "For" loop) will then read:

Do
$cinst.$objs.$add(kBobj,vOffset+(#L-1)*rowHeight,hOffset,boxHeight,boxWidth,
kFalse,kFalse)

The "$add()" method comes with a bonus - it returns an item reference to the
object it creates. We can capture this value in an "Item reference" variable
if we have one, so let's create a local variable named "newItem" with that
data type and use it as the "Return variable" for the "Do" command above.

This "box" object is not yet complete, though. There are some important
property setting that need to be made and it requires three methods to work
as it does in our template window. First the properties.

We agreed earlier to name our "box" objects with the concatenation of the
word "box" and a unique identifier for the record it represents. This
identifier would be the value of "nodeID" on the line of "nodeList" that
contains the information about the node. So we will make our next method
line within the "For" loop read as follows:

Calculate newItem.$name as con('box',nodeList.nodeID)

Since our "For" loop sets the current line of "nodeList" with each
iteration, we only need to specify the list name and column name and the
current line of "nodeList" is assumed.

We need to make each "box" draggable, but we need to keep the drag range
within our window instance, so we'll add these two lines to the loop:

Calculate newItem.$dragmode as kDragObject
Calculate newItem.$dragrange as kRangeWindow

We had also set up a nice context menu in the previous article that may
prove useful, so we'll bring that into play as well:

Calculate newItem.$contextmenu as 'appearanceMenu'

Copying Methods From The Template

This is the end of the "special" properties we need to assign, but there is
still the matter of the methods our "box" object require. To add a method to
a field, we again use the "$add" method, but this time we apply it to the
"$methods" group for the field. The syntax that "$add()" follows in this
context is:

newItem.$methods.$add(cName)

All we can do when we dynamically create a new method is give it a name.
Notice that this must be assigned as a "Character" value (which means we
must enclose literal values in quotes). So if we want to create a method for
our "box" field named "$event", we would issue the command:

Do newItem.$methods.$add('$event')

Again, the "$add()" method returns a reference to the item it just added (it
always does), so we will create another local variable named "newMethod" of
"Item reference" type to catch this in order to breathe a little more life
into the methods we create using this technique. The method line then
becomes:

Do newItem.$methods.$add('$event') Returns newMethod

At this point, we could continue building this method with other commands
that add the local and parameter variables to each method as well as the
method text...But there is a shorter way. Let's build a more useful
structure around this line of code.

Since we already have a "box" object with all the methods we require in our
template window class, perhaps we can simply check to see what methods it
contains and copy those methods over to our newly create object on this
window instance. This still requires a few lines of code, but not half as
many as the alternative. Another advantage of this is that we may decide to
change some of the code in the template (as we come up with more clever
solutions) and even add more methods. Let me show you the code first, then
we'll break it down:

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

First, we create another local variable named "methodList" of type "List".
We populate this using the "$makelist()" method applied to the group of
methods of a known object in our template window class to yield a list of
the names of the methods in that design object. (Notice that the "$makelist"
method does not assign any column names for the list it creates, but that we
can still access cells in the list by using the notation "Cx" where "x" is
the column number.)

We then step through each line of this list creating a method of the same
name as the one in our template object and then copying the entire contents
of the template method into the newly created one. Using the "Calculate"
command for transferring methods in this manner copies the entire contents
of the source method into the target, including all local and parameter
variable definitions (with initial value calculations) and method lines.

If we had gone the long route and just copied the "methodtext" from the
source method, we would have had to create each local and parameter variable
individually, setting any initial values using the "$objinitval" property of
the variable, BEFORE copying the "methodtext". If any variable required by
the method does not already exist, the "methodtext" cannot be placed into a
method using a calculation (unlike copying and pasting in design mode).
Copying the whole thing is so much easier!

And Now For The "Label" Objects

Next we need to dynamically build our "label" objects. We do this in the
same loop as for the "box" objects since we need the same source information
(lines from the node list).

The "$add()" method works exactly the same as before. We make some slight
changes because we want to add a disabled Entry field instead of an enabled
Shape Field and we need to use a few more of our positioning variables to
properly place the "label" relative to the position of its companion "box".
The result is shown here:

Do
$cinst.$objs.$add(kEntry,vOffset+labelTop+(#L-1)*rowHeight,hOffset+labelLeft
,labelHeight,labelWidth,kFalse,kTrue) Returns newItem

Our "label" fields also have different property value requirements. We
assign the new object a name according to our naming convention and
"dragmode" and "dragrange" property values as with the "box" fields:

Calculate newItem.$name as con('label',nodeList.nodeID)
Calculate newItem.$dragmode as kDragObject
Calculate newItem.$dragrange as kRangeWindow

We want this field to "blend in" with the "box", so we remove any border it
may have brought along from the Component Store default:

Calculate newItem.$effect as kBorderNone

For cosmetic purposes, we set the fields alignment property to center the
text it contains:

Calculate newItem.$align as kCenterJst

And finally, we set the property that determines what text it will display.
Remember that we intend to allow the user to edit this text and, ultimately,
we should find some way of using this modified value to update the database.
It would be really cumbersome to create a new instance variable for each
node on our chart and transfer the "nodeName" value to it...But wait! We
already have a variable containing exactly this information - each cell in a
list is actually a separate variable in its own right! Why don't we just use
"listname.rownumber.columnname" as the "dataname" property value for each
"label" Entry field? Here is how we do this within our "For" loop:

Calculate newItem.$dataname as con('nodeList.',#L,'.nodename')

Each "label" field is now a data entry point to a different cell in our node
list. You'll have a chance to test this once we've finished with the setup.
Coupled with Omnis Studio "smartlist" technology, this can provide an easy
path for updating the database when a user changes the value displayed in a
"label".

The only other thing to deal with for "label" objects is copying the methods
from a "label" object on our template window. Since there is only one method
for a "label" field ("$event") and that method has only one local variable
("boxRef"), this is a good time to show you the "long way" I referred to
above. In this case, it only requires four lines of code, but it does
require an additional "Item reference" local variable - "newVar".

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

We create the shell of the "$event" method as before. The next thing we must
do is to create the definitions for any local variables required by that
method. The parameters required by the "$add()" method for the "$lvardefs"
(local variable definitions) group are:

1) the name we wish to assign to the variable
2) the data type for the variable
3) the subtype for the variable (0 if none required)
4) the length of the variable (for Character, etc.)
5) whether the variable is a parameter

Notice that we must still assign the initial value expression using the
"$objinitval" property of the variable. Take care that the resulting value
is a character string (and take care with those quotes!).

Once the variables have been established, we can copy the "methodtext"
property value from our template object. Notice that we must use "notation
quotes" to surround the name of any method here - otherwise Omnis will try
to evaluate the method instead of simply pointing to it.

All we need now are our connecting lines and we can test our handiwork.

Building The Connection List

While we're processing the information from our node list, we can also
capture much of the information needed to manage our lines into our
connection list. We know that our "From" field is the "box" object whose
name includes the current value of "nodeID" and the "To" field is the "box"
whose name includes the current value of "nodePointer". The only thing we
don't know is the "ident" property of the connecting line and that is only
because the line "ident" is not available until the line object is actually
created. Since we can't assign names to background objects, we must rely on
the" ident" - and so we have to wait to fill in this information in our
connection list.

We also need to be aware that we only need lines in the connection list when
a connecting line is required. We know whether this is true by whether or
not the current line of the node list has a value in the "nodePointer"
column. The result of this logic is the following three lines of code that
complete the contents of our "For each line of list" loop:

If len(lst(nodePointer))
    Do connList.$add('',con('box',lst(nodeID)),con('box',lst(nodePointer)))
End If

We can now use the contents of "connList" to create our connecting lines and
complete the window construction process.

Creating Connecting Lines

Again I will choose to use a private method at the class level for this
process. This allows me to temporarily set the "Current List" to "connList"
to more quickly process the list contents using tokenized Omnis Studio list
manipulation commands rather than notation. Since this is a "single list"
process, this is slightly more efficient.

We will name the method "createLines". We will need only one local variable
named :newItem" (just like the one in "$construct") of "Item reference"
type. Here is the code for the method:

Begin reversible block
    Set current list connList
End reversible block
For each line in list from 1 to #LN step 1
    Do $cinst.$bobjs.$add(kLine,100,100,100,100,kFalse,kFalse) Returns
newItem
    Calculate connList('connLineID',#L) as newItem.$ident
    Calculate newItem.$backpattern as 15
    Do $cinst.$linereset(connList)
End For

We begin by reversibly setting "connList" as the "Current List". We can then
use the "For each line of list" command that requires a "Current List"
setting. We will accept the default parameters of this command since we want
to process all lines of this list in order. Within this loop we will then
create each line (with arbitrary coordinate settings to start with),
backfill the "connLineID" column of "connList" with the "ident" of each
newly created line object, set the "backpattern" property of the line to the
"transparent" setting (which also worked nicely for our "line" Shape Fields,
by the way, to keep them from blocking our view of underlying "lines").
Finally, we set the proper "top", "left", "height" and "width" coordinates
by invoking the "$linereset" method of the window instance. Let's discuss a
couple of brief issues involving these command lines.

First, we are adding to the group of "background" objects ("bobjs") this
time, but the "$add()" method requires exactly the same parameters as for
"foreground" objects. (The "disabled" parameter doesn't make much sense, but
otherwise this is logical.)

I chose to use a special form of notation for updating the "connLineID"
column in "connList". This "matrix" notation is very stable and may be more
intuitive to the more mathematically inclined Omnis Studio developer. Notice
that the name of the column must be enclosed in quotes, although the column
number (1 in this case) could also have been used. This form of list cell
notation is read/write by nature, so it can be used as the target of a
calculation as it is used here. Since we can't assign a name to a window
background object, we'll use the "ident" value of the newly created line.

Remember that the "$linereset" method receives the "lineParams" parameter as
a row variable. But notice that we are passing the "entire" list. The key
bit of knowledge here is that the data type of the parameter determines how
the parameter value is received. This works here in the same way as
calculating a row variable as a list: The row variable is assigned the
column values from the current line of the list.

The final element to add to our window class is to complete the "$construct"
method with this command line:

Do method createLines

If we don't, the connecting lines will never get created...

Quick Code Review

Because most of these methods were done a piece at a time, here is the
complete code for a quick check in case your version doesn't work as
advertised:

$construct:

Begin reversible block
    Set current list nodeList
End reversible block
Define list {nodeID,nodeName,nodePointer,nodeLevel}
Do connList.$define(connLineID,connFromID,connToID)
Do method populateNodeList
For each line in list from 1 to #LN step 1
    Do
$cinst.$objs.$add(kBobj,vOffset+(#L-1)*rowHeight,hOffset,boxHeight,boxWidth,
kFalse,kFalse) Returns newItem
    Calculate newItem.$name as con('box',nodeList.nodeID)
    Calculate newItem.$dragmode as kDragObject
    Calculate newItem.$dragrange as kRangeWindow
    Calculate newItem.$contextmenu as 'appearanceMenu'
    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
    Do
$cinst.$objs.$add(kEntry,vOffset+labelTop+(#L-1)*rowHeight,hOffset+labelLeft
,labelHeight,labelWidth,kFalse,kTrue) Returns newItem
    Calculate newItem.$name as con('label',nodeList.nodeID)
    Calculate newItem.$effect as kBorderNone
    Calculate newItem.$align as kCenterJst
    Calculate newItem.$dataname as con('nodeList.',#L,'.nodename')
    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
    If len(lst(nodePointer))     ;; create line in line list for line info
        Do
connList.$add('',con('box',lst(nodeID)),con('box',lst(nodePointer)))
    End If
End For
Do method createLines

$control

On evDrag
    Calculate origX as $cobj.$left
    Calculate origY as $cobj.$top

$linereset

Calculate obj1ref as con('objRef.',lineParams.connFromID)
Calculate obj2ref as con('objRef.',lineParams.connToID)
Calculate $cinst.$bobjs.[lineParams.connLineID].$left as
[obj1ref].$getcenterx()
Calculate $cinst.$bobjs.[lineParams.connLineID].$top as
[obj1ref].$getcentery()
Calculate $cinst.$bobjs.[lineParams.connLineID].$width as
[obj2ref].$getcenterx()-[obj1ref].$getcenterx()
Calculate $cinst.$bobjs.[lineParams.connLineID].$height as
[obj2ref].$getcentery()-[obj1ref].$getcentery()

populateNodeList

Add line to list {('0001','President','',1)}
Add line to list {('0002','VP Sales','0001',2)}
Add line to list {('0003','Sales Asst 1','0002',3)}
Add line to list {('0004','Sales Asst 2','0002',3)}
Add line to list {('0005','VP Eng','0001',2)}
Add line to list {('0006','Eng Asst 1','0005',3)}
Add line to list {('0007','Eng Asst 2','0005',3)}
Add line to list {('0008','VP Finance','0001',2)}
Add line to list {('0009','Fin Asst 1','0008',3)}
Add line to list {('0010','Fin Asst 2','0008',3)}

The "createlines" method is listed in the previous section in its entirety.

Code from the template objects:

$event for box02

On evWillDrop
    Process event and continue
    Calculate labelRef.$left as labelRef.$left-(origX-$cobj.$left)
    Calculate labelRef.$top as labelRef.$top-(origY-$cobj.$top)
    Do
connList.$sendall($cinst.$linereset($ref()),$ref.connFromID=$cfield.$name|$r
ef.connToID=$cfield.$name)

$getcenterx from box02

Quit method $cfield.$left+int($cfield.$width/2)

$getcentery from box02

Quit method $cfield.$top+int($cfield.$height/2)

$event from label02

On evAfter
    Calculate $cobj.$enabled as kFalse
    Calculate $cobj.$dragmode as kDragObject
On evClick
    Calculate $cobj.$enabled as kTrue
    Calculate $cobj.$firstsel as 0
    Calculate $cobj.$lastsel as len($cobj.$contents)
    Calculate $cobj.$dragmode as kNoDragging
On evKey
    If upp(pKey)='Z'&#COMMAND
        Do $cobj.$redraw(kTrue,kFalse)
    End If
On evWillDrop
    Process event and continue
    Calculate boxRef.$left as boxRef.$left-(origX-$cobj.$left)
    Calculate boxRef.$top as boxRef.$top-(origY-$cobj.$top)
    Do
connList.$sendall($cinst.$linereset($ref()),$ref.connFromID=boxRef.$name|$re
f.connToID=boxRef.$name)

If your code all agrees with this, it should work!

Testing Our Results

Now let's instantiate the window. It will take a few moments to work through
all that code in $construct, but not an unreasonably long time. When the
window does draw, if you see lines along with the rectangles everything
should be working.

Admittedly, this is not yet ready to ship. We should work out some kind of
layout grid strategy so the presentation is "nice" when the window first
draws. As it is now, the user has to drag the "boxes" into better positions.
The point is: The user CAN drag the "boxes" to any position on the window
(except on top of another "box"). The window can be opened to a larger size
and can be scrolled to get to "boxes" currently out of sight.

We already have some of the tools needed to create a nice layout grid. We
have variables that define column widths and row heights and offsets from
the top and left edges of the window. We also have a column in "nodeList" to
track the "level" at which a node resides in our hierarchy and we can
perform operations involving our list to determine how many nodes exist at
each "level", which can help us determine more information about possible
"shapes" for our chart. We could choose to build the chart starting at the
top node and working our way down each "leg" until all nodes are drawn, or
we could build the chart a level at a time. There are lots of possibilities.

In The Next Issue

Next time we'll examine a slightly more complex chart: a Connection Diagram
for a native Omnis database built from the information stored in the File
classes of a Library. We'll also discuss ways a similar thing can be created
for SQL databases using Schema classes (if certain setup operations are
performed diligently).

I hope that you found some of the techniques introduced here to be of
value - even if you never end up trying to create an organization chart in
Omnis Studio.


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

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