Tips_tutorials   >   Studio103   >   Error Handler

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.

Create Error Handler Object

In this section we'll create an error handler object, oErrorHandler, with some basic methods.

  1. Create a new object class in the Contacts library.
  2. Name the object, oErrorHandler.
  3. Double-click the oErrorHandler class to edit its methods.
  4. Select the $construct method and enter the following code.

    ; 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

  5. Right-click the Class methods and select Insert New Method.
  6. Name the method, $logError.
  7. Add the following parameters to the $logError method

    Variable - prcmethod (This is a reference to the method which logs the error.)
    Type - Item reference

    Variable - pMssg
    Type - Character
    Subtype - 1000000
  8. Enter the following code in the $logError method.

    ; 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

  9. Right-click the Class methods and select Insert New Method.
  10. Name the method $promptonceLastError.
  11. Enter the following code in the $promptonceLastError method.

    ; 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

Instantiate the Error Handler

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.

  1. Double-click the Startup_Task class in the Contacts library.
  2. Add a variable to the Task tab of the variables section of the method editor as follows:

    Variable - errhndlr
    Type - Object
    Subtype - oErrorHandler
  3. Close the method editor window.
  4. Close and reopen the Startup_Task to instantiate the oErrorHandler object class with the errhndlr task variable.

Test the Error Handler

We'll test the error handler by logging an error message to it, and then asking it to prompt the last error.

  1. Enter the following code in the Programmer Test Method.

    ; 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()

  2. Select Contacts menu > Programmer Test Method and step through the test code.

    All going well the error handler will prompt the error message on the first request and ignore the second request.

Modify the Non-Visual tBase Methods

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.

  1. Double-click the tBase table class to get at the class method.
  2. Select the $:PrimaryKeyColName method.
  3. Modify the method code as follows.

    ; 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

  4. Select the $dowork method.
  5. Modify the last part of the method code as follows.

    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

  6. Select the $getAllRecords method.
  7. Modify the method code as follows.

    ; 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

  8. Select the $setPrimaryKey method.
  9. Modify the method code as follows.

    ; 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

Modify the Visual Methods

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.

Note

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 Find and Replace window.

  1. Ctrl/Cmnd+F to open the Omnis Studio Find and Replace window.
  2. Enter .$getAllRecords in the Find entry field. By including the . period character the find will be restricted to the Do ListName.$getAllRecords() method lines.
  3. Select the Class tab in the Find and Replace window.
  4. Select the Contacts library in the left list.
  5. Select a class in the right list and then press Ctrl/Cmnd+A to select all the class in the Contacts library.
  6. Click the Find All button. Methods which we are going to need to modify will appear in the Find and Replace list.
  7. Double-click the first line in the list. This takes you to the wCountryList.$construct method.
  8. Modify the end of the method so that it sends a $promptonceLastError message to the error handler if the flag is false.

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

  9. Close the method editor window to return to the Find and Replace window.
  10. The second line in the list is the wStateprovList.buildLists method. Because this is a private method we do not need to add $promptonceLastError to the end of the method.
  11. The fourth line in the list is the wTowncityList.buildLists method. Because this is a private method we do not need to add $promptonceLastError to the end of the method.
  12. The last line in the list is the wTowncityList.StateProvName.event_evAfter method. Because this is a private method we do not need to add $promptonceLastError to the end of the method, however we do need to modify the public $event method of the wTowncityList.StateProvName field which calls the private event_evAfter method.
  13. Select the $event method of the StateProvName field.
  14. Modify the On evAfter code in the $event method as follows:

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



    The $event method is not called by another method, so there is no need to include Quit method FlagOK at the end of an $event method.
  15. Close the method editor window to return to the Find and Replace window.

Repeat the above process for all the locations where $dowork messages are being sent to the tBase table class.

  1. Ctrl/Cmnd+F to open the Omnis Studio Find and Replace window.
  2. Enter .$dowork in the Find entry field. By including the . period character the find will be restricted to the Do ListName.$dowork() method lines.
  3. Select the Class tab in the Find and Replace window.
  4. Select the Contacts library in the left list.
  5. Select a class in the right list and then press Ctrl/Cmnd+A to select all the class in the Contacts library.
  6. Click the Find All button. Methods which we are going to need to modify will appear in the Find and Replace list.
  7. For each of the listed public visual class methods modify the code to test for flag false and send a $promptonceLastError message to the error handler if the flag is false.
  8. In each of the listed private visual class methods modify the $event method which calls the private method to test for flag false and send a $promptonceLastError message to the error handler if the flag is false.