Dear lazyweb: I've some problems with Drag& Drop into the sidebar.
The goal: drop any notes document to a sidebar target and do some LS magic with them (create a new document, call some functions from a LS-lib -> add a eProductivity Action from any NotesDocument). The Idea: use a Folder and the Drag&Drop/AddToFolder events, add it to a Outline, add the outline to a form, add the form to the sidebar. The Problem: using the "QueryAddToFolder"-event (and probably "QueryDragDrop" -> couldn't get it to fire...) on a folder will not work, because the event is called on the source folder, not the target :-(
Is there any way to get to the dropped documents in the target design element? And call LS function from there?
I also thought about java sidebar views (example @ SNAPPS), but some functions are in LS and I can't replicate them in java (source is hidden) :-( And I didn't find a way to call LS from Java.
Any Ideas?
|
Drag&Drop to the sidebar: calling code at the D&D ...
|
Andre Guirard released some of his little treasures on OpenNTF: several lotusscript libraries. I especially like one if them: StringDiff.
This diff util was part of the replication conflict tool he posted some years ago. I used it to generate field diffs for a field level history:

Then I had to refactor it to get text output instead of html, now I only need to write a "TextDiffWriter" implementation (there is already a RichText and a HTML one), which should be about 20 lines of code, most of it function definitions and so on. Nice!
Thanks Andre!
|
Diff strings with the new "LotusScript Gold Collec...
|
Peter Pressnel wrote a post about class casting and polymorphism in LotusScript. Unfortunatelly, in LS you can only extend one class, not implement Interfaces (like in every other OO language). Interfaces lets you add functionality to a given class by implementing the Interface: a Person will also by "Compareable" by implementing that interface. Now every method which needs a "Compareable" argument can work with Persons.
This missing feature in LS basicly means that you have to build this functionality anew every time you need it: you need a person.compareToAnotherPerson(aPerson as Person) and also your sorted list (or whatever needs something compared) needs to know about that method. This means a lot of unnecessary programming and also it binds your code together and you will very soon get either a very big LS file or the famous "Illegal circular use" error. Not nice!
But we have help from the Adapter Pattern. Instead of adding the functionality by implementing an interface we ask for an adapter of the object which implements this interface [1].
The adapater pattern is usually also implemented via an interface: IAdaptable.getAdapter(classname) which either returns an object of that classname or nothing. As there is no Interfaces in LS this method is implemented in a basic class from which every other of my custume classes is derived: BasicObject. This class also implements "toString() (usefull as a message to logError()...) and such basic things.
The API contract for that method is very easy: either return an object of the specified class, which represents the original object or nothing. Classcasting an object now becomes:
dim managerĀ as Manager set manager = person.getAdapter("MANAGER") if manager is nothing then Error 1000, "Person is not a Manager!" [...]
instead of
dim manager as Manager if not (person isa ucase("Manager")) then error 1000, "Person is a not a manager!" set manager = person [...]
So the insert method of a sorted list, which expects a "Compareable" object (or nothing), would be something like this: list.insert(me.getAdapter("COMPAREABLE")).
This is the BaseObject.getAdapter implementation.
Class BaseObject ' [ public function toString() as String ... ]
'/** ' * Returns an Adapter of the current Object of the aked-for type or nothing ' * The basic implementation returns this object if the caller asked for the class ' * or a subclass of the class of the current object ' * Should be overwritten to do something more usefull! ' * @autor Jan Schulz ' */ Public Function getAdapter(nameOfObject As String) As BaseObject If Ucase(nameOfObject) = Typename(Me) Then Set getAdapter = Me End If If Me Isa Ucase(nameOfObject) Then Set getAdapter = Me End If End Function End Class
This LotusScript was converted to HTML using the ls2html routine, provided by Julian Robichaux at nsftools.com.
An addition would be the implementation of an AdapterManager (see the article on eclipse.org): This would even solve the problem that you need to know the adapter class in the "adaptable" object (-> problems with "Illegal circular use"...). This would need a small AdapterFactory with one methods: getAdapter(object as BaseObject, classname as String). This factory would be implemented for each used combination and then an instance of such an object added to the AdapaterManager. Now you can query the AdapaterManager for an Adapater of your object: getAdapterManager().getAdapter(object, "SomeClassName"). In this method you would ask each adapterFactory for this combination and return if you get an object back (or nothing if no factory can return such an object). Now the object wouldn't need to know that it is adaptable to a "SomeClassName".
[1] This pattern is usually used in other OO languages to keep the API of an class small and clean: if you need your person compareable, the methods of the "Compareable" Interface would be part of the API contract for the "Person" class. This is usually not desireably, especially if you add serveral interfaces.
|
The Adapter Pattern or working around that LS has ...
|
Just a short follow up to my problem from this afternoon about recompile going into an endless loop or not saving at all, when changing the order of the Use statements (Error "Duplicate PUBLIC Name"): it seems that the compiler can't cope with long names of script libs. My scriptlib was named "de.katzien.ls.MVC.Model.MonitoringCapability". Changing it to "de.katzien.ls.MVC.Model.Monitoring" fixed the problem.
|
Compile Problem fixed: Scripname was too long
|
... or to hell... Currently I'm trying to build an LS framework, which is runtime configurable. Instead of using static functions or simple class extention, I try to build an object graph, where each object adds some functionality. So something which needs to run after a Document is saved will add a observer to the document model and do the work in the right callback method. This results in small objects and class libs, but lots of use of "Use". Unfortunatelly designer seems to be unable to cope with this. Right now I'm in a situation, where designer compiles my code, if I press strg+s, but is sent into an endless loop if I use "Recompile all Lotusscript".
Other fun includes "Use" ordering: this compiles with strg+s (but sends "Recompile all" into an endless loop):
Use "class::logger" Use "de.katzien.App.Infomail" Use "de.katzien.App.InfomailDB" Use "de.katzien.ls.Email"
this not:
Use "class::logger" Use "de.katzien.App.InfomailDB" Use "de.katzien.App.Infomail" Use "de.katzien.ls.Email"
The only change is the ordering... Error: Duplicate PUBLIC Name "CHANGE_LISTENER_EVENT_BEFORE_CHANGING" in USE de.katzien.App.Infomail This name is the public Const in script lib "MonitoringCapability", which is use'd directly by .App.Infomail and indirectly by .App.InfomailDB (uses InfomailEntry which uses MonitoringCapability). setting this to private just ends up with the next error and I can't set a complete class to private if I want to use that class in some other lib...
And don't even ask about "Illegal Circular Use".
@IBM: Please improve the Compiler for LotusScript!
|
Sending Designer into endless loops with "Recompil...
|
I like to write code like this:
public function getSomething() as Something if oSomething is nothing then set oSomething = new Something() end if set getsomething = oSomething end function
How do this with dynamic arrays? I was very surprised when this threw an error:
dim arrSomethings() as Something if isArray(arrSomethings) then print ubound(arrSomething) ' unitialized array error when calling the ubound function
You have to call a redim arrSomething(0 to 0) bevor you can do anything with that array. I tested it with every isXYZ function I could find in the designer help, but i couldn't find a way to detect that...Somehow I agree with Tommy Valand: LS Arrays are strange. Give me a Java collections like API and I'm happy :-) BTW: datatype (arrSomething) gives "8738", even after the redim, but the designer help says that "8704" is a Dynamic array. Bug?
|
How to test if an array is initialized?
|
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:
 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 :-)
|
Notes becomes stranger every day: binding to onHel...
|
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
|