[Omnis-Newsletter] Omnis Technical Newsletter

omnis-news-admin@omnis.net omnis-news-admin@omnis.net
Wed, 12 Dec 2001 15:13:19 -0000


 December 12th, 2001

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

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 the Lite
version of Omnis Studio. 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.

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

CHRISTMAS/NEWYEAR HOLIDAY
The Omnis Technical Newsletter team are taking a well-deserved break over
the Christmas/New year holiday season. The next issue of the technical
newsletter will be on January 9th, 2002. Over the holiday break, you may
like to review the back issues of the newsletter, especially if you are
working through Geir's tutorial. For back issues, please go to
www.omnis.net/newsletter and click on the 'Newsletter Archive' link. And
don't forget, you can always get the latest Omnis news on our web site at:
www.omnis.net/news


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

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 his tutorial
by showing you how to add the appropriate SQL methods and syntax to
implement a search facility in the Task window. In the second article, David
Swain discusses the methods, such as $makelist() and $sendall(), that you
can use to manipulate notation groups. 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
-Building part 14: Implementing the data entry interface, part 3 by Geir
Fjaerli
-New Omnis Successes!
-Group Methods 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. Latterly, David has
appeared at Omnis Developer conferences providing his own Studio 101
introductory course to Omnis programming and application development.


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

Building part 14: Implementing the data entry interface, part 3.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net

Hi, and welcome back to our Omnis Studio tutorial.

Here is our work so far:
Part 1: Hello World, our first little Studio application
Part 2: The Studio Classes. The classes are the building blocks of an
application, and include windows, reports, menus etc.
Part 3: The Omnis Integrated Development Environment. Here we looked at the
Component store, the Property Manager and the Catalog.
Part 4: The Data Structure. Including basic terminology.
Part 5: Creating schemas and database tables.
Part 6: Adding table classes and using our schemas in application windows.
Part 7: What did the wizard build, and a tour of the method editor.
Part 8: The logon window and object class.
Part 9: The task window.
Part 10: The task window continued.
Part 11: Designing a proper data entry interface.
Part 12: Implementing the data entry interface.
Part 13: Implementing the data entry interface, part 2.
You should have those handy, and your "tasks.lbs" library file.

As usual, I strongly suggest that you refer to these parts if you haven't
already, or if you feel uncertain about the details. From part 5 onwards,
each new part will be based on programming done in previous parts, so you
have to follow them all. (For back issues, please go to
www.omnis.net/newsletter and click on the Newsletter Archive link.)

WHERE ARE WE?
In our last issue we continued implementing our data entry interface. Having
covered the pre-entry functions in chapter 12, we devoted our efforts to the
post entry processing in chapter 14. That included the OK and Cancel buttons
and the subroutines for those. Then we made the placeholders for the Insert,
Update and Find methods. The Insert and Update methods were completed right
away, as they only contain a couple of method lines.

Then we started our discussion on the functionality of the Find function. We
ended up focusing on three simple search criteria:
1. Equals date planned. All tasks planned for the given date.
2. Equals employee selected. All tasks for the given employee.
3. All tasks where the task description begins with what was typed.
We also decided that for now we only allow searches on a single column. We
may enhance this later.

We also decided to use a list to display the results of our search, unless
there was only one, in which case we could load it directly into our task
details.

We left off with a brief discussion of the syntax of an SQL Where-clause.
Please reread the paragraphs on the Find and Where clause before you start
on today's chapter.

A COMMENT ON LINE BREAKS.
This newsletter is distributed in email format. This works good and can be
read by anyone. The disadvantage is the lack of formatting. One problem that
may be slightly irritating is that email text will wrap to a given number of
characters in many clients. You may therefore see lines of code broken at
odd places in my examples. I hope this isn't too much of an inconvenience
for you all.

THE WHERE CLAUSE CONTINUED.
So, having decided which search criteria we will allow, let us start
building the methods. Basically, we have four distinct operations here:
1. Identify the column,
2. Build the where statement,
3. Execute the search, and
4. Display the results.

So how do we do it:
1. Identify the column:
We said in our discussion that we would search on the first of the relevant
fields that had a value. So we can test on that:

  If iTaskRow.cTask>''

