Sw4   >   Misc   >   Misc (All Contents)

Miscellaneous

This is the catchall section for topics that don't warrant a separate category.

Constants

The oConstants object class is used to store any constant values which you want available to all of the class instances in your application. The oConstants object is instantiated using the startup task variable cn.

Property methods can be used to get and set constant values.

; Set the app mode.
Do cn.$:AppMode.$assign('web')

; Get the app mode.
Do cn.$:AppMode() Returns AppMode

To add your own constant to oConstants do the following:

  1. Subclass swBase4.oConstants to your main library, if this isn't already done.
  2. Point your main library startup task variable cn to oConstants in your main library.
  3. Copy the schema class swBase4.sConstants_listdef to your main library.
  4. Add a new column to sConstants_listdef in your main library. The column name will be used as the property method name. It is recommended that you use all lower case characters to stick with the StudioWorks _listdef naming convention.
  5. Right-click the main library Startup_Task. Close and then reopen the startup task.

    When the oConstants object is initialized during startup it will automatically add the getter and setter property methods to the oConstants object in your main library. You can then rename the method names to mixed case names for better readability. The new getter and setter property methods are available throughout your application.

You have the option of setting any of the oConstants property values from the startupsettings.txt file.

  1. Add a constants group to the APP/startupitems/startupsettings.txt.
  2. Add a propery and value for each constant you want to set from the startup settings files.

    constants {
        appmode = 'web' ;
       }



    Note

    Always remember to include the ; delimiter after each property. The property names are case insensitive.



    During startup the swBase4.oStartupTaskVarTool.$init_cn method loops through the constants group of the startupsettings file and set the values in oConstants. An error will be logged if the $assign setter method is not found in oConstants.

Cached Lists and Module Data

Cached lists contain additional information about your StudioWorks application.

  1. SQL Meta-Data
  2. Window Instances
  3. Window Menu Lines
  4. Navigation Menu Lines, groups, and subgroups
  5. Report Instances
  6. Icons List
  7. String Tables

Cached lists are stored in the oModuleData objects classes. StudioWorks automatically creates an oModuleData object in a library when needed. Each module stores its own portion of each of the cached lists in its own oModuleData object. The oModuleDataTool is used to access the module data.

Cached lists save a lot of startup time. For larger applications it would take a lot of time to build the cached lists and the string tables cached list on startup.

When you open your app, StudioWorks loads the cached lists using the oModuleDataTool object class. The portions of each cached list which are stored in all of the libraries are merged together into master cached lists that are used by the application. The master cached lists are accessed through the applicable tvars. (lsts, stb, rprts, icns, etc.)

  1. SQL Meta-Data list - held in memory in oSQLLists instantiated by the lsts tvar.
  2. Window Instances list - held in memory in oWindows instantiated by the wn tvar.
  3. String Tables - held in memory in oStringTables instantiated by the stb tvar.

The Programmer Workbench is where you can view and edit the cached lists. When you make changes to a cached list item in the Programmer Workbench the changes are immediately saved to the oModuleData object of the applicable library. This is done so that you won't (shouldn't) lose any cached list changes made if Omnis Studio should crash on you.

Some cached list changes cascade to other cached lists. e.g. If you change the name of a schema class column, it could affect numerous query classes, and it will affect the string tables. The Programmer Workbench does its best to do the cascading updates for you, but you may find a situations where it doesn't correctly cascade the changes. Please report these situations with as much details as possible so that they can be corrected in the StudioWorks framework.

If you add a SQL class (schema or query) in the Omnis Studio IDE the cached lists will not be aware of the new SQL class. In order to add the SQL class to the cached lists you will need to click on it in the SQL Meta-Data window of the Programmer Workbench. When you click on the new SQL class, the StudioWorks framework realizes it doesn't have that SQL class in its cached list and immediately adds the SQL class and its columns to the cached lists with all of the default values and saves it to the the SQL class' source library oModuleData object. As you make changes to the SQL class' meta-data each of the changes is saved back to the oModuleData object.

Tip

If you are not using the VCS you should set the $alloweditifnotcheckedout Omnis Studio preference to kTrue. This will save StudioWorks from having to first test cascading updates to make sure any affected classes are checked out before actually doing the cascading updates. It will cut the time for cascading updates in half.

In pre-2008-07-31versions you were editing code or meta-data and then building the cached lists from your code or meta-data. As of the 2008-07-31 release you are now directly editing the cached lists, negating the need to 'rebuild lists'. In some cases it might feel a bit sluggish editing module data in the Programmer Workbench, but you are saving loads of time by not having to constantly rebuild cached lists.

If you want to see what is really in the cached lists, select the Cached Lists tab in the Programmer Workbench. This shows you exactly what is currently in each cached list. You can shift+click on any of the tabs in the Programmer Workbench to open the tab's subwindow in a stand alone window. The cached list window contents are refreshed each time the window comes to the top.

Create New SW App

When you are learning StudioWorks the easiest way to create a new StudioWorks app is by following the instructions given in the Quick Startup Tutorial. The Quick Startup Tutorial takes you through the steps of modifying StartNewApp which is the demo app that comes which each release of StudioWorks.

If you want to create a new app from scratch, here are the steps to follow:

Note

