Tips_tutorials   >   Studio102   >   State/Provinces
In this section we will add a Stateprov table to the database. A foreign key column, Country_fkey, will be included in the Stateprov table. The foreign key column will be used to store the primary key of the parent Country record.
In SQL terms, the Country table is called the referenced table, and the Country_pkey column is called the referenced column.
CREATE TABLE Stateprov (Stateprov_pkey INTEGER NOT NULL,StateProvName VARCHAR (30) NOT NULL,StateProvAbbrev VARCHAR (5),Country_fkey INTEGER NOT NULL)
CREATE CASE SENSITIVE UNIQUE INDEX Stateprov_pkey ON Stateprov (Stateprov_pkey)
CREATE CASE SENSITIVE UNIQUE INDEX StateProvName ON Stateprov (StateProvName)
CREATE INDEX Country_fkey ON Stateprov (Country_fkey)
The above SQL scripts used to create the unique indexes is specific to the Omnis data file. For creating unique index constraints on other databases check the documentation specific to the RDBMS.
I find it helpful to have some test data in the database when developing an application so lets insert a few Stateprov records using the window.
To test the join between the Stateprov and Country records:
In this section we will create a schema class which maps to the Stateprov table in the database.
We need a to create a table class which is mapped to the sStateprov schema class. We could duplicate and modify the tCountry table class, but that will result in code duplication. The custom table class methods of tStateprov will be identical or only slightly modified. We are also going to need table classes for Towncity and Contact. Duplicating the table class over and over is going to result in lot of duplicate code. Not a good thing.
A situation like is where you want to use a superclass, and create subclasses which inherit properties and methods from the superclass.
We now have a superclass table class, tBase, and two subclass table classes, tCountry and tStateprov. All of the methods and properties of tBase, except the $sqlclassname property, is currently be inherited by the subclasses.
We now need to look at the tBase methods to check if they will work for all of the subclasses. If possible, we will modify the methods to make them generic so that they will work for all the subclasses.
Quit method ''
; Assume that the first column in the schema or query class is the primary key.
Calculate ColName as $cinst.$cols.1.$name
; If the column name does not include the suffix '_pkey' report an error and set the colname variable to null.
If pos('_pkey',low(ColName))=0
OK message [sys(85)] (Icon) {Unable to find the primary key in the [$cinst.$sqlclassname] SQL class.}
Calculate ColName as #NULL
End If
Quit method ColName
; In a query class, there can be multiple table names.
Calculate Scratch as $cinst.$servertablenames
; Parse the first server table name as the base table name.
Calculate TableName as strtok('Scratch',',')
Quit method TableName
In this section we will create a query class which joins the Stateprov records to the Country records.
You can select and drag multiple columns from the
onto a schema class.I have run into situations where the Omnis data file was finicky about the order of the foriegn key and primary key. The join would fail if the primary key was first. To avoid this problem I always put the foriegn key ahead of the primary key in the $extraquerytext. To help you remember think of it as putting them in alphabetic order, fkey come before pkey.
We need to create a table class mapped to this query class. We can either create another subclass of tBase or just copy the tStateprov table class and change the $sqlclassname property to qStateprovList.
We can test the schema and query classes using the
.; Define the list binding it to the table class.
Do List.$definefromsqlclass('tStateprov')
If List.$colcount=0
OK message [sys(85)] (Icon) {The list has zero columns after $definefromsqlclass('tStateprov')}
Else
; Set the session object.
Do List.$sessionobject.$assign($ctask.dbsessionobj)
; Get all the records.
Do List.$getAllRecords() Returns FlagOK
End If
The wStateprovList has the added complication that for each Stateprov record, the user needs to be able to select a parent CountryName record and link the Stateprov record to the Country record.
When the user selects a Country record, we will need to set the foreign key, Country_fkey, in the Stateprov record to the primary key, Country_pkey, of the Country record.
Another complication is that we cannot issue a $dowork against the tStateprovList table class because it includes columns from more than one table. The reason for this is that when a record is added to the list or updated, how does Omnis Studio decide that it should insert/update a Stateprov record, but not insert/update a Country record? In this situation we are only joining two tables, imagine a query that joins 4 or 5 tables. It would be a logistics nightmare to figure out which tables to insert, update, or delete records. Therefore, the Omnis Studio built-in $dowork will not execute for query classes which include columns from more than one schema class.
It is possible to override the built-in $dowork method and come up with your own technique for looping through the smartlist and inserting/updating/deleting records for a multi-table query class. We use this technique in the StudioWorks framework to simplify our application code.
For this tutorial we will be using the tStateprovList table class (which points to qStateprovList) to fetch the records, but then merge those fetched records into a list defined from tStateprov (which points to sStateprov). Before merging the tStateprovList records we will add a CountryName column to the tStateprov defined list. Columns added to a list defined using $definefromsqlclass are not included in the insert/update/delete SQL generated by Omnis Studio.
There are numerous user interface techniques we could use. For this example:
Since the wStateprovList window is going to be similar to the wCountryList window, we can save some work by duplicating the wCountryList window and then modifying the duplicate.
; Define and build the list variables used in this window and fetch records as applicable.
Do method retDefinedList ('tStateprovList') Returns List
If List.$colcount=0
Calculate FlagOK as kFalse
Else
; Get all the Stateprov records joined to their parent Country records.
Do List.$getAllRecords() Returns FlagOK
If FlagOK
; Define a list based on the sStateprov schema.
Do method retDefinedList ('tStateprov') Returns iList
If iList.$colcount=0
Calculate FlagOK as kFalse
Else
; Add a column to the Stateprov list for the country name.
Do iList.$cols.$add('CountryName',kCharacter,kSimplechar,100)
; Merge the query based list into the schema based list.
Do iList.$merge(List,kTrue) ;; (Match column names)
; Make the list into a smartlist.
Do iList.$smartlist.$assign(kTrue)
Do method retDefinedList ('tCountry') Returns iCountryList
If iCountryList.$colcount=0
Calculate FlagOK as kFalse
Else
; Get all the records in the Country table for use by the CountryName lookups.
Do iCountryList.$getAllRecords() Returns FlagOK
; Set the list to be a smartlist so that we can save any changes to the list.
Do iCountryList.$smartlist.$assign(kTrue)
End If
End If
End If
End If
Quit method FlagOK
; Define a main list to be used for displaying Stateprov records joined to Country records.
Do List.$definefromsqlclass(pTableClassName)
If List.$colcount=0
OK message [sys(85)] (Icon) {The [pTableClassName] list has zero columns.}
Else
; Set the session object in the list variable so that the SQL statements will be issued to that session's database.
Do List.$sessionobject.$assign(dbsessionobj)
End If
Quit method List
; Define and build the list variables used in this window.
Do method buildLists Returns FlagOK
Quit method FlagOK
On evBefore
; Find the matching country in the countries list.
Do iCountryList.$search($ref.Country_pkey=iList.Country_fkey,1,0,0,0)
On evClick ;; Event Parameters - pRow ( Itemreference )
; Set the foreign key to match the selected country's primary key.
Process event and continue
Calculate iList.Country_fkey as pRow.Country_pkey
Calculate iList.CountryName as pRow.CountryName
Do $cfield.$redraw()
On evAfter
Do method event_evAfter
; Preset the flag to true.
Calculate FlagOK as kTrue
; Make sure the CountryName entered by the user matches an entry in the Countries list.
If iCountryList.$search(low($ref.CountryName)=low(iList.CountryName),1,0,0,0)
; Update the foreign key in the Stateprov list.
Calculate iList.CountryName as iCountryList.CountryName
Calculate iList.Country_fkey as iCountryList.Country_pkey
Else
; If not, ask the user whether or not they want to add the country.
Process event and continue (Discard event)
No/Yes message [sys(85)] (Icon) {The Country Name entered does not match a country in the countries list.////Do you wish to add [$cobj.$contents] as a country?}
If flag true
; Add a line to the countries list.
Do iCountryList.$add()
Do iCountryList.$line.$assign($ref.$linecount)
; Set the country name.
Calculate iCountryList.CountryName as $cobj.$contents
; Use $dowork to inserted the new country into the database.
Do iCountryList.$dowork() Returns FlagOK
If FlagOK
; Update the foreign key in the Stateprov list.
Calculate iList.Country_fkey as iCountryList.Country_pkey
End If
End If
End If
Quit method FlagOK
Phew! That was a lot work. Better take a break and treat yourself to a coffee!
In order to open the wStateprovList window we need to add a menu line to the menu.
; Find and open an instance of the state/prov list window class.
Calculate ClassName as 'wStateprovList'
Do $clib.$windows.$findname(ClassName) Returns rClass
If isnull(rClass)
OK message [sys(85)] (Icon) {Unable to find the window class '[ClassName]'.}
Else
Do rClass.$openonce('*') Returns rWin
If isnull(rWin)
OK message [sys(85)] (Icon) {Unable to open an instance of the window class [ClassName].}
End If
End If
Quit method rWin
We are ready to test the wStateprovList window.
There are many user interface approaches you can use for this window. One of the more popular approaches is to auto-suggest a list of countries by opening a droplist of countries which match what the user has typed in the CountryName field. If the user types the letter U, a droplist displaying UK and USA is opened.
Another form of auto-suggest is type-ahead which types ahead of the user in the entry field the first matching record found in the database. If the user types the letter C, the country name Canada is entered for the user in the entry field for the user. The user can continue typing a different country name, e.g. Cambodia, and when they type Cam, if Cambodia is in the database, type-ahead will enter Cambodia in the entry field for the user.
Type-ahead and auto-suggest can be combined.
A helpful feature is to remember the last country the user selected beginning with the first letter they type, and default to selecting that country. For example if they last selected USA, the next time they type the letter U, pre-select USA rather than UK.
Creating an auto-suggest type-ahead widget which defaults to the last selection for each letter of the alphabet is beyond the scope of this tutorial. The StudioWorks framework includes an auto-suggest type-ahead widget which defaults to the last selection for each letter of the alphabet for each user.