This means "If the task description is greater than an empty string", that
is if it contains any text. An alternative syntax would have been:

  If len(iTaskRow.cTask)>0

Which means if the length of the task description is greater than zero
characters.

The other two search columns will be tested in similar ways:

    Else If iTaskRow.cDatePlanned>''

This means "If the date planned column has a value greater than an empty
string". To understand how this comparison works, it is useful to remember
that Omnis has built in type conversion. That is, it will automatically
convert between text, number, dates and other values as needed. So I can say

  Calculate myTextField as myNumberField

and Omnis will put the number into the text field as a text string. In other
tools, type conversion is explicit, so you have to use a "number_to_text"
function to make this work.

We are actually making use of this Omnis feature above. In theory, a date
isn't a string, so the expression "if date is greater than an empty string"
doesn't make sense. But because of the automatic type conversion, Omnis lets
us get away with it.

Note: Sometimes our intention isn't obvious to Omnis. Did we mean the date
as a date or as a string? In those cases we may force Omnis to use one
rather than the other by explicitly using a type function, e.g.:

  Calculate myDate as dat('1 DEC 2001')'

The final test is:
  Else If iTaskRow.cEmployeeID>0

This test is different. We have no entry field for the employee in our
window, instead we select her/him from a list. This sets the primary key of
the chosen employee as the value for the cEmployeeID foreign key in the
iTaskRow variable. So if an employee was selected, the value of cEmployeeID
will be greater than zero.

THE IF STATEMENT.
So we end up with the following three tests:
  If iTaskRow.cTask>''
  Else If iTaskRow.cDatePlanned>''
  Else If iTaskRow.cEmployeeID>0

So what does this mean? Although I usually refer you to the Omnis manuals
and other books on programming, I will spend a few paragraphs on the If
statement here:

The first one starts with "If", the other two with "Else If". This "If -
Else If" construct works as a unit. What it says is:
  If <first condition is true>
    Do the first action
  Else if <second condition is true>
    Do the second action
  Else if <third condition is true>
   Do the third action

So only one action is ever performed, and as soon as a test is evaluated as
true, the rest are skipped. So this could be described as follows:
Do first action if first condition is true, OR if not then do second action
if second condition is true, OR if not do third action if third condition is
true

If we wanted to evaluate all the tests and perform all actions if true, we
would say:
  If iTaskRow.cTask>''
  End If
  If iTaskRow.cDatePlanned>''
  End If
  If iTaskRow.cEmployeeID>0
  End If

This could be described as: Do first action if first condition is true, AND
do second action if second condition is true, AND do third action if third
condition is true.

We could even do this:
  If iTaskRow.cTask>''
    If iTaskRow.cDatePlanned>''
      If iTaskRow.cEmployeeID>0
      End If
    End If
  End If

Here we test on the first condition, and only if that is true do we test the
second, and only if that one is true too do we test the third. Code could be
triggered between each.

Since we decided to only use one search criteria for now, the first syntax
will be appropriate. So we will enter that in our Find method.

Open the wTask window and the method editor and select the Find method, and
type the following code:
  If iTaskRow.cTask>''
  Else If iTaskRow.cDatePlanned>''
  Else If iTaskRow.cEmployeeID>0
  End If.

You may put empty lines between each of these, since we are going to add the
actions for each conditions later.

Note: The If and Else IF statements have three versions. If you just type
If, you will get the "If Flag True" statement. To be able to use a
calculated test as above you have to select "If calculation" and "Else if
calculation", and put the test in the calculation box.

Note 2: An If statement (or group of statements) is always terminated with
an End If. This is not really a command, it doesn't do anything. It just
tells Omnis that it doesn't need to look below this line for more Else If
conditions to test, or more code belonging to that condition.

THE WHERE STATEMENTS.
So, now that we have the tests, it is time to build the search clauses. We
will build them into a local variable that we can use in our $select
statement later. So with the Find method selected, choose the Local tab in
the variable pane and create a variable named lWhere. The type is character,
and you can leave the length at the default value, as Omnis will only
allocate the space needed for the actual text anyway.

The local variable is bound to the method it belongs to. It is created when
the method starts and destroyed when the method ends, so it is ideal for the
temporary assignment that we need here. We will use it to store a where
statement built in the beginning of the method, and when the method ends the
select has been executed, so we don't need it anymore.