Be sure to complete the Quick Start Tutorial before you attempt to create a new app from scratch.

  1. Download and unzip the lastest StartNewApp zip file from the StudioWorks FTP server.
  2. Decide on a 2 or 3 letter lower case acronym for your company name which you will use as a prefix for your library names. In this example we'll use xyz as the company prefix. You substitute your own company prefix.
  3. Decide on a short name for your application. In this example we'll use Great App as the application name. You substitute your own app name.
  4. Rename the unzipped StartNewApp folder to Great App. The Great App folder is referred to as the APP folder. The entire application is enclosed inside the APP folder.
  5. Inside the Great App folder, rename open_myApp to open_GreatApp.
  6. Inside the APP/libraries folder, rename myAppMain to xyzGreatAppMain.
  7. Inside the APP/libraries/modules folder, rename mySysAdmin.lbs to xyzSysAdmin.lbs. The SyAdmin module contains the users and groups schemas plus other classes which you need for your StudioWorks app.
  8. Inside the APP/libraries/modules folder, delete all of the other libraries and the app_prefs.df1 file. None of these files are needed for creating your new app. All that should be left in the modules folder is the xyzSysAdmin.lbs library.
  9. Inside the APP/startupitems folder, delete the NewData.df1 file. This file contains the initial data which gets inserted into a new database.
  10. Inside the APP/data folder, delete the myAppData.df1 file.
  11. If you are going to use something other than an Omnis data file located inside the APP/data folder, you will need to modify the logon settings in the startupsettings.txt file. For more information see StudioWorks > Configuration > Startup Settings > Logon
  12. Open the open_GreatApp library. This opens the xyzGreatAppMain library, which in turn opens the libraries (currently just xyzSysAdmin) located in the APP/libraries/modules folder and the libraries at the first level in the APP/studioworks folder. The StudioWorks framework libraries are located inside the studioworks folder.
  13. All going well you will get to the Sign-In window.
  14. If you are using the Omnis data file you can create one as follows:
    1. Click the Change Session button.
    2. Select the OMNISSQLDAM session and click the Edit button.
    3. Click the orange file cabinet icon to the right side of the Host Name field. The tooltip on the button says 'Create a new Omnis data file.'
    4. Navigate to the APP/data/ folder and create a new data file called GreatApp.df1
    5. Click the Save button in the Session Manager toolbar.
    6. Close the Sessions Manager window.

  15. If you are using a different RDBMS you will need to create the new database using the RDBMS of your choice (MySQL, FrontBase, etc.). If the RDBMS requires users and passwords, I recommend that you create the user DBADMIN as the user who will own all of the tables in the database.

  16. Test logging onto the new database by clicking the Change Session button in the Sign-In window, then select the correct session, and click the Test button in the Sessions Manager toolbar. If you can't logon through the Session Manager, you won't be able to logon through the Sign-In window! Tinker with the session settings in the Session Manager until you can successfully test logon. Once you can Test logon, close the Sessions Manager window.

  17. In the Sign-In window enter user name: SYS and password: pass. Click the Sign-In button.

  18. StudioWorks will logon to the database, discover that there are no tables in the database and prompt you to make sure you want to syncn the new database to the application. Click Continue.

  19. All going well the database will be correctly synchronized and you will be prompted with instructions on what to do next.

  20. Click the Sign In button again. All going well the My App main menu will be installed and the main window will be opened.

  21. Close the main window.

  22. You will notice in the F2 Browser that the library names have not been changed. You will need to change the $defaultname of each renamed library to match the new file name.

  23. Select myAppMain in the F2 Browser > F6 Properties > Prefs tab. Change the $defaultname to xyzGreatAppMain. The library name in the F2 Browser won't change until you close and reopen the library.

  24. Change the xyzSysAdmin default name to xyzSysAdmin.

  25. F2 Browser > select myAppMain > open the schema class sMn_stb.

  26. Change the description for the AppName column from My App to Great App. StudioWorks will now translate AppName to Great App where ever AppName is uses as a string table ID.

  27. Change the column name myAppMain to xyzGreatAppMain.

  28. My App menu > select Close My App. This closes any libraries located in the APP/libraries/modules folder and the main library located in the APP/libraries folder.

  29. Reopen the app by double-clicking the open_GreatApp file. All going well the string tables will be reloaded so that after you sign-in you will see Great App as the main menu name. In the main menu, Close My App will have changed to Close Great App.

  30. If the text didn't change rebuild the string tables. Programmer Menu > Reload String Tables.

  31. Create your a fresh new module using Programmer Menu > Create New Module...

Okay, you've got a fresh new StudioWorks app ready to go. Enjoy!

Observers

The section explains how to implement an observer using the oObserver class.

Observers are great for loose coupling of different subwindow instances. Suppose you are viewing a record in one subwindow and if the user changes to a different record you want another subwindow to be notifed when ever this happens. Observers are your friend for setting this up.

oObserver Class

The oObserver object class in swBase4 makes it very easy for you to set up and observer. There is also an oEventsObserver class which is a subclass of oObserver that specialized in notifying observers of event handling event.

To setup an observer you need to have an instance of oObserver inside the class where the change that you want to track will occur.

  1. Add an appropriately named ivar to the class. e.g. ioChangeRecordObserver
  2. Set the type to Object and point it to swBase4.oObserver
  3. Right-click ioChangeRecordObserver and select Interface Manager...
  4. Note the following public methods:
    • $attach - Use to attach an observer. (Generally I only have one observer, but the object will support up to 400 observers) Another name which could used for this method is $subscribe
    • $remove - Used to detach an observer (I rarely use this method) Another name which could used for this method is $unsubscribe
    • $notify - Use this method to notify all the attached observer when the things they have subscribed to observer has happened. Another name which could used for this method is $publish

Attach Observer

