Tips_tutorials   >   Studio103   >   Error Handler
I don't think it is possible to write non-visual code without an error handler object.
In the Studio 101 and 102 tutorial we created table classes. The table classes are non-visual objects, however whenever we hit and error in the table class methods we prompted the user with an OK message. Really what choice did we have? There was no process for passing an error back to the visual window class method which sent the $getAllRecords or $dowork message to the table class.
How can we pass an error message back to a visual class method from a non-visual class method?
The technique which I use is to create an error handler object. When a non-visual method hits an error, it logs the error with the error handler object, and then returns false (or null if a return value is expected) to the sender. If the sender is another non-visual object is also returns false (or null if a return value is expected) to its sender. Eventually the visual class method which started the thread is going to get a return value which indicates an error occurred. The visual class method then sends a message to the error handler asking it to prompt the user with the last error that was logged.
The error handler hold the logged error messages in a list and could also write the error to a log file, or a table in the database. For this tutorial we are simply going to store the errors in a list. You can add the code to write the error to a log file or the database on your own.
Once concern with this style of error handling is how to avoid prompting the user multiple times with the same error message. If one visual class method, calls another visual class method, which in turn calls a non-visual class method, and an error is logged by the non-visual class method, both visual class methods will (and should) attempt to prompt the user with the error message. This problem is solved by adding a prompted boolean column to the errors list. The first time the error is prompted, prompted is set to kTrue. Subsequent requests to prompt the current error are ignored by the error handler.In this section we'll create an error handler object, oErrorHandler, with some basic methods.
; Define the errors list.
Do List.$cols.$add('libname',kCharacter,kSimplechar,100)
Do List.$cols.$add('classname',kCharacter,kSimplechar,100)
Do List.$cols.$add('methodname',kCharacter,kSimplechar,100)
Do List.$cols.$add('prompted',kBoolean)
Do List.$cols.$add('datetime',kDate,kDatetime)
Do List.$cols.$add('errormssg',kCharacter,kSimplechar,1000000)
Calculate iErrorsList as List
Quit method kTrue
; Add the error to the errors list.
Do iErrorsList.$add(prcmethod.$lib().$name,prcmethod.$class().$name,prcmethod().$name,kFalse,#D,pMssg)
; Set the last line in the list to be the current line.
Do iErrorsList.$line.$assign($ref.$linecount)
Quit method kTrue
; Only open the prompt if this is the first time the error is being prompted.
If iErrorsList.prompted=kFalse
; Set the prompted flag to true to avoid multiple prompts of the same error message.
Calculate iErrorsList.prompted as kTrue
; Open an OK prompt message.
Calculate Mssg as iErrorsList.errormssg
Calculate Method as con(iErrorsList.libname,'/',iErrorsList.classname,'/',iErrorsList.methodname)
OK message Error (Icon) {[Mssg]////Logged by://[Method]}
End If
We need to instantiate the error handler object with task wide scope so that all the classes and methods in the Contacts application can send messages to the error handler object. The Contacts library Startup_Task task variables have task wide scope.
We'll test the error handler by logging an error message to it, and then asking it to prompt the last error.
; Log an error.
Do errhndlr.$logError($cmethod,'Test error message')
; Prompt the last error.
Do errhndlr.$promptonceLastError()
; Attempt to prompt the same error again.
Do errhndlr.$promptonceLastError()
Now we need to remove our OK messages in the non-visual tBase table class and replace them with code that logs the errors with the error handler.
; 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
Calculate Mssg as con("Unable to find the primary key column in the ",$cinst.$sqlclassname," SQL class.")
Do errhndlr.$logError($cmethod,Mssg)
Calculate ColName as #NULL
End If
Quit method ColName
If FlagOK
If pos(',',$cinst.$servertablenames)
Calculate Mssg as con("Unable to execute $dowork on a query class that has columns from more than one table.")
Do errhndlr.$logError($cmethod,Mssg)
Calculate FlagOK as kFalse
Else
; Do the built-in default $dowork method.
Do default Returns FlagOK
If not(FlagOK)
Calculate Mssg as "Flag false after running the default $dowork method."
Do errhndlr.$logError($cmethod,Mssg)
End If
End If
End If
Quit method FlagOK
; Prepare the ORDER BY text.
If len(pOrderBySQL)
Calculate OrderBy as pOrderBySQL
Else
Calculate OrderBy as $cinst.$:DefaultOrderBy
End If
; Prepare the SQL text to exclude the empty zero(0) primary key record.
Calculate ColName as $cinst.$:PrimaryKeyColName
If len(ColName)=0
Calculate FlagOK as kFalse
Else
If pos("WHERE ",$cinst.$extraquerytext)
Calculate SQLText as con("AND ",ColName," <> 0")
Else
Calculate SQLText as con("WHERE ",ColName," <> 0")
End If
Calculate SQLText as con(SQLText,' ',OrderBy)
; Select all the records in the table.
Do $cinst.$select(SQLText) Returns FlagOK
If not(FlagOK)
Calculate Mssg as con("Flag false after $cinst.$select(",SQLText,") for the $sqlclassname ",$cinst.$sqlclassname,".")
Do $ctask.errhndlr.$logError($cmethod,Mssg)
Else
; Fetch all the records in the table.
Do $cinst.$fetch(kFetchAll) Returns FetchStatus
If not(FetchStatus)
Calculate FlagOK as kFalse
Calculate Mssg as con("Flag false after $cinst.$fetch(kFetchAll) for the $sqlclassname ",$cinst.$sqlclassname,".")
Do $ctask.errhndlr.$logError($cmethod,Mssg)
Else
; Set the current line to the first line.
Do $cinst.$line.$assign(1)
End If
End If
End If
Quit method FlagOK
; Check if the primary key column is null and not zero.
Calculate ColName as $cinst.$:PrimaryKeyColName
If len(ColName)=0
Calculate FlagOK as kFalse
Else
If isnull($cinst.[ColName])|$cinst.[ColName]=0
Calculate FlagOK as kTrue
Else
; Get a new statement object from this table instance's session object.
Do $cinst.$sessionobject().$newstatement() Returns StmntObj
; Select and fetch the maximum primary key column value from the table's records.
Calculate TableName as $cinst.$:BaseTableName
If len(TableName)=0
Calculate FlagOK as kFalse
Else
Calculate SQLText as con("SELECT MAX(",ColName,") FROM ",TableName)
Do StmntObj.$execdirect(SQLText) Returns FlagOK
If not(FlagOK)
Calculate Mssg as con("SQL error when issuing the following SQL Text: ",SQLText," for the $sqlclassname ",$cinst.$sqlclassname,".")
Do $ctask.errhndlr.$logError($cmethod,Mssg)
Else
Do StmntObj.$fetchinto(MaxPKey) Returns FetchStatus
If FetchStatus=kFetchError
Calculate FlagOK as kFalse
Calculate Mssg as con("SQL error when fetching the max pkey after issuing the following SQL Text: ",SQLText," for the $sqlclassname ",$cinst.$sqlclassname,".")
Do $ctask.errhndlr.$logError($cmethod,Mssg)
Else
; Set the primary key column to the maximum primary key value plus one.
Calculate $cinst.[ColName] as MaxPKey+1
End If
End If
End If
End If
End If
Quit method FlagOK
We also have to find any visual class methods which send message to tBase and add code that will prompt the user if an error is returned to the visual class method.
There is something important to note here! One of the guidelines which I follow for error handling is that only public methods of visual classes prompt the user with error messages. Private methods log errors but simply return the flag to the sender. This reduces the number of methods which need to check for flag false and send a $promptonceLastError.
The easiest way to find all the locations where messages are being sent to the tBase table class is to use the Omnis Studio window.
If not(FlagOK)
Do errhndlr.$promptonceLastError()
End If
Quit method FlagOK
Do method event_evAfter Returns FlagOK
If not(FlagOK)
Do errhndlr.$promptonceLastError()
End If
Repeat the above process for all the locations where $dowork messages are being sent to the tBase table class.