Now remember the basic syntax of a Where clause? "where table.column =
value"

The first thing to notice here is that we have to use the name of the
database table, not our row variable name, in the Where clause. Why is that?
Well, the Where clause is sent is sent with the select to the server which
executes it. And the server database is blissfully unaware of our Omnis
variables, they simply have no meaning at that end of the network cable. So
we have to use terms it understands, and that is the table structure. So
assuming we are finding on the task description, our where clause would be
something like:

"where Task.cTask = 'Phone'"

But this will not quite do. Because we cannot assume what the user will
search on, so we cannot hardcode the value as we have done here. Instead we
have to put in the value they have typed into the cTask field on screen. The
easiest solution would be to use square brackets. As discussed in an earlier
chapter the purpose of square brackets is: To evaluate the argument between
them as a variable name, and use the value for that variable in its place.

So we could change our statement to read:
"where Task.cTask = '[iTaskRow.cTask]'"

THE CON FUNCTION.
This however may get a bit too complicated for Omnis to understand, with
square brackets inside single and double quotes. So to make sure we get it
right we will do it like this instead:

con("where Task.cTask = '",iTaskRow.cTask,"'")

The con() function takes a comma separated list of arguments and joins them
together. The arguments can be strings and variable values. So if ivAge =
44:
con('Geir is ',ivAge,' years old.')
will return: Geir is 44 years old.

Now SQL uses single quotes around literal text, so we have to use double
quotes in our con function to tell them apart. We can break our con
statement into three:
"where Task.cTask = '"
iTaskRow.cTask
"'"

The first part is the where statement up to the opening single quote, then
we insert the value of the cTask column, and finally we add the closing
single quote.

The full statement would then be:
  Calculate lWhere as con("where Task.cTask = '",iTaskRow.cTask,"'")

Now when Omnis executes this line of code, it will evaluate the contents of
iTaskRow.cTask. So if the user typed Phone, lWhere will now contain "where
Task.cTask = 'Phone'" (without the double quotes)

Now we are getting closer. But the statement above has one weakness. It
assumes that the column in the database row contains exactly what the user
types, and only that. We wanted a "begins with" type search. Say the user
only types "Ph", we would still want to find Phone (as well as Physical,
Philosopher, etc.) To achieve this in SQL we use a wildcard and the LIKE
test. So we substitute "LIKE" for "=", since "=" means "exactly like". And
then we add a wildcard character: "where Task.cTask LIKE 'Ph%'"

The wildcard character is %, and will match any sequence of zero or more
characters. So 'Ph%' will match 'Ph' alone and any string starting with Ph.
This is called pattern matching. Refer to your SQL book of choice for more
on this subject.

So our statement now looks like this:
  Calculate lWhere as con("where Task.cTask like '",iTaskRow.cTask,"%'")

We have simply added a % between the variable and the closing single quote.

Now return to your Find method and make room for this statement between the
If and the first Else If, unless you already have an empty line between
them. (Click on the Else if line and type Ctrl-I to move it down and insert
an empty line above it.)

THE DATE PLANNED WHERE CLAUSE
So that was the task description clause. Next out is the date planned. We
will use a similar approach here. But there are some differences.

There is little sense in searching for a date that "begins with"
something... begins with what? Say the date is December first, 2001. In
Norway that would begin with 1, as we write our dates 1/12-2001. Some users
will write 12-1-2001, and some will write 2001-12-1. So we treat our dates
the same way we treat numbers, and say e.g. "equals" (=) or "greater than"
(>). In our case we wanted to search for tasks planned for the exact date,
so we use =.

Our date where clause then would be
"where Task.cDatePlanned = 'Date'"