In order to attach an observer, we need to add an accessor method to the window class methods. Observers will use this method to subscribe for notifications when the thing they want to know about happens.

  1. Add an appropriately named class method. e.g. $attachChangeRecordObserver with the parameters pfrObserver - field reference and pCallBackMethod - character.
  2. This method forwards the call to ioChangeRecordObserver

    ; Forward the message to the observer class instance.
    Do ioChangeRecordObserver.$attach(pfrObserver,pCallBackMethod) Returns FlagOK
    Quit method FlagOK

  3. Other window instances can now attach themselves as observers. Or you could have a container window subscribe one of its subwindows to observe events in another subwindow.

    ; Subscribe the list subwin to be notified when ever the edit window changes its record.
    Do rEditSubWin.$attachChangeRecordObserver(rListSubWin,'$updateEditRecordChanged') Returns FlagOK
    Quit method FlagOK

  4. Make sure the observer instance has the callback method which was specified when it subscribed.
The great flexibility in the observer design pattern is that the call back method can be whatever you want it to be and you can change it without the observed class knowing or needing to change anything. Without the observer design pattern you would have to hard code the exact method calls on both sides.

Notify Observers

Finally, we need to notify the observers when the thing they are asking to be told about happens.

  1. Inside the class which has the ioChangeRecordObserver ivar you will need to add code in methods or event where the thing beings observed will happen.
  2. When the big moment happens, you simply send a $notify message to the the oObserver instance with parameter(s) that will be useful to the subscribers.

    ; Notify the observers.
    Do ioChangeRecordObserver.$notify(iList) Returns FlagOK
    Quit method FlagOK

That is all there is too it. Any attached observer will be notified through the call back method which the observer specified when it subscribed.

Performance Testing

A Programmer_Task is availalbe to developer for testing and improving performance of methods.

There are two Programmer Menu items used to interface with the programmer task millisecond timecount functions.

  1. Programmer Timecount (on/off) - Selecting this menu item toggles the timecount counter. When the menu is checked the timecount is on.
  2. Programmer Timecount Log - Use this menu item to look at the timecount log results window. You can leave the window open. Each time it comes to the top, the list in the window is updated.

If you have a method or series of method you want to check to find out where the greatest performance hit is taking place, do the following.

  1. Turn the Programmer Timecount on by selecting the menu item so that it is checked.
  2. Add $logTimer message to various places in your methods where you want to capture the time.
  3. Put a 'begin' $logTimer message at the beginning of the method

    Do $itasks.Programmer_Task.$logTime($cmethod,'begin')

  4. Put an 'end' $logTimer message at the end of the method

    Do $itasks.Programmer_Task.$logTime($cmethod,'end')

  5. You can also put a 'begin added comment' and 'end added comment' $logTimer messages at various points in any method.

    Do $itasks.Programmer_Task.$logTime($cmethod,'begin')

    Do $itasks.Programmer_Task.$logTime($cmethod,'begin SQL fetch')
    Do List.$getAllRecords() Returns FlagOK
    Do $itasks.Programmer_Task.$logTime($cmethod,'end SQL fetch')

    If FlagOK
       
       Do $itasks.Programmer_Task.$logTime($cmethod,'begin loop')
       For List.$line from 1 to List.$linecount step 1
          
          ; Do some stuff.
          
       End For
       Do $itasks.Programmer_Task.$logTime($cmethod,'end loop')
       
    End If

    Do $itasks.Programmer_Task.$logTime($cmethod,'end')
    Quit method FlagOK

  6. Run the method(s).
  7. Select the Programmer Timecount Log menu item to look at the results.
  8. Run the same method or series of methods several times to compare the results.
  9. You can then zero in on the methods or parts of methods which are taking the greatest amount of time.

Preferences

This section covers the different types of preferences in StudioWorks, how to get and set them, where they are saved, and how to add your own preferences.

StudioWorks classifies three types of preferences:

  1. Application Preferences These are preference settings and values which are specific to the application that you write. Application preferences are shared by all the runtime users of the application at each site. The cached lists and string tables are application preferences.
  2. Local Preferences These are preference settings which are specific to each user of the application. The size and location of each window when it was last closed and the last selected value in a lookup droplist are examples of local preferences.
  3. Module Preferences These are preference settings which are specific to a particular module of the application. The default currency is an example of an accounting module preference. The security default timeout and default signoff are examples of system administration module preferences.

App Preferences

The oAppPrefs object class is the interface for getting, setting, and saving app prefs. The main library startup task variable, app_prefs, instantiates the oAppPrefs object class, located in the main library.

Using the Interface Manager you can view all the property methods of the oAppPrefs object. Each property method has a getter and setter method.

When you open your application StudioWorks loads the application preferences from one of two locations:

  1. app_prefs.df1 data file location in the APP/libraries/modules folder.
  2. AppPrefs table class in the main database. See Shared Application Preferences for more information.

The prefs are loaded into a row variable of the oAppPref object. The row variable is defined using the sAppPrefs_listdef schema classes located in swBase4 and in the main library.

To add your own application preference:

  1. Add a column to the sAppPrefs_listdef schema class located in your main library. e.g. TechSupportEmail
  2. Close and reopen the main library startup task.

When the oAppPrefs object is initialized it automatically adds getter and setter methods for the new column which you added to the sAppPrefs_listdef schema class in the main library.

The following sample code shows you how to set or get any app pref value.

; Set an app preference.
Do app_prefs.$:developercompanywebsite.$assign('www.vencor.ca')

; Get an app preference.
Do app_prefs.$:developercompanywebsite Returns URL

; Save all app preferences.
Do app_prefs.$savePrefs() Returns FlagOK
If not(FlagOK)
   Do errhndlr.$promptonceLastError()
End If
Quit method FlagOK

Note

The app prefs are saved each time you use the Programmer Menu to do any rebuild, reload, or runtimize operations.

Shared Application Preferences

