Blogs

  • Browse Blogs
  • My Blog
  • My Updates

Tags Help

  • View as cloud  | list

Similar Blogs

photo

Patrick Picar...

62 Entries |  Patrick Picard
Updated 
RatingsRatings 2     CommentsComments 112
photo

Lotus Nut

111 Entries |  Chris Whisonant
Updated 
RatingsRatings 23     CommentsComments 157
photo

Uh Clem's Adm...

54 Entries |  Chris Mobley
Updated 
RatingsRatings 8     CommentsComments 55
photo

Life is too s...

33 Entries |  Barbara Skedel
Updated 
RatingsRatings 3     CommentsComments 56
photo

Yellow is the...

72 Entries |  Tim Tripcony
Updated 
RatingsRatings 2     CommentsComments 34

Jan Schulz

Blog Authors:  Jan Schulz  

Main  | Next

Notes becomes stranger every day: binding to onHel...

Jan Schulz  |     |  Tags:  event lotusscript binding  |  Comments (3)
Binding to an onHelp event via "On Event Onhelp From fUIDoc Call delegate_onHelp" is only working, if you have any code/Comment in the onHelp event on the Form: just leaving it with no comment in it in the client lotuscript part will not trigger delegate_onHelp. Putting a comment into the event on the form works:
image
I found that only because I wanted to test if this code element is triggered at all and then commented out my code. And behold: it worked from there...

Can someone explain this?

I think I saw a different case, where putting a comment into some otherwise empty code block made a difference, but I can't remember anymore. IMO this is just weird...

Now I only need the LS version of OpenHelpDocument :-)

NotesUIWorkspace.openHelpDocument(...)?

Jan Schulz  |     |  Comments (0)
I implemented a context sensitive help in one of my applications, but now I want to migrate this code to an event based handler which uses the 'onHelp' NotesUIDocument event. Unfortunatelly I can't find a replacement for this code in LotusScript: @Command([OpenHelpDocument]; ""; "LHelp";"FORM-"+ Form))

Is there no other possibility than using evaluate?

Extending OpenLog

Jan Schulz  |     |  Comments (0)
I use OpenLog for my error handling and mostly it just works. But there are a few cases, when I wanted to have something more:
  • show the error messages to the user and get some information what the user did
  • Sending the logentry via mail to the central OpenLog DB in case the user is offline
While I was at it, I also added "Handlers": you can pass in a handler and openlog will ask each handler if the error can be handled (ignored, resolved, etc) by the handler and if so, will return. I got the Idea for error handlers from the "Lotusphere 2008 - AD311 - Object Oriented Programming with Lotusscript – Take It To the Next Level" presentation/ example DB.

Now you can write something like this:
handleerror:
If handleError() Then Resume Next
There is also a new "handleOrRethrowError() method, which corresponds to "addToStackTrace()" but can handle an error.

This handler mechanism are a great way to let someone else extend your code without changing your code. Everything what is needed are some entry points, where you can add you own "HandlerObjects" and of course your code must delegate the handling to it.

This is the new handleError() method I came up with:
Public Function handleError() As Boolean
'** logs whatever error happens to be on the stack (if any)
On Error Goto processError

Dim session As New NotesSession

handleError = False

' First try to handle the error
' Loop over some handler List
' If any can and has handled this error, just return true

Forall handler In errorHandlers
If handler.isResponsibleFor(Err) Then
handleError = handler.handleError()
End If
If handleError = True Then
Exit Function
End If
End Forall

'if not:
' Create a log item and a document
' Ask the user for input with a dialog

Call setupLogItem("","",Nothing,Getthreadinfo(10)) 'refactored to get rid of code duplication
Dim db As NotesDatabase

If session.IsOnServer Then
' I can garantee that there is a OpenLog DB on each server
Set db = GetLogDatabase
globalLogSuccess = globalLogItem.WriteToLog(db)
Else
' local means in most cases not online -> mail it and ask for input
Set db = New NotesDatabase("", "")
Call db.OpenMail
Dim dummyDoc As NotesDocument
Set dummyDoc = globalLogItem.CreateLogDoc(db)
' Just shows a dialog, which displays some fields from the LogItem
If askForAdditionalInformation(dummydoc) Then
globalLogItem.message = globalLogItem.message + Chr(13)+Chr(10)+ dummydoc.GetItemValue("ErrorMessage")(0)
End If
Call dummydoc.Remove(True)
' creates a doc and uses doc.send(...) to mail it to the OpenLog Mail-In DB
globalLogSuccess = globalLogItem.sendViaMail(db)
End If

Call ClearStackTrace()
If Not globalLogSuccess Then
Error 1001, "Log was not saved or sent"
End If