And so our calculate statement will be:
  Calculate lWhere as con("where Task.cDatePlanned =
'",iTaskRow.cDatePlanned,"'")

Insert this below the date planned Else If condition in the find method.

So on to the final where clause.

THE EMPLOYEEID WHERE CLAUSE.
The final where clause will match all tasks for the selected employee, by
matching on EmployeeID. Since this is a simple number comparison, all we
have to do is use =, and there is no need for quotes around the number. We
can simply say:
"where Task.cEmployeeID = 1234"

So we end up with the following statement:

  Calculate lWhere as con("where Task.cEmployeeID = ",iTaskRow.cEmployeeID)

Add this after the final Else If and then add an End If. Your method should
now look like this:

  If iTaskRow.cTask>''
    Calculate lWhere as con("where Task.cTask like '",iTaskRow.cTask,"%'")
  Else If iTaskRow.cDatePlanned>''
    Calculate lWhere as con("where Task.cDatePlanned =
'",iTaskRow.cDatePlanned,"'")
  Else If iTaskRow.cEmployeeID>0
    Calculate lWhere as con("where Task.cEmployeeID =
",iTaskRow.cEmployeeID)
  End If

Note that the code above is rather simplistic. It does however meet our
current requirements. In a later chapter we shall demonstrate how a few
simple changes will allow for multi column searches, e.g. all tasks for a
given employee on a given date.

PERFORMING THE SEARCH.
So, having set up the where clause we can now do the search. We do this by
using the built in $select and $fetch methods. The $select will take our
where clause as a parameter and instructs the database to locate all
matching rows. The $fetch then retrieves these rows to our workstation. This
of course may be one or more rows, if any at all. Currently our window only
has a row variable for the task. So we need to set up a list variable as
well for the result of our search. So in the instance variables for wTask,
add a variable called iTaskList, type list, no subtype needed.

For this list to work, it needs to be defined. Since we want to use it with
the table class methods of tTask, we will define it from that class. So in
$construct of the window, add the following:

  Do iTaskList.$definefromsqlclass('tTask')

You can put it wherever you like in $construct, but to keep your method tidy
and readable you might want to put it right under the definition for
iTaskRow, so you can make room for it there by clicking the next line and
typing Ctrl-I. In fact, the definition for iTaskList is identical to that
for iTaskRow, apart from the name, so you may copy the existing one and
simply change the variable name from iTaskRow to iTaskList.

Then go back to your Find method, and below the existing lines add the
following

  Do iTaskList.$select(lWhere)
  Do iTaskList.$fetch(kFetchAll)

This will select all rows that matches our where clause and retrieve them
into iTaskList. kFetchAll is a built in constant in Omnis that means all
rows. (Actually, like most constants it is a number = one billion, but that
should be big enough to qualify for all rows in a result set...) By using a
smaller number, you can bring the data back in chunks, even a single row at
a time.

THE SEARCH RESULT.
So now that we have selected and fetched the rows, we need to take
appropriate action. We have three possible outcomes of our search, and three
actions to perform:
1. No rows were found. There wasn't any rows matching our where clause, so
we bring up a message saying "Sorry, try again".
2. One matching row was found. Our search found only one row that matched
our where clause, so there is no need to display it in the list. Instead we
copy it into the row variable and redraw the window so that the user can
start working on it right away.
3. Multiple rows were found. Our search returned multiple matching rows.
This means we will have to ask the user to identify the correct one. So we
display the list in the list pane, and then the user can click on the one
they want to use.

Number 1 and 2 are fairly simple to implement. To test we can use a list
property called $linecount, which means "the number of lines in the list".
So below your $fetch statement in the Find method add the following If
statement:

  If iTaskList.$linecount=0
    OK message No matching rows {No rows where found to match your search.
Please try again.}

Now this could of course be enhanced. We could have displayed the search
value in the message, and we could have triggered a new Find, so that the
user didn't have to click again. But for now this is OK.

The next test handles when only one row was found. Here is the code for that
option:

  Else If iTaskList.$linecount=1
  Calculate iTaskRow as iTaskList
  Do $cinst.$redraw()

We use an Else If because there is no need to test this if we already found
in the first test that there was no lines. The alternatives are mutually
exclusive.

You can actually test this code already. You just have to add a closing End
If below the $redraw statement to finish off the If. The bottom part of your
Find method then will look like this:

  If iTaskList.$linecount=0
    OK message No matching rows {No rows where found to match your search.
Please try again.}
  Else If iTaskList.$linecount=1
    Calculate iTaskRow as iTaskList
    Do $cinst.$redraw()
  End If

That leaves handling multiple row results. For that we need a list on the
second tab to display them, with code to handle clicks. That will be our
first task for next time. Which will not be until the new year. Until then
have a peaceful and merry holiday. Thanks for following me this year, and I
hope to see you again next year. :)


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

New Omnis Successes!

Omnis solutions have been implemented with great success in a wide variety
of industries, all over the world. We have recently added two new Omnis
success stories to our web site:
- Based in Germany, Opix develops and markets a high-performance system for
Workflow and Media Asset Management, described by customers as both easy to
use and advanced in performance.
http://www.omnis.net/successstories/opix.html
- Business Base is a successful Dutch developer of Customer Relationship
Management software, Integrated CRM/ERP, e-business and Professional
Services Automation.
http://www.omnis.net/successstories/businessbase.html


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

Group Methods
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com

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

In the last issue I described the use of the $add() and $remove() methods
for an object group. Two other group methods, $makelist() and $sendall() are
also very useful, but they were a little off-topic for that article. They
have enough similarities, though, that they can be treated together, so
let's examine them here.

$makelist()

The $makelist() method is used to create a list value based upon specified
properties of the members of the group to which it is applied. The
properties that we can specify will vary from group to group, but the way we
specify them is the same. The $makelist() method takes a comma-delimited
list of property parameters that become the values in the columns of the
list value it creates. We specify these properties in a special way:

notationForGroup.$makelist($ref.$prop1,$ref.$prop2,...)

The notational item "$ref", as it is used here, is a reference back to a
member of the group. You can think of the $makelist() method as a "For" loop
that cycles through each member of a group ("For each member of the group")
and performs an "Add line to list" for each one. The "$ref" acts as the
"loop iteration pointer", pointing to each member of the group in its turn.
In pseudocode, this would look like:

Do returnList.$define()  ;;to clear any column definitions and contents
For each member of the group  ;;$ref range from first to last group member
    Do returnList.$add($ref.$prop1,$ref.$prop2,...)
End for

The result is a list value with one line for each item in the group and a
column for each property specified as a parameter of the $makelist() method.

The Return Value

The list value generated from this method usually must be assigned to a list
variable to be useful. We can do this in one of two ways:

Do notationForGroup.$makelist($ref.$prop1,$ref.$prop2,...) returns
returnList
or
Calculate returnList as
notationForGroup.$makelist($ref.$prop1,$ref.$prop2,...)

In either case, the returned list value has no column names. We can assign
column names using returnList.$redefine() (or the "Redefine list" command if
our list variable is set as the Current List). Care must be taken to make
sure that the variables used to name these columns have appropriate data
types for the column contents if there is a chance we will be using the
corresponding variables to transfer column values out of the list using
either the "Load from list" command or the "$loadcols()" method.

We can also use the notation "returnList.Cn" to refer to the contents of the
"nth" column if redefining the list is too cumbersome.

Searching and Sorting the Resulting List

The ordering of members of a group is important in the use of $makelist().
For some groups, this is the order of the object's creation or addition to
the group. In other groups, such as the objects on a window class or
instance, there is an "order" property that we can modify notationally or at
design time. For still others, like the lines of a menu or method, it is
entirely fixed by the position of each item. (They have an "order" property,
but its value can't be modified.) The $makelist() method builds a list of
all the members of the group in their order within the group, however that
is determined. It offers no way of ordering the returned contents by any
built-in sorting mechanism.

Neither does the $makelist() method offer us a filtering mechanism to limit
the list creation to a subset of the group membership. All members of the
group are always included. All this means is that we have to do our sorting
and filtering work with additional lines of code (or some very clever
nesting of notation for those who prefer obscure one-liners).

Property Doesn't Exist

But what if certain members of the group don't have one of the specified
properties? (It can happen.) That cell will simply be empty. A line in the
list is still created for each member of the group, but it is entirely
possible for some lines to be incomplete.

For example, suppose we want to know the $dataname value for all the fields
on the current window instance. If we issue

Do $cinst.$objs.$makelist($ref.$dataname) returns nameList

we will only have entries for items that are associated with variables.
Pushbuttons, group boxes and other "controls" will return empty lines. If we
don't want those extra lines that contain the empty cells, then we must
execute other code to remove them from the resulting list. (A topic for
another time, perhaps...)

Using Method Calls as Parameters

We can include methods that return values in the $makelist() parameter list.
Suppose that we have a window class named "callerInfo" that we allow to
multi-instantiate. In this class we have provided a public method named
"$getCustomerName" that returns the name of the customer record displayed on
the specific instance of the window class. If we wanted to create a list of
all the customers that are currently "on hold" (are displayed in an existing
instance) and the name of the instance with which they are associated, we
could issue the command:

Calculate returnList as
$windows.callerInfo.$insts.$makelist($ref.$getCustomerName(),$ref.$name)

The "$getCustomerName" method is executed for each window instance and its
return value is added to the list along with the instance name.

Uses of $makelist()

There are two general times when we may want to use the $makelist() method
for a group: within our finished applications and while we are developing
them. In other words, we may want to build lists for foreground or
background use at runtime within an application, but we might also want to
use this to poke around in design mode to learn things about the inner
workings of Omnis Studio.

For example, perhaps we would just like to see what kinds of attributes are
available to explore the possibilities in Omnis Studio. We can take
advantage of the fact that Omnis Studio is nicely documented internally and
issue a command like:

Calculate #L1 as $cclass().$attributes.$makelist($ref.$name,$ref.$desc)
Variable menu command: Open Value Window {#L1}

to see a list of the attributes available and their descriptions for some
aspect of Omnis Studio. This can sometimes return more than expected,
however. In some early explorations of the position vector of the $print
method for a report field, I issued the following command:

Calculate #L1 as
pPosition.$attributes.$makelist($ref.$name,$ref.$type,$ref.$desc,$ref)

and received a list of over 900 lines, each with its own attribute
information, including datatype and current value (when applicable). The
list contains much more than attributes of the position vector and I am
still mining it for "hidden" tidbits about how Omnis Studio functions.

$appendlist()

The $appendlist() method is an extension of the $makelist() method. Its
first parameter must be the name of a list variable. This is then followed
by the normal parameters for $makelist(). Executing this method for an item
group appends the resulting list to the list variable named in the first
parameter. If that variable is empty to begin with, this method is
essentially equivalent to assigning the $makelist() result to that list
variable.

$insertlist()

The $insertlist() method is a further extension of the $makelist() method.
Again its first parameter is the name of a list variable to which the
returned list is inserted, but the second parameter indicates at which line
in that list the result list is to be inserted. (The $makelist() parameters
follow the second parameter.) If that list variable is empty to begin with,
this method is essentially equivalent to assigning the $makelist() result to
that list variable. (The insertion line is irrelevant in this case.)

$sendall()

The $sendall() method is used to send the same message to a number of
members of a group. This can be ANY group recognized by the Omnis Studio dot
notation, not just groups of objects on a window, so it can include lines of
a list variable, lines displayed by a list display field, attributes of a
notational item, or anything else you can find that looks useful.

The message sent is a built-in or public method of the members of the group.
The examples in the manual all show using built-in methods, but we can do
some interesting things by creating our own custom methods and naming them
so that they are public (beginning them with a "$"). The method can also be
a method of a property of the items in the group - specifically, the
$assign() method - which we reference using:

$ref.$propertyname.$assign(value)

We specify the method to be sent in a similar way to how we specify the
properties to be retrieved for the $makelist() method:

notationForGroup.$sendall($ref.$methodname())

This is the equivalent of the pseudocode:

For all members of the group  ;;$ref range from first to last group member
    Do $ref.$method()
End for

The "$ref" is again used as a "loop iteration pointer" to specify each
member of the group in its turn. $sendall() differs from $makelist in that
it causes the execution of a method rather than retrieving the value of a
property.

As with the building of a list using $makelist(), this method execution is
performed in the order implied by membership in the group. We can see this
in action with a little experiment:

Create a new window class and place three entry fields and a pushbutton on
it. Name the entry fields "Field1", "Field2" and "Field3" and the pushbutton
"Pushbutton". Create a new method named "$testmethod" for one of the entry
fields and give it the following line of code:

OK message  {[$cfield.$name]}

Now copy this method (name and all) and paste it into the other objects
(including the pushbutton). Then give the $event method of the pushbutton
field this code snippet:

On evClick
    Do $cinst.$objs.$sendall($ref.$testmethod())

If we instantiate the window and click the pushbutton, we will see OK dialog
boxes appear one by one for each object in the order implied by their
"order" property. Change the order of the fields a few times to test whether
this is so.

If an item in the group does not contain the specified method, it
(obviously) isn't executed. This does not cause any kind of error at
runtime; the item is simply skipped. Remove one of the "$testmethod" methods
in the example above and try the exercise again to test this.

We can also use "$ref" in the parameters we send for the specified method.
Try the following to demonstrate this:

In our window, create three instance variables of floating decimal numeric
type named "number1", "number2" and "number3" and assign them as the
dataname properties to the corresponding entry fields. Next, create a new
method for one of the entry fields, name it "$accumulate", give it a
parameter named "pValue" of floating decimal numeric type and give it the
following line of code:

Calculate [$cfield.$dataname] as pValue+2

Change the $event() method of the pushbutton field to be:

On evClick
    Do $cinst.$objs.$sendall($ref.$accumulate(eval($ref.$dataname)))
    Do $cinst.$redraw()

Now test the window. If our window was set to modeless data entry, we can
modify the value of any of the instance variables at any time, but when we
click the pushbutton, the value of each variable is incremented by 2.

Compound Method Execution

The $sendall() method only allows one method-bearing parameter, unlike the
$makelist() method that can have any number of property parameters. But that
doesn't mean that we can't cause more than one to be executed per item in
the group. This parameter can be an expression, so we can include as many
method calls as we need separated by operators of some sort. It doesn't seem
to matter which operators we use and the normal precedence rules don't seem
to apply as they always get evaluated from left to right in the expression.
Try changing the $event() code of the pushbutton to:

On evClick
    Do
$cinst.$objs.$sendall($ref.$accumulate(eval($ref.$dataname))+$ref.$redraw()+
$ref.$testmethod())

When we test what happens, we see that each variable is recalculated and
redrawn before the corresponding OK dialog appears. If we move the $redraw()
message to the end of the expression, we now see that the field is redrawn
to display the new value after the corresponding OK dialog is dismissed. Try
this using various other operators ("*", ">", "|", etc.) and see what you
find.

Nested Method Execution

We can also make nested method calls in the same way we used $ref in a
parameter for passing a property value above.

On evClick
    Do
$cinst.$objs.$sendall($ref.$accumulate(eval($ref.$dataname))+$ref.$testmetho
d($ref.$redraw()))

At first this appears to not work, but that is only because the
$testmethod() method doesn't have any parameters defined, so Omnis Studio
doesn't evaluate the "parameter" expression we included. If we give
"$testmethod()" a parameter of any type and test this again, it now works as
expected (even though we don't otherwise use the parameter). In this case,
our "parameter" is simply used for executing an enclosed method, but we
could also have a situation where this secondary method call generates a
value that is passed as a parameter to the primary method.

The Filtering Parameter

In all the excitement above I forgot to mention that the $sendall() method
has a second parameter that is used to limit the items to which the
method(s) in the first parameter are sent. This parameter is optional, but
it must be a valid expression if it is included. more specifically, it must
be a "search calculation" expression that describes the subset of items from
the group that is to receive the message(s) in the first parameter.

For example, if I only want to send the message to entry fields, I would
include as my second parameter:

$ref.$type=kEntry

This expression can certainly be much more complex, but you get the idea.
Some part of this should refer back to the members of the group if it is to
limit the message to only certain of them.

The expression could, however, be related to other data and act as an
all-or-nothing switch for sending the message. Perhaps we would only like
the message sent in the morning. We would then supply as our second
parameter:

tim(#D)<=tim('12:00')

This has nothing to do with the item group members, but it will be evaluated
and used to determine which of the (all or none in this case) will be sent
the message.

As stated above, the $makelist() method doesn't have a filtering parameter.
We can instead filter the resulting list before using or presenting it.
$sendall() method calls, on the other hand, must be filtered BEFORE they are
sent since there is no way to undo their results after the fact.

There are more examples I could include here, but they would only be
variations on the theme and, therefore, somewhat redundant. I hope I have
suggested a few things you may not have thought of in working with these
very useful methods.


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

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