The problem with using the app_prefs.df1 data file for storing the app prefs is that you have to redistribute the file to all of the runtime users when ever you make a change that affects any of the cached lists. For larger apps the app_prefs.df1 file can grow to several MB in size.If an application is being used by a group of users on the same database it is much more efficient to store the app prefs in the main database. To switch an StudioWorks application to use the main database for storing the app prefs:
  1. If your application is not set to using tablesownerlogon follow the instructions found under the topic Tables Owner Logon to set this up.
  2. Open the APP/startupitems/startupsettings.txt file with a word processor.
  3. Find or add a constants group to the file and a SharedAppPrefs property as follows:

    constants {
        sharedappprefs = TRUE ;
    }



    Note

    Be sure to remember the ; delimiter at the end of the line!

  4. Close and reopen the application with your developer version of Omnis Studio.
  5. Programmer Menu > Build Runtime Lists

    This completely rebuilds all the lists for use by the runtime users. You will be asked if this is a rebuild for a runtime release.
    • If you answer yes, the cached lists will be copied to app_prefs and then saved to the main database.
    • If you answer no, the cached list are copied to app_prefs, but they are not saved to the main database. Instead of saving the app_prefs to the database they are copied to a special column of the local_prefs. This is done to prevent messing up the shared app prefs if you are developing the application on the same database as the runtime users.


        The first time you rebuild the runtime lists StudioWorks automatically creates the AppPrefs server table in the main database for you.

Local Preferences

The oLocalPrefs object class is the interface for getting, setting, and saving local prefs. The main library startup task variable, local_prefs, instantiates the oLocalPrefs object class, located in the main library.

Using the Interface Manager you can view all the property methods of the oLocalPrefs object. Each property method has a getter and setter method.

When you open your application StudioWorks loads the application preferences from the local_prefs.df1 data file located either in the preferences folder used by the StudioWorks app. The preferences folder is normally in the OMNIS folder, but it could be in the APP folder depending on your startup settings.

Note

Keeping the preferences in the OMNIS folder is recommended so that the user's local preferences will persist even if they replace their entire APP folder.

The prefs are loaded into a row variable of the oLocalPref object. The row variable is defined using the sLocalPrefs_listdef schema classes located in swBase4 and in the main library.

To add your own application preference:

  1. Add a column to the sLocalPrefs_listdef schema class located in your main library. e.g. LastUserName
  2. Close and reopen the main library startup task.

When the oLocalPrefs object is initialized it automatically adds getter and setter methods for the new column which you added to the sLocalPrefs_listdef schema class in the main library.

The following sample code shows you how to set, get, or save any local pref value.

; Set local preference.
Do local_prefs.$:LastUserName.$assign('John Smith')

; Get a local preference.
Do local_prefs.$:LastUserNam Returns UserName

Note

The local prefs are saved each time you close the main library startup task.

Module Preferences

You could store module preferences in a special table in the database. An alternate approach used in StudioWorks is to store them in columns of a row variable in the Refs table. Doing so negates the need for creating additional tables in the database for each module and the need for adding columns to the servertable in the database each time you need to add a new preference for the module.

The StartNewApp demo has a myModulePrefsDemo library which includes the classes needed to implement the StudioWorks approach to storing module preferences in the columns of a row variable in the Refs table.

The mySysAdmin of the StartNewApp demo has an oSysPrefs object class and wSysPrefs window class which you can also review.

The design pattern for module prefs is similar to oAppPrefs and oLocalPrefs. When you want to add a new preference you simply add a column to the applicable sModuleNamePrefs_listdef schema class and rebuild lists.

StudioWorks adds getter and setter propery methods to the oModuleNamePrefs object and you are ready to go!

You can add meta-data for the label and tooltip and then add the edit field to the wModuleNamePrefs window class.

You can directly get any preference settings by instaniating the oModuleNamePrefs object and sending the respective message to the object.