Exit Function
processError:
DebugPrint(StandardErrorMessage(Err, Erl, Error$, Getthreadinfo(1)))
Resume Next

End Function


I'm not yet satisfied: there is still some possibility to refactore code (there is much code duplication in the static methods) and I think most of the setup code (setupLogItem(...) and also for the stackTrace) could go into the classes. Also the dialog could go into a ErrorHandler: just let isResponsible(...) return true if not session.isOnServer and then return false from the handleError(). But this would need some priority in the handlers (no need to ask the user if we can handle it internaly) and a way to indicate if we are in the rethrow phase or in the final handleError()... Let's see....


Another thing is adding Context to the error (also an idea I got from the above example DB...). LogErrorEx(...) already lets you pass in some information, but it would be great if this could be more than a string: something like an object store, some variable values,... No idea yet how to implment that without registering every variable/object in a store :-/ Any Ideas?


assertThat(me, is_(aBlogPost()).and_(is_(interesti...

Jan Schulz  |     |  Tags:  testing validation hamcrest  |  Comments (1)
A few days ago I found Hamcrest on the web. It's a framework, which lets you write validation rules in almost plain english:
Dim value as Variant
value = "Hallo"
Call assertThat(value, is_( not_( equalTo("Hallo") ) ).and_(is_(ofType("String")))

Resulting in output like this:

Assertion failure: expected: is not equal to Hallo and is of type STRING; got: (STRING) Hallo

(bold is from the Matcher (second argument to assertThat), rest from the Error, which the assertThat method throws)

The Implementation is quite easy: you have a IMatcher abstract Class with several concrete matcher implementation and a static method, which returns a object of that matcher. The IsMatcher (just to have proper english sentences :-) and the static method looks like this:

Public Function is_(matcher As IMatcher) As IMatcher
Set is_ = New IsMatcher(matcher)
End Function

Class IsMatcher As IMatcher
Private fMatcher As IMatcher

Sub new(matcher As IMatcher)
Set fMatcher = matcher
End Sub

Public Function matchesSafely( value As Variant) As Boolean
matchesSafely = fMatcher.matches(value)
End Function

Public Function describeMeTo(description As Description)
description.appendText("is")
Call fMatcher.describeTo(description)
End Function
End Class

The testThat (just returns a boolean, assertThat uses that to throw the error) is just:

Public Function testThat(value As Variant, matcher As IMatcher) As Boolean
testThat = matcher.matches(value)
End Functio

IMatcher.matches(variant) is a wrapper to get the IMatcher.and_(IMatcher) and IMatcher.Or_(IMatcher) methods working. In the end it returns the logical result of all included Matchers.

The IMatcher.decribeMeTo(Description) is again the internal method (describeTo(...) is the wrapper to get AND and OR working) used to get the plain english description from the matcher composite (the bold Text above).

Dim oDescription As New Description
Call matcher.describeTo(oDescription)
print oDescription.toString()


Implemented Matchers up to now: is, not, equalTo, ofType, instanceOf, like. Feel free to add some matchers or leave a comment for other ideas :-)

Here is the script lib as it is up to now (BleedYellow seems to not allow any Notes/Domino related content: no *.lss, no *.nsf):

Edited, as I can upload now: LS-Hamcrest.lss

Save as Draft not working?

Jan Schulz  |     |  Comments (1)
I get an error notice when I try to save a blogpost as draft, loosing all my data (back does not work in Firefox :-( ). Anybody else with that?

Ok, the blog post about
Call assertThat("Hallo", is_( not_( equalTo("Hallo") ) ).and_(is_(ofType("String"))))

Assertion failure: expected: is not equal to Hallo and is of type STRING; got: (STRING) Hallo
 will have to wait until tomorrow :-)

testable code = better code?

Jan Schulz  |     |  Tags:  unittest design pattern oop  |  Comments (0)
After my entry about event delegation, I went out and refreshed my memory about Design Pattern (The GOF book was the first - and up to now my only - book about programming. Hey, It's just a hobby for me :-). I also found the Google Testing Blog. Especially one of the entries was very interesting: writing testable code. Here are my Top7 from that entry:
  1. Don't mix object graph construction with application logic
  2. Ask for things, Don't look for things (aka Dependency Injection / Law of Demeter)
  3. Don't do work in constructor
  4. Favor composition over inheritance
  5. Favor polymorphism over conditionals
  6. Don't mix Service Objects with Value Objects
  7. Mixing of Concerns
So even if I don't plan on writing unittests (lsUnit and lsMock anyone?) I found the recomendations quite usefull and will try to follow them in my programming.

Fun with events

Jan Schulz  |     |  Tags:  events lotusscript  |  Comments (0)
After seeing how much fun everybody else had with events, I tried to get my own version together.

I tried my luck with delegating the actual event handling to EventHandlers, which do the actual job. So the called QueryOpen Method looks like this:

Sub delegate_Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Queryopen(Source , Mode, Isnewdoc , Continue ) If Not Continue Then Exit Sub End Forall End Sub


The actuall handlers extend a basic EventHandler (which does nothing in each object method) with the required functionality. The rest of the design is modeled after the MVC Pattern as described in this presentation/example DB.

Fun: binding object methods to an event and not having the object around at "call time" (like when you forget to declare your object in Declaration and just do a <code>dim controller as New BaseDocumentController(source)</code> on the PostOpen-Event of the Form) will do funny things in the debugger: after the QueryClose Event the client goes into a endless loop. Without debugger, just nothing happens.

This is the complete code for Tims example:
Private Const ERR_ABSTRACT_INSTANTIATION = 1000 Private Const MSG_ABSTRACT_INSTANTIATION = | Attempt to instantiate as an object instance the abstract class | Public Class BaseDocumentController %REM The Controller Part of MVC Binds to a UIDocument, delegates all events to the EventHandlers %END REM Private fUIDoc As NotesUIDocument Private fModel As BaseDocumentModel Private fIsInitialized As Boolean Private listEventHandlers List As AbstractDocumentEventHandler Sub new(uidoc As NotesUIDocument) If Not uidoc Is Nothing Then Call initializeWithUIDoc(uidoc) End If End Sub Public Function getUIDoc() As NotesUIDocument Set getUIDoc = fUIDoc End Function Public Function initializeWithUIDoc(uidoc As NotesUIDocument) If uiDoc Is Nothing Then Error 1111, "Need a UI Document" End If Set fUIDoc = uidoc Set fModel = New BaseDocumentModel( fUIDoc.Document) fIsInitialized = True Call bindEvents() Call setupEventHandler() End Function %REM adds the default EventHandler (Validation) the to eventhandler List Can be overwritten to extend the EventHandler List, needs to call BaseDocumentController..setupEventHandler() to get the basic EventHandler setup or you need to add validation yourself %END REM Function setupEventHandler() Dim evHandler As New ValidationHandler(fModel) Call Me.addEventHandler( "validation", evHandler) Dim evHandler2 As New AuditTrailHandler(fModel) Call Me.addEventHandler( "AuditTrail", evHandler2) End Function Public Function addEventHandler(evName As String, evHandler As AbstractDocumentEventHandler) Set listEventHandlers(evName) = evHandler End Function Private Function bindEvents() On Event Queryopen From fUIDoc Call delegate_Queryopen On Event Postopen From fUIDoc Call delegate_Postopen On Event Querymodechange From fUIDoc Call delegate_Querymodechange On Event Postmodechange From fUIDoc Call delegate_Postmodechange On Event Queryrecalc From fUIDoc Call delegate_Queryrecalc On Event Postrecalc From fUIDoc Call delegate_Postrecalc On Event Querysave From fUIDoc Call delegate_Querysave On Event Postsave From fUIDoc Call delegate_Postsave On Event Querysend From fUIDoc Call delegate_Querysend On Event Postsend From fUIDoc Call delegate_Postsend On Event Queryclose From fUIDoc Call delegate_Queryclose End Function Sub delegate_Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Queryopen(Source , Mode, Isnewdoc , Continue ) If Not Continue Then Exit Sub End Forall End Sub Sub delegate_Postopen(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.PostOpen(Source) End Forall End Sub Private Sub delegate_Querymodechange(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Querymodechange(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postmodechange(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postmodechange(Source) End Forall End Sub Private Sub delegate_Queryrecalc(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Queryrecalc(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postrecalc(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postrecalc(Source) End Forall End Sub Private Sub delegate_Querysave(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Querysave(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postsave(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postsave(Source) End Forall End Sub Private Sub delegate_Querysend(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Querysend(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postsend(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postsend(Source) End Forall End Sub Private Sub delegate_Queryclose(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Print "Calling eventhandler for QueryClose" Call evHandler.Queryclose(Source, Continue) If Not Continue Then Exit Sub End Forall Print "End QueryClose" End Sub End Class Class BaseDocumentModel %REM The model part of MVC Binds to the backend document and does validation (not implmented properly) and other stuff with the data %END REM Private fDoc As NotesDocument Private fIsInitialized As Boolean Sub new(doc As NotesDocument) If Not doc Is Nothing Then Call initializeWithDocument(doc) End If End Sub Function initializeWithDocument(doc As NotesDocument) If doc Is Nothing Then Error 1111, "Model needs a Document" End If Set fDoc = doc fIsInitialized = True End Function Public Function validate() As ValidationResult If Not fIsInitialized Then Error 11111, "Uninitialised Model!" ' Checks the model: something like a array of validation rules and the ValidationResult is passed to each rule to collect the result ' Just for show: Set validate = New ValidationResult() End Function End Class Class validationResult %REM Collects the result of a validation %END REM Sub new () ' Nothing to do End Sub Public Function addError(fieldname As String, Message As String) ' unimplemented End Function Public Function hasErrors() As Boolean ' unimplemented hasErrors = False End Function Public Function getFirstInvalidField() As String ' unimplemented getFirstInvalidField = "Subject" End Function Public Function getFormattedErrorMessage() As String 'unimplemented getFormattedErrorMessage = "Some Field: invalid" End Function End Class Public Class AbstractDocumentEventHandler %REM Handels the delegated events from the Controller Overwrite specific methods to let them do something %END REM Private fModel As BaseDocumentModel Public Sub New (model As BaseDocumentModel) Dim classname As String Let className = Typename(Me) If (className = "ABSTRACTDOCUMENTEVENTHANDLER") Then Error ERR_ABSTRACT_INSTANTIATION, MSG_ABSTRACT_INSTANTIATION & classname Exit Sub End If Set Me.fModel = model End Sub Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant) ' To be overwritten End Sub Sub Postopen(Source As Notesuidocument) ' To be overwritten End Sub Public Sub Querymodechange(Source As Notesuidocument, Continue As Variant) ' To be overwritten End Sub Public Sub Postmodechange(Source As Notesuidocument) ' To be overwritten End Sub Public Sub Queryrecalc(Source As Notesuidocument, Continue As Variant) ' To be overwritten End Sub Public Sub Postrecalc(Source As Notesuidocument) ' To be overwritten End Sub Public Sub Querysave(Source As Notesuidocument, Continue As Variant) ' To be overwritten End Sub Public Sub Postsave(Source As Notesuidocument) ' To be overwritten End Sub Public Sub Querysend(Source As Notesuidocument, Continue As Variant) ' To be overwritten End Sub Public Sub Postsend(Source As Notesuidocument) ' To be overwritten End Sub Public Sub Queryclose(Source As Notesuidocument, Continue As Variant) ' To be overwritten End Sub End Class Public Class ValidationHandler As AbstractDocumentEventHandler %REM Handels the validation of the model in the QuerySave Event, - stops saving when the model is not valid - jumps to the first invalid field, as returned by the ValidationResult %END REM Sub new(model As BaseDocumentModel), AbstractDocumentEventHandler(model) ' nothing to do, just pass the model on to parent class... End Sub Public Sub Querysave(Source As Notesuidocument, Continue As Variant) Print "doing validation" Dim oResult As ValidationResult Set oResult = fModel.validate() If oResult.hasErrors() Then Continue = Continue And False Msgbox oResult.getFormattedErrorMessage(), 0 , "Validation error" Call source.GoToField(oResult.getFirstInvalidField()) End If End Sub End Class Public Class AuditTrailHandler As AbstractDocumentEventHandler %REM Adds a comment to the backend document in the QuerySave Event, - This should better be done in an event in the Model: Controller -> QuerySave -> Everything validates, etc -> model.QuerySave(....) -> Calls a listener, which does the Trail or the model does it itself -> even when you use the model without a controller, it gets a Trail %END REM Sub new(model As BaseDocumentModel), AbstractDocumentEventHandler(model) ' nothing to do, just pass the model on to parent class... End Sub Private Sub Querysave(Source As Notesuidocument, Continue As Variant) Print "Doing Audit" Dim sess As New NotesSession Dim newEdit As String Dim editType As String If (Source.IsNewDoc) Then Let editType = "created" Else Let editType = "modified" End If Let newEdit = Cstr(Now) & " - Document2 " & editType & " by " & sess.commonUserName Call Source.Document.ReplaceItemValue("AuditTrail", Fulltrim(Split(newEdit & Chr(13) & Join(Source.Document.GetItemValue("AuditTrail"), Chr(13)), Chr(13)))) End Sub End Class
This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

Main  | Next
Skip to main content link. Accesskey S
IBM Lotus Connections Help Tools About

Tags

A tag is a keyword that is used to categorize an entry. To view the entries with a particular tag, click a tag name or enter a tag in the box.
The tag cloud indicates the frequency of tag use. Popular tags appear darkest. The slider control adjusts how many tags are displayed in the tag cloud.