To implement module preferences in one of your libraries do the following:

  1. Create a module preferences list definition schema class in your library. e.g. sPayrollPrefs_listdef
  2. Add at least one preference column to the schema class.
  3. Add the text and tooltip in the description column. Delimit the text and tooltip with the ` backquote character
  4. Set the $servertablename to the string table name you wish to use for the module preferences. e.g. Payrollprefs
  5. Copy oModulePrefs from myModulePrefsDemo to your library and rename it to a suitable name for your library. e.g. oPayrollPrefs
  6. Select the $:PrefsSchemaClassName property method of the module preferences object class and change the return value to the match name of the module preferences list definition schema class you created in your library. e.g. sPayrollPrefs_listdef
  7. Copy wModulePrefs from myModulePrefsDemo to your library and rename it to a suitable name for your library. e.g. wPayrollPrefs
  8. Change the $dataname property of each edit field in the window class to match the appropriate column names in your module preferences schema class. Change each $tooltip to match the $servertablename and appropriate column name. Delete any extra fields and labels.
  9. Change each $text property of any labels to match the $servertablename and appropriate column name.
  10. Set the ioModulePrefs ivar of the window class to point to your oModulePrefs module. e.g. oPayrollPrefs.
  11. Add your module prefs window class to the oWindowsList object of your module. See oWindowsList in myModulePrefsDemo if you need more details.
  12. Add your module prefs window instance ID to the oMenusList object of your module. See oMenusList in myModulePrefsDemo if you need more details.
  13. Add your module prefs window instance ID to the sWn_stb schema class of your module.
  14. Rebuild all lists and reload string tables for your module.

    All going well your module preferences window instance will appear in the navigation treelist. Clicking the node will present your module preferences window class instance and you can set your instance values.
Tip

If you want to have default values for any of the module preferences you can set the default in the appropriate propety method of your module preferences object class.


If isnull(iPrefsRow.ColName)
Calculate iPrefsRow.ColName as 'DefaultValue'
End If
Quit method iPrefsRow.ColName

Refs

The Refs server table can be used for storing many different bits of information for use by your application.

Refs is an abbreviation for References. The swRefs4 library is where the various Refs related classes are contained.

Some of the possible uses of the Refs table are:

  1. Edit helper droplists or combo-box lists.
  2. Counters - For keeping track of the LastPONumber, LastInvoiceNumber, ...
  3. Storing preference settings which apply to all of the users of a database. (System Preferences, Report Properties, ...)
  4. Business rules that are soft coded.

The primary use of Refs is for edit helper lists to suggest possible entries in a field (Mr., Mrs., Ms., Dr., Sir, Lord,...), or limit the possible entries in a field. (F - Female, M - Male, U - Unknown) In the SQL Meta-Data Editor these are called Refs Lookups.

The oRefs object class is the interface which you should use to access the Refs records. oRefs is instantiated by the main library Startup_Task task variable, refs. To get group/subgroup set of refs records from anywhere in your application you could do the following:

Do refs.$retLookupList(pRefsGroup,pRefsSubGroup) Returns LookupList

If you want to add your own custom methods to oRefs, or wish to modify the behavior of an existing oRef method:

  1. Create a subclass of oRefs in your main library if it doesn't already exist and point the refs main library Startup_Task task variable to the oRefs subclass in your main library.
  2. Override and modify or add your own custom methods to the oRefs subclass in your main library.

Refs Table Columns

The sRefs schema class lists all of the columns in the Refs table.

The Refs columns of interest are:

  1. RefsType - This is set to lookup is you are working using the SQL meta-data Refs Lookups.
  2. RefsGroup/RefsSubGroup - For lookups these match the group/subgroup you enter in the SQL meta-data Refs Lookups.
  3. RefsDesc - The description to be displayed to the user.
  4. RefsDataType - Specifies the Refs column where the actual Refs record value is stored.

    Each Refs table record has columns for storing different data types. (RefsBinary, RefsBoolean, RefChar, RefsDate, RefsTime, RefsInteger, RefsNumber2dp, RefsNumber3dp, RefsNumberFloatdp, RefsPicture, RefsRow) By setting RefsDataType = char, the oRefs object knows to look in the RefsChar column for the actual stored value. This gives you the flexibility of storing virtually any data type in any refs record.
  5. RefsDisplayText - The actual Refs record value in the specified RefsDataType column is copied to this column, so that we have a common column for displaying the record's value. The table class tRefs automatically sets this value on insert or update. 

Refs Lookups

As stated previously, the most common use of the Refs table is for edit helper lists.

For example you might have a person table with as a PersonGender column. For consistent data entry you want to give the users 3 options (F - Female, M - Male, U - Unknown).

You don't want to create a special lookup table for Gender, which then requires you to join Person and Gender each time you want to include the person's gender in a list or report. You simply want to stamp M, F, or U in the PersonGender column.

You don't want to hard code the options of F,M,U because your application might be used in a company where English is not the primary language and F,M,U would not be the letters they would use for PersonGender.

The SQL Meta-Data Refs Lookup makes this very easy to implement.

  1. Open the SQL Meta-Data Editor, select the sPerson schema, select the PersonGender column.
  2. Enter Person in the Refs Group field.
  3. Enter Gender in the Refs Sub Group field.
  4. Check the Mandatory field to enforce matches with the Refs Lookup.
  5. Close the SQL Meta-Data Editor.
  6. Open the Refs window from the Main Window.
  7. Insert a New Refs record. Type=lookup, Group=Person, SubGroup=Gender, SortOrder=0, RefsDesc=Female, RefsDataType=Char, RefsChar=F. Save the record.
  8. Create a New Copy of the previous inserted Refs record. Set RefsDesc=Male, RefsChar=M. Save the record.
  9. Create a New Copy of the previous inserted Refs record. Set the RefsDesc=Unknown, RefsChar=M. Save the record.

In the auto configure edit window the PersonGender will appear as a lookup field and the user will be able to choose from the 3 options which you entered in the Refs table. If the user types ? in the field a droplist will appear with the following options:

F Female
M Male
U Unknown

Leave the SQL Meta-Data Mandatory checkbox unchecked if you only want to let users choose from a list of suggestions or type any value they want. For example in a NamePrefix field your might want to provide a lookup list that suggests (Mr. Ms. Mrs. Dr. Sir Lord,...) If Mandatory is not checked in the meta-data the user can pick one of the droplist name prefixes or enter their own value. When they leave the field the RefsDisplayText value they picked or other value they entered is what gets stored in the NamePrefix field.

The Refs Lookups are not meant to work as hidden foreign key child/parent table relationship. The Refs Lookups are data entry helpers. The column in the target table gets stamped with the actual refs value which the user can see and edit. Normally the actual Refs actual value is an abbreviation or ID which is meaningful to the users.

The problem with trying to make the refs lookups do "hidden things" behind the scenes is that without a true child/parent - fkey/pkey relationship something is sure to go wrong down the line. You will have a pile of records in a table that are loosely linked to a Refs record and with special code are depending on the loose connection to display the RefsDesc in your window and headed lists. Along comes an unsuspecting System Administrator and they delete the Refs record or change the 'Refs Value' and whamo your edit window, lookup list, and headed lists are dead in the water. The lose connection that was based on the Refs value is lost.

Refs Lookups vs. Child/Parent Table Relationships guidelines:

  1. If you want to store lookup 'keys' which you 'hide' from your user, go with a true lookup table and an fkey to the lookup table.
  2. If you want a flexible edit helper that suggests or limits the possible entries in a particular field, then use the Refs Lookups.

The alternate solution for #2 is a hard coded droplist or combo-box ... but it seems users always want to change the options, so the Refs lookups are much more flexible solution. They give you the options of droplist or combo-box style editing based on the meta-data Mandatory setting and you can give the user and/or sys admin access to adding/changing the lookup entries.

If it works for the situation - option #2 lets you reduce the number of tiny lookup tables in your database and the number of joins you have to do for lists and reports.

Refs Counters

Incremental counters are sometimes needed in your application. (eg. Purchase Order numbers, Invoice Numbers, Employee IDs, etc.)

The oRefs object supports incremental counters through the $retNextCounterNum method.

Let's say you have a PurchOrder table with a PONum field. In the method where the user starts a new purchase order you can get the next unique PO number from Refs using just one line of code:

Do refs.$retNextCounterNum('PurchOrder','PONum) Returns NextPONum

If a counter doesn't already exist for PurchOrder/PONum the oRefs object automatically inserts a refs counter record for PurchOrd/PONum with the default value of 1000 and then increments it to 1001. If the record exists, oRefs fetches the record, increments its counter value, updates the record in the database, and if the update succeeds returns the incremented value to the sender.

The oRefs object has its own autocommit session with the database to ensure that counters don't get rolled back. Once a counter has been incremented it can't be rolled back. To do so would cause problems in a multi-user environment.

StudioWorks will automatically set up a counter and use if for a field if you enter [refscounter] as the Default Value for a field in the SQL meta-data editor.

Refs System Preferences

I find the every time you turn around someone has the need for yet another system preference that needs to be store in the database. Having to add a column to an sSysPrefs schema and then to the table in the database adds to your work and makes it difficult to keep applications in sync with the database. Especially if you have multiple customers with multiple databases.

System preference data types vary. They could be character, date, time, boolean, integer, or other data types.

The Refs table can be use for storing your system preferences. You can insert Refs records with the RefType='syspref' and then set the RefsGroup, RefsSubGroup, RefsDesc, etc. to whatever values you like.

You can create a special qSysprefsList and qSysprefsEdit query class ($extraquerytext = WHERE RefsType='syspref') and declare window instances SysprefsList and SysprefEdit for viewing and editing your own System Preferences.

Any time you need another system preference, you simply add it to the Refs table with the RefType='syspref'.

You may want to create an oSysPrefs object with property methods that fetch the appropriate Refs record(s) through the refs task variable.

Do refs.$retSysPrefsList(TableName) Return SysPrefsList

These are just some ideas and suggestions on how you can use the Refs table for storing your System Preferences.

Credits

The Refs library is the inspiration of Robert Mostyn. At OmniCamp UK 2004 Robert explained his technique of having a References table for storing all kinds of short lookup lists, user defineable business rules, preferences, etc.

Robert pressed me to implement it in StudioWorks. It took a while for the power of this technique to sink in but as I developed new applications it become clearer to me that a Refs table would be handy in many situations, and flexible.

The decision to put the Refs table into its own swRefs4 library was inspired by the concept used by many Unix programs where they create and use many small programs which have a limited well defined scope of functionality. By keeping the swRefs4 library's scope narrow, it can be more easily implemented in a wider range of StudioWorks applications. At least that's what we're hoping for.

I hope that the swRefs4 library will assist you in more quickly developing your applications and allow them to be more flexible.

Thanks for Robert Mostyn for sharing the Refs table strategy with us.

Doug Kuyvenhoven
Vencor Software

Security

The oSecurity object is the security guard of your StudioWorks application. oSecurity is instantiated by the main library Startup_Task task variable secur.

When a user signs in to a StudioWorks application, the wSignIn window sends a $signIn(pUserID,pPassword) message to the oSecurity object. The $signIn method searches for the UserID in the list of users and, if found, verifies the password. If the UserID is found and the password matches; information about the user and their security clearances (window instances and schemas) are loaded into the oSecurity object instance and kTrue is returned to the sender. The sign-in process continues and the main window is opened where the user is presented with the navigation treelist of window instances which they are permitted to access.

Sign-in is the first level of security in StudioWorks. The next levels of security in StudioWorks are:

  1. Window Instances - whether or not the user may access a window instance.
  2. Schemas - whether or not the user has permission to view, insert, edit, or delete records of a particular schema class.
Note

Any user which is declared to be a system administrator has access to all window instances and has full permissions for all schema classes. You do not need to set security for system administrators.

Tip

If have an application which does not require security, or a client who does not require security, you can disable StudioWorks security by setting the $:EnforceSecurity property of the oSecurity object to false.

Do secur.$:EnforceSecurity.$assign(kfalse)

Window Instances Security

Every window instance in a StudioWorks app is declared by the oWindowsList objects. There is one oWindowsList object in each module. The WinInstID must be unique for each window instance.

StudioWorks automatically declares a default list and edit WinInstID for each schema class. For the schema class sAuthor, StudioWorks would declare the WinInstIDs AuthorList and AuthorEdit.

The system administrator has control over which WinInstIDs each user is permitted to access. The navigation treelist only shows WinInstIDs which the user has permission to access.

A user may have access to a window instance, but that doesn't mean the user can edit, insert, or delete records within the window. Those permissions are controlled by schema security.

Schema Security

The system administrator has control over which servertables the user is permitted to access in the database. StudioWorks security uses the schema classes which are mapped to servertables. The security control is broken down into the following permissions for each schema class:

  1. View - The user can fetch records and view them.
  2. Insert - The user can insert new records.
  3. Edit - The user can edit existing records.
  4. Delete - The user can delete existing records, provided that there aren't any child records with restricted delete constraints.
  5. MultiUpdate - The user can mass update a field of multiple records at the same time. (This is a StudioWorks future enhancement.)

Setting Security Privileges

The system administrator controls security for users and groups.

  1. Users - Each user must be added to the database. The Usr table is normally where the user records are stored.
  2. Groups - Window instance and schema class privileges can be given to a group. Users can be linked to one or more groups. This makes it easier to administer security for large groups of users.

Window instances and schema class permissions are set by the system administrator in the Security window. In the Security window you can add or remove users from groups, allow or deny access to window instances, and set permissions for schema classes.

Some window instances have a baseschema declared for it in the oWindows list. If access is given to the WinInstID, but view permission is not given to the schema class, a red checkmark will be displayed for the WinInstID access in the Security window.

Tip

It is recommended that a Group be set up for each type of task or job function and security privileges be set for the Group, then Users added to the Group. This makes it much simpler to administer security. If a user's position or job function changes they can be added or removed from the applicable groups.

Writing Security Sensitive Code

From the StudioWorks developer's point of view, you normally only need to deal with the public interface of the oSecurity object class. The oSecurity object class is instantiated by the main library Startup_Task task variable, secur.

Messages can be sent to oSecurity as needed in your code.

Do secur.$:UserKey Returns UserKey
Do secur.$:UserFormalName Returns FormalName

If secur.$canDelete(BaseSchema)
   ; Display the 'Delete' button in the toolbar
   ; or delete the selected records.
End If

Be sure to review the public methods of the oSecurity object if you are writing security sensitive code.

Special Security Groups

You can create groups for purposes other than window instance or schema security. You may need to create a special group for users which are authorized to click an Accept Order button on a particular window.

You could create an AcceptNewSalesOrders group and add users to this group in the Security window. When the window instance is opened you could enable/disable the Accept Order pushbutton based on whether or not the current user is a member of the AcceptNewSalesOrders group.

; Enable/disable the 'Accept Order' button based on whether or
; not the user is a member of the 'AcceptNewSalesOrders' security group.
Do secur.$isMemberOfGroup('AcceptNewSalesOrders') Returns bAuthorized
Do irAcceptOrdersButton.$enabled.$assign(bAuthorized)

Testing User Security

There is a Change User... button on the wSecurity window which prompts you with a list of users. You select a user and continue. The changeUser method then automatically signs you in using the selected user's ID and password. The menus and window are reinstalled so that you can see exactly what windows the user can access and what menus and toolbar buttons are available to the user.

When you are done, select Sign-In (Logon) from the main menu, and sign-in as yourself again.

Schema Security Technical Details

The sSchemaSecurity_listdef defines the schema security list used to keep track of the user and group permissions for all of the schema classes. A series of boolean columns, (canview, canedit, caninsert, candelete, canmultiupdate) indicate the specific permissions.

The schema permissions are normally set for a group. When a user is a member of a group, the user inherits the group's permissions. The user's permission is null if they are inheriting the group's permissions. It is possible to override the group permissions for a specific user (though not recommended).

A user could be given different permissions for the same schema class in different groups which the user is a member of. When this happens the user is given the highest permissions for that schema of the groups which it is a member of. This makes for some pretty complicated code in the oSecurity object. StudioWorks has to loop through all of the groups which the user is a member of, then loop through the schema classes and check the permissions and set each permission to the highest level given for the user.

The list and edit window instances decide which toolbar buttons to display based on the user's permissions for the baseschema of the SQL class being used for the window instance.

If you are writing any security sensitive code use the public interface methods of the oSecurity object class the the secur task variable. You can view all of the method by right-clicking on secur in the task variables pane and selecting the Interface Manager menu line. Each method has a description which you can read in the bottom tab pane of the Interface Manager.

Security Timeout

There is a security timeout option available with the StudioWorks framework. The timeout option provides an added level of security to a StudioWorks application by opening a timeout window in front of all the other Omnis Studio windows if there is no user activity in the StudioWorks application for a predetermined amount of time. If a user leaves their computer without closing their application, the timeout security will prevent other users from accessing the user's application on their computer after the predetermined amount of inactive time.

Users could get the same type of security on their computer by using a screen saver with password protection. However, that puts control of timeout security in the hands of the users. If enabled, the StudioWorks timeout will enforce timeout security for everyone who has the StudioWorks application open.

Enabling Timeout Security

To enable the security timeout option modify the $signInOKContinue method of the main library Startup_Task to send an $initializeTimeoutTimer message to the oSecurity object followed by a $startTimeoutTimer message.

The following sample code is copied from the initializestartTimeoutTimer method of the Startup_Task of the myAppMain library of the StartNewApp demo.

; Does the user profile, specify a timeout or signoff?
If secur.$:UserTimeoutMinutes.$cando
   
   Do secur.$:UserTimeoutMinutes Returns TimeoutMinutes
   Do secur.$:UserTimeoutSignOffTime Returns SignOffTime
   
End If

; If the timeout minutes is zero, use the default system preference timeout.
If TimeoutMinutes=0
   
   ; Use the system prefs default timeout.
   If oSysPrefs.$:DefaultTimeoutMinutes.$cando
      Do oSysPrefs.$:DefaultTimeoutMinutes Returns TimeoutMinutes
   End If
   
End If

; If the signoff time is null, use the default system preference signoff time.
If isnull(SignOffTime)
   
   If oSysPrefs.$:DefaultSignOffTimeOfDay.$cando
      Do oSysPrefs.$:DefaultSignOffTimeOfDay Returns SignOffTime
   End If
End If

; Initialize the timeout timer.
If TimeoutMinutes>0&not(isnull(SignOffTime))
   If secur.$initializeTimeoutTimer.$cando
      Do secur.$initializeTimeoutTimer(TimeoutMinutes,SignOffTime) Returns FlagOK
      If FlagOK
         
         ; Start the timeout timer.
         Do secur.$startTimeoutTimer() Returns FlagOK
         
      End If
   End If
End If
Quit method FlagOK

This method is called by $signInOKContinue right after the default method is run.

; Redirect to the startup task default methods object.
Do redirect ioStartupTaskDefaultMethods Returns FlagOK
If FlagOK
   
   Do method initializestartTimeoutTimer Returns FlagOK
   
End If

If not(FlagOK)
   Do errhndlr.$promptonceLastError()
End If
Quit method FlagOK

The timer is reset each time the user opens a window or switches subwindows, clicks on a toolbar button or a headed list, or presses a key while in an entry field. (You can find all the places where the timeout timer is reset by searching the StudioWorks libraries for Do secur.$resetTimeoutTimer)

If the timer is not reset for the specified number of timeout minutes, the timer is stopped and the wSecurityTimeoutSignIn window is opened. The timeout window opens in front of palette windows, does not allow clicks behind, and disables menus and toolbars. The user must reenter their password in the timeout window. If the user correctly enters their password the timer is restarted and the timeout window is closed allowing them to continue working where they left off. If the user incorrectly enters the password 5 times the timeout window force quits Omnis Studio.

Setting Timeout Minutes

You the developer can decide when, where, and how to set the timeout minutes and where to store it.

The practice I normally follow is to have a default timeout number of minutes stored in the system preferences. The default timeout minutes can then set by the system administrator. I also give the option of having a user timeout minutes stored in the user profile. If the user timeout minutes is a non-zero value it is used, otherwise the system preference default timeout minutes is used.

The oSysPrefs object and related wSysPrefs window can be found in the mySysAdmin library of the StartNewApp demo.

Setting the user timeout minutes and signoff time can be found in the wUsrEdit window in the mySysAdmin library of the StartNewApp demo.

Security New Password

There is a new password security option available with the StudioWorks framework. The new password option provides an added level of security to a StudioWorks application by checking to see if the user's password has not been changed for a specified number of days. If the password has expired the user is prompted to enter a new password.

There are several security problems with user passwords:

  1. If you let the user set their own password, they will often use very easy to guess passwords. (Favorite color, make of car, license plate)
  2. If you assign a random secure password users will write it down on a note and keep it within arm's length of their keyboard.
  3. If you change the password too often users will write it down on a note and keep it within arm's length of their keyboard.

A technique I use to create easy to remember secure passwords is to compose a password sentence that includes at least one number and is easy for me to memorize. For example:

My home is located at 229 Arthur Street.

My password is composed of the first character of each word, and if there are numbers, the full number. So the password for the above password sentence is:

mhila229as

Instead of memorizing the password, you memorize the password sentence. When you need to enter the passord you say the password sentence (silently) and type the first character of each word.

The StudioWorks new password security has several password sentence themes. When a new password is created the date and theme are stored in the user's security info row. When the password expires the user is prompted with the next password sentence theme. The new password window gets the user to input the information need to generate a password sentence, explains it to them, and has them enter the password correctly three times before allowing them to continue and saving the new password to the user's security info row. Hopefully by following this technique, users will commit the password sentence to memory and not need to write down the password.

Enabling New Password Security

To enable the new password security modify the $signInOKContinue method of the main library Startup_Task to send a $checkPasswordHasExpired message to the oSecurity object. If the password has expired you then send an $openNewPasswordWindow message to the oSecurity object.

The following sample code is the checkPasswordExpired method which can be found in the Startup_Task of the myAppMain library of the StartNewApp demo.

; Get the default password expiry days from the oSysPrefs object.
If oSysPrefs.$:DefaultPasswordExpireDays.$cando
   
   Do oSysPrefs.$:DefaultPasswordExpireDays Returns PasswordExpireDays
   
   If secur.$checkPasswordHasExpired.$cando
      
      ; Check if the user's current password has expired.
      Do secur.$checkPasswordHasExpired(PasswordExpireDays) Returns bPasswordHasExpired
      If bPasswordHasExpired
         
         Do secur.$openNewPasswordWindow() Returns rWin
         If isnull(rWin)
            Calculate FlagOK as kFalse
            
         End If
      End If
   End If
End If

Quit method FlagOK

The checkPasswordExpired method is called by the $signInOKContinue method as follows:

; Redirect to the startup task default methods object.
Do redirect ioStartupTaskDefaultMethods Returns FlagOK
If FlagOK
   
   Do method checkPasswordExpired Returns FlagOK
   If FlagOK
      
      Do method initializestartTimeoutTimer Returns FlagOK
      
   End If
End If

If not(FlagOK)
   Do errhndlr.$promptonceLastError()
End If
Quit method FlagOK

Note

The oSysPrefs object and related wSysPrefs window can be found in the mySysAdmin library of the StartNewApp demo.

Temporary Password

The new password security also makes it possible to give a new user a temporary password which they can can use to sign-in but are then immediately prompted for creating a new password.

The $checkPasswordHasExpired method of oSecurity checks to see if the password is prefixed with temp_. Any password that begins with temp_ (e.g. temp_xzy) is considered to be expired by the $checkPasswordHasExpired method, so the method will return kTrue (yes the password has expired) to the sender. The wSecurityNewPassword window will be opened and the user will be prompted through the steps of creating a new password which will then be save to their user profile replacing the temporary password.

If a user forgets their password, rather than having a system administrator look up and view their password, the system administrator can simply assign a new temp_ password (e.g. temp_dog) and instruct the user to sign-in with the temporary password, following which they will be prompted through creating a new password.

To assign a new password, go to System Administration > Users and edit the user record. Click the Change Password button in the edit window. You will be prompted to enter a new password. The prompt message includes instructions on setting a temporary password.