+Tags Hilfe zu Tags anfordern?

  • Anzeigen als -Wolke-  | -Liste-

+ Ähnliche Blogs

photo

Lotus Nut

124 Einträge |  Chris Whisonant
Aktualisiert 
BewertungenBewertungen 29     KommentareKommentare 174
photo

TexasSwede

127 Einträge |  Karl-Henry Martinsso...
Aktualisiert 
BewertungenBewertungen 2     KommentareKommentare 102
photo

Yellow is the...

72 Einträge |  Tim Tripcony
Aktualisiert 
BewertungenBewertungen 2     KommentareKommentare 34
photo

Patrick Picar...

69 Einträge |  Patrick Picard
Aktualisiert 
BewertungenBewertungen 2     KommentareKommentare 124
photo

Urs Meli

48 Einträge |  Urs Meli
Aktualisiert 
Keine BewertungenBewertungen 0     KommentareKommentare 56

+ Blogautoren  

Alle Einträge mit dem Tag oop

1 - 15 von 27
  • Seite   1
  • 2

OOP: Part 14 Subforms And Custom Controls

Peter Presnell |   | Tags:  subforms oop ssjs customcontrols lotusscript | Kommentare (1)  |  Besuche (639)
 IBM's implementation of OOP within Lotus Notes is far from perfect.  In fact, it would be very easy to assume that IBM do not see OOP as being a target for Lotus Notes developers.  One of the areas in which an Object Oriented Programmer must overcome some additional obstacles is when it becomes necessary to add business logic to either a subform (LotusScript) or a custom control (SSJS).  While it is possible to expose a Subform on its own via a DialogBox it is more typical that a subform or custom control is exposed as a component within a Form or XPage.  In Part 3 of this series I discussed ways to wire a class to a form or an XPage.  But what about Subforms and Custom Controls?  These components do not provide direct access to the variables defined in their parent.  Nor are the events fired by these components accessible from the parent object.
.
Subforms
Does the subform get used to display data from one core object (document type)?  If the answer is yes then you can create a new variable global to the subform that instantiates another instance of the class defined on the parent form.  Then you can wire actions/events to that variable.  Theoretically this is far from perfect.  From a practical perspective this usually provides most (if not all) of what is needed.  Note: Depending on the version of Lotus Notes you are using you may encounter compile issues with this approach.  On occasions I have been forced to make the variable declaration local to each action just to get the LS code to compile.
.
If your subform is more generic and could potentially be included on many forms representing different document types then it starts to get more complicated.  In this scenario you may need a base class that defines the business logic for this subform (and any other subforms that serve the same group of document types).  Then you can instantiate an instance of that class on your subform(s).  The parent form will now (most likely) be using a superclass that extends the base class with additional logic specific to that document type.  In a way this actually improves the design of your application.  The fact that a subform can be shared by multiple objects is really an indication that there is a group of Fields (properties) and Actions (methods) that are common to a group of objects (Forms).

Custom Controls
A well designed XPage application is almost always going to include the use of Custom Controls.  So this scenario is potentially a constant issue for OOP XPage development.  The good news is that it is possible to pass an object to a custom control via property.  To do this you need to define a property of the type java.lang.Object.  This is available as "Object" under the list of Complex Types that can be selected when specifying the "Type" of a property.  So if your XPage has a Controller variables and several Model variables (e.g. two documents on an XPage or One Document and a View) you can pass these variables to each Custom Control by specifying these variable as the values for one or more properties.  To access a function in my controller I can make a SSJS call to compositeData.controller.functionName()  (assuming the property name assigned is "controller").  This offers a lot of advantages over the Subform approach as we are sharing the same variable (passed by reference) allowing us to communicate (and signal) between the parent XPage and the underlying custom controls.  In later blogs I hope to provide more details about how this capability can be exploited to do things I can only dream about using Forms/Subforms. 

OOP Series
Keine BewertungenBewertungen 0

OOP: Part 13 Test Driven Development

Peter Presnell |   | Tags:  development testing oop | Kommentare (0)  |  Besuche (649)
Test Driven Development has been made popular by agile programming methodologies such as Extreme Programming and SCRUM.  TDD involves starting the development cycle by first building automated test scripts that define the requirements of the code.  The developer then develops new code, or refactors existing code that is designed to pass the tests.  
.
Process
  1. Translate requirements into Classes/Properties/Methods:  Each requirement can be translated into a series of one or more properties/methods that must be performed against core objects (databases, views, documents).  Assign a name to each property/method and create a shell for that property/method in your Script library.
  2. Translate requirements into use cases: Reinterpret the requirements a second time by considering the scenarios each property/method should handle.  Create test scripts that test each of these scenarios.
  3. Create test data: Where needed, create test data (documents) that will facilitate the testing of scenarios.  Forms used during prototyping usually act as an excellent way to create test data.
  4. Code: Add the code necessary to implement each property/method.
  5. Test Code: When coding is completed execute the appropriate unit tests to see if all the tests pass.  If they do, then the code is complete.  If the test fails then make changes to the code to address the issues and retest.
.
Advantages
Test Driven Development brings a lot to the development cycle:-
  1. Developing tests first forces a developer to think through the details of requirements before they start writing code.  The developer then has a clearer understanding of the requirements which usually leads to a higher quality of code being developed in the first round of development.
  2. In traditional development methodologies it is often only during testing (Unit Testing, Integration Testing, System testing etc.) that scenarios are discovered that were neither outlined in the requirements and/or written into the code.   Making changes at this late stage is expensive often leading to revised requirements, additional coding, new tests, and another round of testing.  TDD increases the chances that the need for clarifications will be detected early on in the cycle before development begins.
  3. If you are working with a separate QA or UAT team it is possible to review the tests scripts with those teams to ensure that everyone is in agreement about the scenarios that must be tested.  A consensus can be reached before the code is developed rather than responding to a defect raised in QA/UAT.  Where necessary, requirements can be clarified reducing the rework for both coding and the development/execution of test scripts.
  4. Code developed using TDD has a greater chance of following the well known KISS principal (Keep It Simple Stupid) leading to simpler designs.  The developer needs to only develop their code to the point where it passes the tests.  This tends to reduce the time/effort involved in writing code that is simply not needed by the application.
  5. TDD adds a degree of certainty to the development cycle.  In traditional development lifecycles it can be difficult to measure if development is on track/budget.  Writing code is one thing.  Writing code that works is another.  Until the first round of testing is done it is often difficult to determine how much time may need to be spent to address issues found in testing.  With TDD it is possible to produce burn-rate charts that track the total number of tests and the number of tests passed.  These can be extrapolated to determine if a project is ahead/behind schedule.
Note: If the above approach is taken with Class Notes applications (LotusScript), then the task of migrating an application to XPages becomes much simpler.  The test scenarios and data should remain largely unchanged.  The test scripts will need to be migrated from LotusScript to SSJS/JavaScript but the changes should largely be syntax changes
.
In Part 14 we will return to our Notes Application Catalog and demosntrate how we can apply Test Driven Development.


Keine BewertungenBewertungen 0

OOP: Part 12 Adding "XPage" Validators To A Notes Form

Peter Presnell |   | Tags:  oop .dominoframework validation validators | Kommentare (0)  |  Besuche (688)
 One aspect of OOP I have often struggled with when doing Notes development is the validation process.  In theory the validation of an object forms part of of the Business Logic Layer (BLL).  i.e. The logic for validating a document's content should always be the same if you if you a displaying a form on a Notes client or a Web browser.  It should be the same if it is a Domino Web Form or an XPage.  It should be the same if I edit/add records via a Notes Form or via Inview Edit.  It should be the same if it is displayed on a wide screen monitor or a small PDA screen.  It should also be the same if you are running an agent to import data from another source.  The challenge for classic Notes development is that Lotus Notes makes it so very easy to integrate validation into the presentation layer (PL) of a form by adding validation formula directly to a field on a form.  It's not perfect, as I get one error message for each field validation each and every time validation is triggered.  I must also remember to duplicate the logic should there be more than one way a document can be edited.  Despite this I have often found myself yielding to the RAD model of Lotus Notes development and placing my field validations on the form this way.   When you are building an application just for the Notes client and only one form is ever used to display documents for a core object the practical often rules over the theory of good design and OOP.  I do however wish Lotus Notes did a better job of supporting OOP by allowing me another way to specify validation logic as part of a class so that I could define it once as part of my BLL and use it in many places. (PL) as needed.
.
With the advent of XPages things got both better and worse at the same time.  To IBM's credit they have significantly expanded the ease with which validation rules can be added to an XPage control.  It now requires the mere checking of a box to indicate that a control must have a value entered.  Additional validators have been added that allow me to indicate a specific length range for a control's value, match a pattern etc.  I can even build my own custom validators for complicated validation logic.  There are also two error controls that integrate with these validators.  One allows me to display validation errors for a single field and the other allows me to consolidate these validation errors in a single place.  This is all great for building an application quickly.  But I would argue strongly that this approach also encourages developers to build applications with bad architecture.  Note: This is not the only aspect of XPages that irks me as encouraging bad application architecture, but that's another story.....
.
It took me many years to crack this nut. - (yes I am slow!) but there are solutions.  Ways in which the validation aspects of an application can be implemented that preserve the concepts of OOP and n-tier architecture and at the same time allow a Notes application developer using LotusScript and/or JavaScript/SSJS to remain productive.  It is even possible to take some of the great concepts found in Xpage validators and error controls and implement them as part of Notes Classic applications.
.
As is often the case with OOP, the way to constantly accelerate your Notes development is to separate out application specific logic from generic functionality.  Build reusable components for the generic functionality once and then include them in every application as needed.  In my example I am drawing from my ∙dominoFramework in which I have a DominoDocument class defined to add additional capabilities to the NotesDocument class (things I wish IBM would add).  In this class I have taken many of the XPage validators and implemented them as LotusScript giving my DominoDocument class methods such as validateConstraint(), validateDateTimeRange(), validateDoubleRange(), validateLength(), and validateRequired().  Note: I could take the same approach to implement this concept using JavaScript, SSJS, or Java.
.
The following is a sample of the code needed to support the validateRequired() method:-
'/**
' * Validate a required field has a value
' * @param FieldName The name of the field being validated
' * @param Message The message to be displayed if the validation fails
' * @return True = Field is considered to have a value
' * @unittest 001.011.060
' */
Function validateRequired(FieldName As String, Message As String) As Boolean
Dim Item As Variant
Try:
On Error GoTo Catch
If (iDocument.HasItem(FieldName$)) Then
Set Item = iDocument.GetFirstItem(FieldName$)
Select Case Item.Type
Case Text: If (Item.Text = "") Then Call recordValidationError(FieldName$, Message$) Else validateRequired = True
Case RICHTEXT:
If (Item.GetUnformattedText() = "" And IsEmpty(Item.EmbeddedObjects) And (Item.ValueLength = 88 Or Item.ValueLength = 104)) Then
Call recordValidationError(FieldName$, Message$)
Else
validateRequired = True
End If
End Select
Else
Call recordValidationError(FieldName$, "Error: Unable to locate field " + FieldName$)
End If
Exit Function
Catch:
Stop
Call recordValidationError(FieldName$, "Error " + Error$ + " validating field " + FieldName$)
Exit Function
End Function
.
To integrate validation with the presentation layer I add two special (temporary) fields to a document.  $ErrorFields contains a list of the fields in which a validation (or other) error has been detected.  $ErrorMessages contains a list of the error messages that should be displayed.  The validators manage the contents of these fields and using a special subform I can display a consolidated list of validation errors in a single place on all my forms without the need to write any new code.  I am now free to add the application specific validation logic as a validate() method within each of my core object classes.
.
Returning to our Notes Application Catalog example I can add validation for each of our core objects as the classes I have built extend the DominoDocument class and hence have the validator methods available to me.  For our Application object we may simply wish to ensure that a Title is always provided for our application.  Following our MVP model, a validate() method contains the BLL for validating any Application object.
'/**
' * Validate document placing
' * @return True = no validation errors detected
' * @unittest 010.001.002
' */
Function validate() As Boolean
Call DominoDocument..validate()
Call ValidateRequired("Title","Please provide a title")
validate = Not Me.HasValidationErrors
End Function
.
   The second part of our MVP is a domain controller (Presenter) specifically for a Notes Form.  In this case a onQuerySave() controller that responds to the Form QuerySave event that:-
  1. Causes the save to be terminated if there are validation errors;
  2. Moves the Form's cursor to the first field containing an error, and
  3. Triggers a refresh of hide-when formula allowing all our validation error messages to be displayed.
'/**
' * Form Event: QuerySave - Validate document
' * @param Source Reference to the NotesUIDocument generating this event
' * @unittest Manual
' */
Sub onQuerySave(Source As NotesUIDocument, Continue As Variant)
Try:
On Error GoTo Catch
Continue = Me.validate
If (Not Continue) Then
Call Source.GotoField(Me.ErrorFields.toArray()(0))
Call Source.RefreshHideFormulas()
End If
Exit Sub
Catch:
Stop
Resume Next
End Sub
.
Note: The call to DominoDocument..validate() initializes our $ErrorFields and $ErrorMessages fields.
.
The end result of this approach is a Lotus Notes form that pretty much mimics the validation behavior of XPages, but in a way that follows the sound software engineering principals of OOP and n-tier architecture.
image
.
Note: By implementing the above approach into Notes Classic applications you are taking yet another step in future proofing your applications.  The validation logic is no longer spread across a wide range of fields on any number of forms in your application, it is consolidated into a single method within each of your core object classes.  The code can be easily located and, should you ever decide to migrate your code to XPages, the task becomes a relatively simple one to translate your LotusScript validate() method into a comparable SSJS method.
.
OOP Series
 Part 1 - Wiring A View To A Class
Part 2 - Responding To An InviewEdit Event
Part 3 - Wiring A Form Or XPage To A Class
Part 4 - Building a Notes Application Catalog
Part 5 - Defining Classes For Domain Catalog
Part 6 - Creating An Automated Test Plan
Part 7 - Creating Automated Test Scripts
Part 8 - Why Prototyping Still Matters
Part 9 - Automating The generation Of Classes
Part 10 - The Constructor
Part 11 - How I Destroy The MVP/MVC Model

Never EVER Buy A HP Computer
Keine BewertungenBewertungen 0

OOP: Part 11 How I Destroy The MVC/MVP Model

Peter Presnell |   | Tags:  classes oop .dominoframework | Kommentare (0)  |  Besuche (573)
When reading about OOP and n-tier architecture you will often see discussion given to the Model-View-Controller (MVC)and/or Model-View-Presenter (MVP) design patterns.  No matter how many times I read the material on the subject I am never sure I understand 100% the way these patterns are supposed to be implemented.  Even when using programming languages such as C# it always seem to me that the resulting code is way more complicated than I would like and there is an excessive amount of duplication produced.  So I have developed my own unique approach for LotusScript that adheres to many of the core concepts behind these design patterns without having anywhere near the same level of complexity and/or duplication.  I am sure OOP purists can quickly pick hole in my approach, but I have found my own unique solution seems to be fairly effective at designing Notes applications that remain RAD, relatively simple and still have a solid architecture.
.
To me, one of the most important drivers of MVC/MVP is the need within an n-tier architecture for a lower tier to have absolutely no knowledge about any of the tiers that lie above.  This means, when I am writing my Business Logic Layer I have no knowledge about the client on which the application is running, Whether it is Notes client or Web client, XPages or Classic Notes, 30" Widescreen monitor or a small mobile screen.  Of course, the fact it is being written in LotusScript might eliminate a few of my choices!  
.
Business Logic Layer (Model)
My Business logic layer must consist of a collection of properties and methods that have no direct interaction with the user.  No use of NotesUI classes, no print or messagebox statements etc.  They receive input via parameters and pass back results via parameters and/or storing information in domain data (e.g. document fields).  In theory, the same business logic can be used by any number of presentation layers (remember there are even ways to get an XPage to execute LotusScript code).
.
Presentation Layer (View)

At the "top" of our n-tier application lies the Presentation Layer.  Forms, Views, Pages, XPages etc.  These design elements display information on the screen and then act as an event engine to invoke lower tiers.  Each of our presentation design elements contain their own unique set of events.  The opening or closing of a form/page, clicking of a button or action, the entering, exiting, or changing of a field etc.  When these event occur we must have a way in which we can interact with the business logic layer.  Some implementations of MVC/MVP allow the View to make calls directly to the Model.  Some model require that the View always go via a Controller/Presenter.  All models support the View talking to the model via the Presenter/Controller.  My approach for this is as follows:-
  1. If the event being responded to requires an action that can be executed by simply calling a method in the BLL then I wire the event directly to that method.
  2. If the event being responded to is complex in nature and/or requires additional interaction with the user I wire the event to a special Supervising Controller method.
  3. Controllers can be modularized in such a way that controller can call other controllers as well as make calls the the BLL.
  4. Controllers are part of the presentation layer and can interact with the user.  Because of this fact it is necessary to create separate controllers for each type of client you are supporting (e.g. Notes client versus Web client).
  5. Rather than creating separate classes for Model, View, and Controller, I place all the code in a single class.  I structure the class so that the BLL properties/methods are together in one section of the class code and the controller methods in another section.  I also use a naming standard that makes it clear what might be a BLL method and what may be a controller.  This makes even more sense with modern Notes development because this is LotusScript code and the chances are high that I am developing this for the Notes client only.  i.e. Because of the practical limits of developing XPage applications with anything other than SSJS I will rarely find myself needing multiple controllers within in LotusScript.
.
An Example
So let's return to our Notes Application catalog to see the above in practice.  Having scanned our Domain Catalog (or physical server) we will have collected a list of databases located on our server(s).  Some of these databases will be system databases and/or mail/PIM databases that are supported by Administrators.  So we need a process to "exclude" these databases.  In this application I decided to do this by assigning a status field to manage the workflow of our applications.  We will have two status "System" and "Mail" which are used to denote non-application databases and exclude them from a number of views.

At the BLL level we assign a new status by changing the Status field to the designated value as well as recording the date/time of the status change and the name of the person changing the status in separate fields.:-
'/**
' * Assign a new status to the document, recording details of who made the change
' * @param NewStatus The new status to be assigned
' * @unittest
' */
Function assignStatus(NewStatus As String) As Void
Select Case NewStatus
Case "Discovered","Mail","System":
If (Me.Status$ <> NewStatus$) Then
Me.Status$ = NewStatus$
Set Me.LastUpdatedBy = New NotesName(Session.Username)
Set Me.LastUpdated = New NotesDateTime(Now)
End If
Case Else ' Status Not recognized, so do nothing

End Select
End Function
.
Note that the above logic does not interact with the user in any way or make any assumptions about in what context it is being invoked.  At the presentation layer we will have two ways of invoking this logic.  From a view we might allow all selected documents to be assigned a status via a view action.  At the form level (e.g. when a document is opened) we may allow this one document to be assigned via a form action.  
.
For the view action we need to assign each and every selected document as per the BLL above.  We also need to ensure each document is saved.  We may also choose to refresh the view to display the udpated status as well as de-selecting the selected documents in the view.  Much of this logic is specific to a Notes view being displayed via a Note client and hence is implemented as part of the controller.
'/**
' * Request to change status of selected documents in view
' * @param NewStatus The new status to be assigne dto selected documents
' * @unittest
' */
Sub OnStatusAssignment(NewStatus As String)
Dim Database As DominoCatalogDatabase
Dim DatabaseDoc As NotesDocument
Dim SelectedDocs As NotesDocumentCollection

Set SelectedDocs = Me.SelectedDocuments.toDocumentCollection()
If (SelectedDocs.Count = 0) Then Exit Sub

' Update status for all selected documents

Set DatabaseDoc = SelectedDocs.getFirstDocument()
While (Not DatabaseDoc Is Nothing)
Set Database = New DominoCatalogDatabase(DatabaseDoc)
Call Database.assignStatus(NewStatus$)
Call Database.Save(False,False,True)
Set DatabaseDoc = SelectedDocs.getNextDocument(DatabaseDoc)
Wend

' Refresh UI View

If (Not UIW.CurrentView Is Nothing) Then
Call UIW.ViewRefresh()
Call UIW.CurrentView.DeselectAll()
End If

End Sub
.
The form action invokes the same BLL but the interaction is a little different.  We are now processing a single document which is already open in the Notes client.  We may decide that if the document is being edited we will not execute the save, providing the opportunity for the user to exit the document without being saved.  If the document is open in read mode we do not have this option so we force the change to be saved as part of the event.
'/**
' * Assign a new status to the database record
' * @param newStatus The New status to be assigned
' * @unittest
' */
Function onStatusAssignment(NewStatus As String) As Void
Call Me.assignStatus(NewStatus$)
If (Not iUIDocument.Editmode) Then Call Me.Save(False,False,True)
End Function
.
In the presentation layer we have a view action which simply invokes our View Controller and we have a form action that  invokes our Form Controller.  These actions need to know nothing about how the action gets executed.  The code in our presentation is therefore never more than a single line to invoke the relevant controller or BLL method.  In some cases, such as Form/View events, we can even trap the events as part of our class requiring no code to be added to our form/view.
Sub Click(Source As Button)
Call CatDatabase.onStatusAssignment("System")
End Sub
.
In Part 12 of this series we will look at the issue of form/page validation at look at ways we can add some of the concepts found in XPage validators to Classic Notes Forms via LotusScript code.
.

Keine BewertungenBewertungen 0

OOP: Part 10: The Constructor

Peter Presnell |   | Tags:  oop ∙dominoframework class constructor | Kommentare (0)  |  Besuche (518)
 In Part 9 of this series I shared a few ideas about how it is possible to automate the creation of a shell for a LotusScript class by including property definitions based upon a Form definition or the fields contained in an existing Notes document.  Properties are perhaps the most frequent and least complex components for a class so automating this task is a big win for Notes developers.  It frees up time to focus on the all important business logic.  In today's article I wish to look at the constructor, and again look at how we can simplify/automate this important  class component.
.
In most Notes applications the activity is centered around processing documents.  Most of the classes we define are based upon documents of a particular type (e.g. Form).  In our Lotus Notes Application catalog we have three core objects identified (Application, Database, and Replica).  These core objects have classes which extend the NotesDocument and NotesUIDocument classes.  Not only will we need to wire our forms to these classes, but the methods we create will often need to access another document that is related to the current document.  The constructor is used to instantiate an instance of a specific class.  For Notes Document-centric classes it therefore pays to have a wide range of options for how you application can indicate exactly which document it is that they need to process.
.
The DominoDocument Constructor
One of the first classes I wrote when I started doing Notes development using OOP was a class to extend the NotesDocument class.  This I called DominoDocument.  One of the limitations of LotusScript as an OOP language is that it does not support method overloads.  So I can only have one constructor for each class.  Another limitation of LotusScript is that method parameters cannot be declared as optional.  So to work within the limitations of LotusScript I typically adopt a pattern of declaring a single parameter for my class constructors and declaring it to be of type Variant.  This allows me to create pseudo constructors within my single constructor by conducting a check on the TypeName of the parameter passed.  Why is this important?  Applications often have different contexts from which they need to instantiate a document-centered class.:-
  1. I may already have an instantiated NotesDocument or NotesUIDocument I wish to extend
  2. I may have a ViewEntry that points to the document
  3. I may wish to access the currently opened document (whatever that happens to be)
  4. I may know the NoteID, UNID, or even NotesURL of the document
  5. or I may have some form of key that is specific to documents of this type that I need to look-up to locate the document
The implementation may vary a little from one application to another but generally the same code can be used to handle all these scenarios.  With LotusScript the Constructor has one characteristic that differentiates it from other methods.   If a class extends another base class then the constructor of the base class is executed first and then the constructor for the extended (super) class is executed and so on up the chain.  For standard methods only the outermost method with a matching name is executed.  By basing all document-centric classes from a base class (DominoDocument) it is possible to develop and place most of the generic constructor code in the base class.  Typically the only code the super class requires is to deal with the application specific logic of finding a document based upon a key value.
.
The following is the constructor for my DominoDocument Class
' C O N S T R U C T O R _______________________________________________________________________________
'/**
' * Constructor
' * @param Source A reference to the NotesDocument being extended by this class
' * NotesDocument
' * NotesUIDocument
' * NotesViewEntry
' * NotesID (String)
' * DocumentUNID (String)
' * NotesURL (String)
' * Key (String or String Array) - Function getBykey will be invoked to locate document in a designated view using provided key
' * @unittest 001.011.051
' */
Sub New(Source As Variant)
Select Case Typename(Source)
Case "NOTESDOCUMENT": Set iDocument = Source
Case "NOTESUIDOCUMENT":
Set iUIDocument = Source
If (Source.Document Is Nothing) Then On Event PostOpen From Source Call OnPostOpen Else Set iDocument = Source.Document
Case "NOTESVIEWENTRY": Set iDocument = Source.Document
Case "STRING": Call getByKey(Cstr(Source))
Case "STRING( )": Call getByKey(Source)
End Select
End Sub
.
The class's getByKey() method deals with the scenarios of a NotesID, UNID, or NotesURL.
'/**
' * Locates an existing document based upon the document's key (Invoked by constructor only)
' * Note: By default the "current" database is assumed. Used DominoDatabase.getDocument() to access documents by key in another database
' * @param Source Key that can be used to locate the existing document (NoteID, UNID, Notes URL or HTTP URL)
' * @unittest 001.011.051
' */
Private Function getByKey(Source As Variant) As Boolean
Dim NotesObject As Variant

On Error Resume Next ' Prevent error if key is not a a valid NotesURL
Select Case Typename(Source)
Case "STRING"
If (Ucase(Left(Source,6)) = "NOTES:" Or Ucase(Left(Source,5)) = "HTTP:") Then
Set NotesObject = Session.Resolve(Source)
If (NotesObject Isa "NotesDocument") Then
Set iDocument = NotesObject
getByKey = True
Exit Function
End If
End If
Select Case Len(Source)
Case 1,2,3,4,5,6,7,8: ' Possible NoteID in LS format. Check if the string is a hex number
If (Math.hexToDecimal(CStr(Source)) > 0) Then Set iDocument = DB.GetDocumentByID(Source)
Case 9: Set iDocument = DB.GetDocumentByID(Left(Source,8) + Right(Source,8)) ' Possible Note ID in @Language format
Case 32 : Set iDocument = DB.GetDocumentByUNID(Source) ' Possible UNID
End Select
End Select
If (Not iDocument is Nothing) Then
If (Not iDocument.IsValid) Then Set iDocument = Nothing Else getByKey = True ' Valid UNID returns an invalid document if UNID not found
End If
Exit Function
End Function
.
So when it comes time to create my constructor for a new class such as our Database Class it is rare that any code is needed in the constructor itself.  That is all handled by the DominoDocument constructor.  I do need to create my own version of the getDocumentByKey method.  In the example below you will see that I use the statement  Call DominoDocument..getByKey(Source) to invoke the underlying DominoDocument.getBykey() method first.  If that fails to find a document based upon Notes ID, UNID, or NotesURL then I will do a lookup into a specific view in my application to find a match on the first sorted column (key).  Simply omit this call if you wish to only look up documents based upon an application key.
'/**
' * Locate an existing document based upon the provided key value
' * @param Source Key that can be used to locate a database document (ReplicaID)
' * @return True = document was found
' * @unittest
' */
Private Function getByKey(Source As Variant) As Boolean
Dim DatabaseList As NotesView

Call DominoDocument..getByKey(Source)
If (Not iDocument Is Nothing) Then Exit Function
Set DatabaseList = DB.GetView(FRAMEWORK∙VIEW∙DATABASES)
If (DatabaseList Is Nothing) Then Exit Function
Set iDocument = DatabaseList.Getdocumentbykey(Source,True)
getByKey = Not iDocument is Nothing
End Function
.
One of the things I like most about OOP is its support for the concept of write a solution once and use it many times.   Yes, you would be surprised how often you can exclaim...  "There's A Module For That"
.
In Part 11 of this series I will progress to creating some of the application specific methods needed for our Notes Application catalog.

Keine BewertungenBewertungen 0

OOP: Part 9 Automating The Generation Of Classes

Peter Presnell |   | Tags:  oop ∙dominoframework classes | Kommentare (2)  |  Besuche (545)
 In Part 8 of this series I discussed the use of prototyping as a way of interacting with our users to get a good feel for the overall requirements of our applications.  Having completed the prototyping we now have three forms, one for each of our core objects (Application, database, Replica).  In implementing an n-tier architecture, the Forms combined with the Views represent to major part of the presentation layer for our Notes Application catalog.  The next step in the process is to build the classes into which we will add all our application logic.
.
Because we have already built our forms, we have already defined many of the components we will need in building our new classes.  Our class will require properties for our Domain data.  These largely represent the fields on our form (excluding computed for display). We can either review the form definition and create the property code manually one property at a time or we might consider automating the process.  I have previously mention the property generator tool available in my .Domino Framework 1.0 project on OpenNTF as a way of building the code for each property.  But it is possible to go one step further and build an entire class definition using either the Form definition or by taking an example document and inspecting the fields stored inside the document.  To get the greatest amount of information about our form you will most likely need to use DXL.  This will allow you to identify:-
  1. The name of each field
  2. The type of field (e.g. to eliminate computed for display fields)
  3. The type of data stored in the field (determines the data type of the property)
  4. Whether or not the field allows multiple values (important in defining the property)
  5. Description and field hint (can be used to document each property)
.
So by taking one of our existing forms such as this:-
image

.
And passing this through a code generation tool, we can produce a Class Definition such as the following even before we have had to write a single line of code ourselves.   As with Unit Testing, OOP offers many great opportunities for automation.
'/**
' * Class Database
' *
' * @author Peter Presnell
' * @version 1.0.0
' * @since 1.0.0
' */
Class Database As DominoDocument

' C O N S T R U C T O R ___________________________________________________________________________________
'/**
' * Constructor
' * @param Source Reference to NotesDocument being extended by this class
' * @unittest
' */
Sub New(Source As Variant)

End Sub

' P R O P E R T I E S______________________________________________________________________________________

'/**
' * Status
' * @unittest
' */
Property Get Status As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Status")) Then Status$ = iDocument.GetItemValue("Status")(0)
End Property
Property Set Status As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("Status",Status$)
End Property
'/**
' * A brief description of the application
' * @unittest
' */
Property Get Abstract As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Abstract")) Then Abstract$ = iDocument.GetItemValue("Abstract")(0)
End Property
Property Set Abstract As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("Abstract",Abstract$)
End Property
'/**
' * Production, QA, Development etc.
' * @unittest
' */
Property Get DBEnvironment As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DBEnvironment")) Then DBEnvironment$ = iDocument.GetItemValue("DBEnvironment")(0)
End Property
Property Set DBEnvironment As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("DBEnvironment",DBEnvironment$)
End Property
'/**
' * Process by which application appeared on our radar screen
' * @unittest
' */
Property Get Source As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Source")) Then Source$ = iDocument.GetItemValue("Source")(0)
End Property
Property Set Source As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("Source",Source$)
End Property
'/**
' * Database inherits design from this template
' * @unittest
' */
Property Get Template As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Template")) Then Template$ = iDocument.GetItemValue("Template")(0)
End Property
Property Set Template As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("Template",Template$)
End Property
'/**
' * This database is a template assigned this name
' * @unittest
' */
Property Get TemplateName As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("TemplateName")) Then TemplateName$ = iDocument.GetItemValue("TemplateName")(0)
End Property
Property Set TemplateName As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("TemplateName",TemplateName$)
End Property
'/**
' * The server that administers changes to ACLs etc.
' * @unittest
' */
Property Get AdminServer As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("AdminServer")) Then AdminServer$ = iDocument.GetItemValue("AdminServer")(0)
End Property
Property Set AdminServer As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("AdminServer",AdminServer$)
End Property
'/**
' * The unique identifier assigned to each database when it is first created
' * @unittest
' */
Property Get ReplicaID As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("ReplicaID")) Then ReplicaID$ = iDocument.GetItemValue("ReplicaID")(0)
End Property
Property Set ReplicaID As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("ReplicaID",ReplicaID$)
End Property
'/**
' * IBM's categorization of Notes databases
' * @unittest
' */
Property Get DatabaseType As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DatabaseType")) Then DatabaseType$ = iDocument.GetItemValue("DatabaseType")(0)
End Property
Property Set DatabaseType As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("DatabaseType",DatabaseType$)
End Property
'/**
' * List of people/groups with Manager level access to database
' * @unittest
' */
Property Get Managers As DominoArray
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Managers")) Then Set Managers = New DominoArray(iDocument.GetItemValue("Managers")) Else Set Managers = New DominoArray(Nothing)
End Property
Property Set Managers As DominoArray
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("Managers",Managers.toArray())
End Property
'/**
' * Date the database was last opened
' * @unittest
' */
Property Get LastAccessed As NotesDateTime
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("LastAccessed")) Then Set LastAccessed = New NotesDateTime(iDocument.GetItemValue("LastAccessed")(0))
End Property
Property Set LastAccessed As NotesDateTime
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("LastAccessed",LastAccessed)
End Property
'/**
' * Last known size of the database
' * @unittest
' */
Property Get Size As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Size")) Then Size$ = iDocument.GetItemValue("Size")(0)
End Property
Property Set Size As String
If (iDocument Is Nothing) Then Exit Property
Call iDocument.ReplaceItemValue("Size",Size$)
End Property

' M E T H O D S ___________________________________________________________________________________________

'/**
' * Initialize new document
' */
Function initialize() As Void
Me.FormName$ = "Database.frm"
End Function

End Class
In Part 10 We will discuss the fine tuning of our classes with methods and controllers.
.
OOP Series
Part 1 - Wiring A View To A Class
Part 2 - Responding To An InviewEdit Event
Part 3 - Wiring A Form Or XPage To A Class
Part 4 - Building a Notes Application Catalog
Part 5 - Defining Classes For Domain Catalog
Part 6 - Creating An Automated Test Plan
Part 7 - Creating Automated Test Scripts
Part 8 - Why Prototyping Still Matters
.

Never EVER Buy A HP Computer
Keine BewertungenBewertungen 0

OOP: Part 8 Why Prototyping Still Matters

Peter Presnell |   | Tags:  prototyping .dominoframework oop | Kommentare (0)  |  Besuche (573)
So  far to build our Notes Application catalog we have:-
  1. Identified three core objects  - Application, Database, and Replica.
  2. identified theDomain Catalog as a source for feeding our new application
  3. Built two classes to access the Domain Catalog;
  4. Created a test plan and automated test scripts to test our access to the Domain Catalog.
Now we need to build out the classes for Application, Database, and Replica.  The DomainCatalogEntry class provides a few clues as it tells us what data is being collected automatically by the Domino servers.  But we also need  to identify the additional data that will make our application add value to the organization.  The approach we take varies by application.  In some cases we are provided with a detailed and accurate set of requirements with which to work.  In many cases the Notes Developer acts as an Analyst/Programmer and often needs to work with new applications in which the needs are not well defined and open to suggestions.  This is the skill or the true Notes developer and will be the case in our scenario.  Our manager knows he/she needs to get a better handle on all the Notes applications that exist within the organization but not a lot of detailed thought has yet gone into what exactly is needed.  A common scenario and one in which Prototyping lends itself. 
.
Prototyping
I have never found too many people (especially business users) who have the ability to read lengthy requirements or specification documents and have a clear understanding of exactly what it is they will be getting if they sign off on the document(s).  I have always found the best applications are created when prototyping  is used and one or more sessions held to review the prototype live on the big screen.  These prototyping sessions present the application in a way that is so much easier to visualize and interact.  Shortcomings can be more easily identified in the design phase.  It is so much easier to play out a series of stories/use cases to see if the current "requirements" meet expectations.  It stimulates ideas on how the process and application can be improved.   Prototyping sessions are an example of the power of collaboration.  And when it comes to prototyping there is perhaps no better tool than Lotus Notes for building functional prototypes.

Forms
In our example we need to create three forms.  One for each core object.  On the form we need to decide:-
  1. What fields should appear and their data type
  2. When fields should appear (progressive disclosure)
  3. What type of control is best suited to display the field (text, combobox, listbox, check box etc.)
  4. How best to group related fields
  5. What order the fields/groups should appear.
The above help define our properties for our classes.  To make the prototype more realistic we should also think about the actions we may want to take against each document and how we might present those (actions, buttons, hotspots, picklists, change events etc.).  This will help identify some of the methods and controllers we will need to add to our classes.  We don't need to write much (if any) LS code at this point, we just want a realistic representation of the form with which our Manager can interact to drive a discussion about how well this matches his/her expectations.
.
Views
Having designed the form we now have a good idea about the Fields/Properties needed for our classes.  We can now work on creating views that display these classes in an orderly manner.
  1. What is the most likely way we may want to look at a application/database/replica ?(i.e. the default views)
  2. When we look at a view what columns must we have and what values can we observe via the Form?
  3. Will a preview mode allow us to have less data in our views? (great for performance)
  4. How many ways do we need to sort/categorize the data?  When do we need new views versus dynamic sort columns
  5. What subsets of data do we need to observe?  (e.g. databases not yet part of an identified application)
  6. Will Inview editing speed up the process of quickly adding data to documents?
  7. What actions may I want to take against all (or selected) documents?
Answers to all the above will help us build requirements for our three core objects.  Where the opportunity exists we should repeat the prototyping phase as many times as possible until we have a solid understanding and agreement as to what exactly it is that we need.  Making changes to our application's requirements after the prototyping ends begins to get more and more expensive.   Iterative prototyping is an investment in reducing the overall cost of the project as well as increasing the chances we get it RIGHT!

So we build the forms and views and add the views to so outlines ready for the first prototype session.  After a few hours work our prototype now looks like this:-
image






.
The Prototyping Session(s)

We set up a meeting with or manager to review the prototype.  We invite a few of our fellow Notes developers.  And by good fortune our Manager's boss found out about the project and has offered to sit through the initial prototyping session along with a member of internal audit.  This is good because we are now going to get differing views based upon differing needs.  Seven always seems to be a magical number.  Getting up to seven people with a vested interest in the application usually enhances the quality of prototyping sessions.  When the numbers start to increase beyond that sessions seem to get bogged down in trying to reach  a consensus on ANYTHING.  The session is good.  We learn a few things.  The question comes up about how do we identify applications that are no longer being used.  An important statistic, Date Last Accessed can be a good indicator.   How big and complex is the application?  We have already identified size as a property to be included, but how about the number of forms and views in the application?  What about the number of Libraries or Agents?  This might indicate how sophisticated an application is.  There is a lot of executive  interest in how we get control of all these applications and assign them to specific people or groups.  We need to identify mission critical applications,  We need to identify applications with sensitive data.  We need to find out all those applications no longer being used and archive them.  We need to find out those applications still being supported by end users.  For the rest we need to ensure they are assigned to an application team.  We need to identify development copies and backup copies of database and get them off production servers.  We also need to get all the production applications off development servers.  All good ideas.  It was also suggested we might like to provide a mobile interface to the application to provide executives with a way when there at a meeting to quickly dial up an application by name on their Blackberry and find out about it.  I nod in agreement, but of course immediately shelve that one for version 2.0.  We need to be able walk before we try to start running.....
.
After two prototyping sessions we reach an agreement with a few minor tweaks of what would be acceptable as a 1.0 release of our new Notes Application catalog.  In Part 9 we will start building our classes to support the business logic for the above.  After all, the presentation layer is all but complete as a result of the prototyping.
.
OOP Series
Keine BewertungenBewertungen 0

OOP: Part 7 Creating Automated Test Scripts

Peter Presnell |   | Tags:  unittest oop .dominoframework | Kommentare (1)  |  Besuche (660)
 As discussed in Part 6, a move to Object Oriented Programming is not just about changing the way you write your LotusScript code.  It also opens a lot of door when it comes to testing applications.  OOP lends itself to automated testing to a much greater level than other coding styles.  Those of you who program in Java may be familiar with jUnit, and .Net developers with Nunit.  Both are frameworks for writing repeatable tests.  Both are very similar in style....  Execute a piece of code and then test to see if the results match those expected.  I have made unit testing one of my focuses over the past six months.  For me testing has always been a weakness.  I simply enjoy writing code so much that testing has always seemed like a chore (slow death).  So I decided to combine the two.  Write some code that does the tests.  In the process I developed my own unit testing framework called ∙dominoUnit.  The code itself is not very complicated but you will need something as Lotus Notes does not provided anything out of the box to test either LotusScript of SSJS.  The following may provide you with a few clues on how you can set about building a test framework for yourself.
.
In Part 6 we created a Test Plan.  This helps us to identify all the code modules we need to test to ensure our classes will work as designed.  By following a MVC/MVP model the Business Logic Layer has no direct interaction with the user so these properties/methods lend themselves to automation.  This is typically 80%+ of our code.  So far we have two classes to test.  I like to run all the tests for a class together in what I call a "Test Suite".  For each property/method I have a "Unit Test".  In some cases it makes sense to combine several properties/methods into the one "Unit Test" as they are part of an integrated set of logic.  Each test is assigned a unique ID.  After assigning a unique Test ID I place that ID in the comments for each code module as documentation and to allow further automation of the process.  With each "Unit Test" I have a series of individual tests I need to run in order to test a range of scenarios in which the code could be executed.  Here I would execute the code and then have one or more "Assertions" I can use to measure the results.  Typically the Assertions involve looking at the values returned by my code and/or looking inside the Notes database to see if the views, documents, fields etc contain the expected values.
.
The following is the Test Suite used to test the DominoDomainCatalogDB class created in Part 5.
'/**
' * Unit Testing for DominoDomainCatalogDB class in ∙domino.DirectoryServices
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Option Public
Option Declare
Use "∙domino.UnitTest"
Use "∙domino.DirectoryServices"
Sub Initialize
Dim UnitTest As DominoUnitTest
Dim TestDB As DominoDomainCatalogDB
Dim TestDocument As DominoDocument
Try:
On Error GoTo Catch
Set TestSuite = New DominoTestSuite("∙domino.DirectoryServices","Class DominoDomainCatalogDB")
TestSuite.OutputDestination = FRAMEWORK∙TESTOUTPUT∙DATABASE

' C O N S T R U C T O R _______________________________________________________________________________

Set UnitTest = TestSuite.addTest("002.004.001","DominoDomainCatalogDB.New()")
Set testDB = New DominoDomainCatalogDB(Nothing)
Expected = "Domain Catalog"
Actual = testDB.toDatabase().Title$
Call UnitTest.assertEquals("dominoDomainCatalogDB.New(Nothing)",Expected,Actual)

Set testDB = New DominoDomainCatalogDB("Domain Catalog")
Expected = "Domain Catalog"
Actual = testDB.toDatabase().Title$
Call UnitTest.assertEquals("dominoDomainCatalogDB.New(String)",Expected,Actual)

' M E T H O D S _______________________________________________________________________________________

Set UnitTest = TestSuite.addTest("002.004.002","DominoDomainCatalogDB.getAllEntries()")
Actual = TestDB.getAllEntries().toDocumentCollection().Count
Call UnitTest.assertTrue("dominoDomainCatalogDB.getAllEntries()",Actual > 1000)

' R E P O R T R E S U L T S _________________________________________________________________________

Call TestSuite.ReportResults()
Exit Sub
Catch:
Call UnitTest.LogError()
Resume Next
End Sub
It is a relatively simple example.  You can see that I have developed a DominoUnitTest class which supports an assertEquals method to test two values are the same and an assetTrue method to determine if a condition is true.  I am slowly expanding the number of Assertions my own class supports.  A key one not used in the example is assertDocumentFieldEquals which can be used to test if the nominated field in a document contains the expected value.  jUnit and Nunit provide great example of the range of assertions you may need.
.
For a simple implementation it is only necessary to write out a message each time a test fails.  In my own ∙dominoUnit framework I have extended the concept further to store the results in a separate Notes database, consolidating them with the Test Plan created in Part 6.  Each time I execute a Test Suite I create two separate sets of entries.  The first entry is a log that shows details of all the tests run together, including information about the tester and his/her environment in conducting the test.  This log is also written to the Agent log.
image


The second set of entries are to update a record in my Unit Test database for each Unit Test and whether or not is passed/failed.  This allows me to aggregate my test results by class, library, application to get a feel of how many tests have passed, failed, and remain to be run.
image



There are a few other steps you can take to help make the test process a smooth one.  To ensure the results are always the same each time the tests are run it is often necessary to have a fixed set of test documents to run the tests against.  I have added a place in my Unit Test database into which I can paste test documents from other Notes applications.  The paste process assigns a unique "Test Document ID" to the document when it is pasted allowing me to access that document for the test.  I have also developed a "Clone" method for my DominoDatabase and DominoDocument classes allowing me to take copies of a document or complete database against which I can run tests.  This ensures methods that make changes do not have a permanent affect on my test database.  Whenever a bug is reported in an application I can usually identify a document that has the problem and copy/paste it into my Unit Test database to ensure all subsequent tests retest my solution to this problem.  As time goes by the number of tests increase and my testing becomes more and more thorough.
.
The entire code for a Test Suite is placed in an agent allowing it to be run as a single operation any time required.  I would then create an agent to test all the classes in a LotusScript library.  This agent merely calls all the other agents that test each class.  So having set it all up I can run one agent to test (or retest) my entire BLL for an application.  For my own ∙dominoFramework application I presently have over 500 Unit Tests in 25 Test Suites that execute and test around 750 assertions.  Yes, it took me a while to write, but having written them I can completely retest my application BLL in 2-3  minutes.  To run these tests manually would probably take me 40+ hours and I doubt I would be anywhere near as thorough.  Having to write the test scripts often forces me to think about the code I have written, presenting opportunities to re-factor the code even before I start testing.  Yes, it acts like a code review.    So if you support mission critical applications or have applications that that are changed/enhanced on a regular basis, an investment in OOP is probably going to pay off big time simply because of the opportunities presented by automated testing.  That's even before we consider the many advantages offered by the code itself.
.
In Part 8 I will return to our application and start development of the views/forms needed to support our own core objects (Application, Database, Replica).
.
OOP Series
Keine BewertungenBewertungen 0

OOP: Part 6 Creating An Automated Test Plan

Peter Presnell |   | Tags:  testplan oop ∙dominoframework unittest | Kommentare (0)  |  Besuche (637)
 InPart 5 of this series we created two new classes to access the Domain Catalog.  One of the great things about OOP is that it lends itself to automated testing much more than other forms of Lotus Notes programming.  By following an MVC or MVP design pattern the Presentation Layer (i.e. View, Controller, Presenter) is separated from the Business Logic Layer (i.e Model).  The BLL is now a set of properties and methods which require no interaction with a user.  Because there is no interaction they can typically be tested by calling them in an agent with a defined set of inputs and then observing the results.  It may take longer to set up automated test scripts for an application, but once they are set up it is possible to rerun the tests in a matter of seconds/minutes versus the hours/days it can take to run similar tests manually.  Not only does this tend to make you more productive as a developer (in the long run) it often places you in a position to run unit tests more frequently and pick up errors much faster.  Who knows what that last little program change may have broken, right?  This type of approach also allows you to test paths that the Presentation Layer may not allow you to follow.  As your test suite grows you are now much more rigorous in your testing.  The end result is a much higher quality application.  Something that is good for you, your manager, and most certainly your customer.
.
So lets have a look at how we might approach this with our new classes from last time....
.
Test Plan
The first step in the testing process should be the generation of a test plan.  This maps out a complete list of all the tests that we need to execute.  It forces us to think ahead about how we want to test the application as well as providing us with an accurate measure of how much work will be involved in creating and executing the test scripts.  
.
If you have been looking closely at the code examples so far in the series you may have noticed that almost all the properties/methods contain a line in the comments in the format "@unittest xxx.yyy.zzz".  No, this is not an IP address, this is a reference to the Unit Test  I am planning to use to test this particular code module.  You can assign multiple test ids to a single code module and you can use the same test id to test multiple code modules in the same class.  The test id can be any unique string, but in the examples shown so far, xxx represents the LotusScript library, yyy represents the class within that library, and zzz represents an individual test for that class.  To speed up this process I decided to develop an application (∙dominoDoc) which parses the contents of my LotusScript libraries and generates documentation for the code, including any references to Unit Tests assigned.  I then developed a second application (∙dominoUnitTest) which reads the contents of ∙dominoDoc and imports the Unit Test data for a nominated LotusScript library.  This automatically creates a test plan for me in a matter of seconds.  The screenshot below shows the TestPlan for the LotusScript library containing my two classes for Domino Catalog.
.
Note: If you are like me, automating processes such as Documentation and Unit Testing are important as they allow me minimize the time I spend performing these tasks and more time doing what I like.... writing code!  So don't just automate everyone else's job, where possible look for opportunities to automate the dull boring parts of your own.
image






.
In the above example I have already started running my tests, so you can see, not only do I get an automated test plan, but I can also monitor the results of my Unit Testing.  In Part 7 I will step through the process of writing automated test scripts to test this test plan.
OOP SeriesPart 5 Defining Classes For Domain Catalog
Keine BewertungenBewertungen 0

OOP: Part 5 Defining Classes For Domain Catalog

Peter Presnell |   | Tags:  oop domaincatalog .dominoframework | Kommentare (3)  |  Besuche (767)
 In Part 4 I outlined a scenario for a new application to be built.  It is a Notes Application catalog.  Even before I get the chance to sit down with my manager to determine the detailed requirements I know I am going to have to extract data from the Domain catalog which, for my company, holds information about all the Notes databases across our entire domain.  To do this I have two core objects.

1) The Domain Catalog Database that holds all the information; and
2) The Domain Catalog Entry that holds information about each and every replica found on our servers.

I can open the Domain catalog and look at the data and quickly identify some of the fields that I will need to extract.  Using this information I can quickly construct a class that will allow me to extract the data from each Domain Catalog Entry.  I need to take care with two fields in particular.

1) ReplicaID: This is not stored as a string as I would need but rather in a NotesDateTime format.  I search the Notes discussion forums and find some clues that I need to use @Text(ReplicaID;"*") to convert this field into a string.
2) ManagersList: This field does not just give me a list of people/groups with manager access.  rather it gives me a long string that embedded additional information from the ACL such as the privileges and roles

For most of the other properties I can use the Property Generator provides with ∙dominoFramework.  The resulting class looks something like this:-
'/**
' * Represents and entry for a database replica within the Domain catalog.
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoDomainCatalogEntry As DominoDocument

' C O N S T R U C T O R _______________________________________________________________________________
'/**
' * Constructor
' * @param Source
' * @unittest 002.005.001
' */
Sub New(Source As Variant)

End Sub

' P R O P E R T I E S _________________________________________________________________________________

'/**
' * Administrator server assigned
' * @unittest 002.005.002
' */
Property Get AdminServer As NotesName
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DbAdminServer")) Then Set AdminServer = New NotesName(iDocument.GetItemValue("DbAdminServer")(0)) Else Set AdminServer = New NotesName("")
End Property
'/**
' * The type of database
' * @unittest 002.005.003
' */
Property Get DBType As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DbType")) Then DBType$ = iDocument.GetItemValue("DbType")(0)
End Property
'/**
' * The filepath in which this database is stored
' * @unittest 002.005.004
' */
Property Get FilePath As NotesName
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Pathname")) Then Set FilePath = New NotesName(iDocument.GetItemValue("Pathname")(0))
End Property
'/**
' * A list of the people/groups with Manager access to the database
' * @unittest 002.005.005
' */
Property Get Managers As DominoArray
Dim ManagerAttributes As Variant
Dim ManagerIndex As Long
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("ManagerList")) Then
ManagerAttributes = iDocument.GetItemValue("ManagerList")
For ManagerIndex& = 0 To UBound(ManagerAttributes)
ManagerAttributes(ManagerIndex&) = StrToken(ManagerAttributes(ManagerIndex&),"$%^",1)
Next ManagerIndex&
Set Managers = New DominoArray(ManagerAttributes)
Else
Set Managers = New DominoArray(Nothing)
End If
End Property
'/**
' * The replica id assigned to uniquely identify a database from replicas
' * @unittest 002.005.006
' */
Property Get ReplicaID As String
Dim Macro As String
Dim Eval As Variant

If (iDocument Is Nothing) Then Exit Property
Macro$ = |@Text(ReplicaID;"*")|
If (iDocument.HasItem("ReplicaID")) Then
Eval = Evaluate(Macro$,iDocument)
ReplicaID$ = Eval(0)
End If

End Property
'/**
' * The server on which the database is held
' * @unittest 002.005.007
' */
Property Get Server As NotesName
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Server")) Then Set Server = New NotesName(iDocument.GetItemValue("Server")(0))
End Property
'/**
' * Size of the database (in bytes)
' * @unittest 002.005.008
' */
Property Get Size As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DbSize")) Then Size$ = iDocument.GetItemValue("DbSize")(0)
End Property
'/**
' * The template from which the database inherits its design
' * @unittest 002.005.009
' */
Property Get Template As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DbInheritTemplateName")) Then Template$ = iDocument.GetItemValue("DbInheritTemplateName")(0)
End Property
'/**
' * For a template, the master template name
' * @unittest 002.005.005
' */
Property Get TemplateName As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("DbTemplateName")) Then TemplateName$ = iDocument.GetItemValue("DbTemplateName")(0)
End Property
'/**
' * Database title
' * @unittest 002.005.005
' */
Property Get Title As String
If (iDocument Is Nothing) Then Exit Property
If (iDocument.HasItem("Title")) Then Title$ = iDocument.GetItemValue("Title")(0)
End Property

End Class
This demonstrates on of the advantages of OOP.  Now when I go to refer to the ReplicaID or the list of Managers none of my applications need to have any understanding of the special way IBM store this information in the database.  By always accessing the properties this layer of complexity is removed.  The problem is solved once and then the solution used multiple times.
.
I also need to build a class that will provide me access to the Domain Catalog.  In this case I am going to use a feature of my fraemwork called a Connection in which I create a special Connection document in my application that has the information about where the database is located.  This can be either server/filpath or replicaid.  I will give that Connection Document document the title "Domain Catalog".  The DominoDatabase class i already have in my framework will know to look for a property called ConnectionName and then use that to locate the database.  That saves me a lot of time coding and give me the ability to change the location of the Domain catalog at any time by simply changing the Connection document.  The only piece of code i really need to write is a method that will allow me to get a document collection of all the Catalog Entry documents.  The code for this is as follows:-
'/**
' * Reprewsents the Domino Domain Catalog which holds information about all databases on the
' * current server (or servers where the database is replicated)
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoDomainCatalogDB As DominoDatabase

' C O N S T R U C T O R _______________________________________________________________________________
'/**
' * Constructor
' * @param Source
' * @unittest 002.004.001
' */
Sub New(Source As Variant)

End Sub

' P R O P E R T I E S _________________________________________________________________________________

'/**
' * The default connection name used to locate this database when accessed from another application
' * @unittest 002.004.001
' */
Property Get ConnectionName As String
ConnectionName = "Domain Catalog"
End Property

' M E T H O D S _______________________________________________________________________________________

'/**
' * The complete list of database entries held in the Domain Catalog
' * @return Document Collection containing all database entries in Domain Catalog
' * @unittest 002.004.002
' */
Function getAllEntries() As DominoDocumentCollection
Dim Entries As NotesView

Set Entries = iDB.Getview(FRAMEWORK∙VIEW∙CATALOG_ENTRIES)
If (Entries Is Nothing) Then Exit Function
Set getAllEntries = New DominoDocumentCollection(Entries)
End Function

End Class
In Part 6 I will show how I can automate the unit testing of these two classes.
.
OOP SeriesPart 4 - Buidling a Notes Application Catalog
Keine BewertungenBewertungen 0

OOP: Part 4 Buidling a Notes Application Catalog

Peter Presnell |   | Tags:  oop .dominoframework | Kommentare (0)  |  Besuche (688)
In the previous three parts of this series I have introduced a range of techniques that can be used to implement Object Oriented programming.  Perhaps I should have started this way in Part 1.  But I thought it might provide better context to the issues if they are wrapped in the actual process of building a real application.
.
The Scenario:
Management are concerned about the number of Notes applications being hosted on your organization's Domino servers.  After a number of re-organizations and transfers there seems to be a lot of Notes applications about which nobody seems to know anything about.  There is growing confusion about who supports what applications, and what applications could be safely retired.  Your manager has asked you to build an application that will gather information about all the known Notes databases so that a strategy can be developed for how best to manage those databases.
.
The Solution:
Being a Notes programmer the obvious solution is to build a Notes application to manage those databases.  You have been reading  lot lately about Object Oriented Programming so you have decided this would be the perfect opportunity to try the concept out.
.
Step 1: Create An Application Shell
It is rare that a Notes Developer starts an application from scratch, right?  Approached vary from developer to developer, but if you have been doing Notes for a while, the chances are high you may have a generic Application Template that you use as a starter kit for.  In this case I will be using a beta copy of the ∙dominoApplication 2.0 template that I have been working on (for the past 16+ years!).  It provides a ready-made OOP framework for me extend reducing the amount of work I need to do up front.  The following is a screen shot of the empty application before a single line of code has been written.
image 
If you look closely you will notice the template also includes a set of instructions providing a checklist of the tasks that are needed to complete the typical" application.  I can mark off steps as i complete them, flag tasks for follow-up and add comments to assist manage the development process.
.
Step 2: Identify The Core Objects
Data Analysis is key to most application development, and OOP is definitely no exception.  So we are going to start by trying to identify the Key objects around which the application will operate.  After some consideration I will take a stab at the following three core objects being needed:-
.
1) Database: A physical database represented by a an unique Replica ID.  The key for this object will be ReplicaID.  I will potentially gather information about databases via the Domain catalog and may also want the option to scan the data directory of a server and/or local hard drives.  To help with my understanding of these databases I will need to allow for the capture of a description about each database. Clear identification of templates will also help identify groups of templates related by a similar design.  It will also help to identify which databases are production, development, QA, backup, archives etc.
.
2) Replica: Any database may have multiple replicas residing on a range of servers.  These will reside on different servers and may have different filepaths.  We will typically manage replicas by linking them to the parent Database".  Providing clear information about the number and location of replicas for each database may help us better manage our replication topology and cut out a few unnecessary replicas or even identify databases missing a replica on a vital replication hub server.
.
3) Application: Business and developers tend to manage "systems" on the basis of Applications rather than databases.  We therefore need to be able to aggregate databases into an application.  Some more complicated applications will consist of multiple databases.  We also need to aggregate all the related databases for our development, QA environments plus consider backup copies, logs, templates, and archives.  For each application we are probably going to assign a Business Owner who is responsible for signing off on business requirements, a Data Owner who authorizes access to the databases, a development group responsible for making changes.  We may also have default developers and analysts who perhaps are typically the "goto" people called upon to resolves production issues or implement enhancements.
.
In Part 5 I will start the task of mapping out classes to define each of the above three core objects.

OOP SeriesPart 3 -OOP: Part 3 - Wiring A Form Or Xpage To A Class
Keine BewertungenBewertungen 0

OOP: Part 3 - Wiring A Form Or Xpage To A Class

Peter Presnell |   | Tags:  oop .dominoframework xpages lotusscript | Kommentare (1)  |  Besuche (916)
In Part 1 of this series I demonstrated a simple way to wire a View to a Class.  Today I will show a simple way to wire a Form to a Class and the comparable process of wiring an XPage to an Object.  This is perhaps the most common requirement in implementing Object Oriented Programming within Lotus Notes applications as the Form is where most of the action tends to take place between the user and the application in Classic Notes and (as we know) the XPage is where EVERYTHING now happens!

LotusScript
1) In the Global Options I add the following to allow the form to locate my code located in a LotusScript library:-
Use "∙domino.Applications.HR.Training"
2) In The Global Declarations I define a global variable to represent this class.
Dim Associate As HRAssociate
3) And finally, in the Form's QueryOpen event I add the following to instantiate the global variable with the NotesUIDocument I am operating against.
Sub Queryopen(Source As Notesuidocument, Continue As Variant)
Set Associate = New HRAssociate(Source)
End Sub
As a result of implementing the above code, it is now possible to move all the LotusScript code containing the logic for processing an Associate into the LotusScript library containing the Associate class.

SSJS
1) Add a resource to the Xpage that links to the library containing the SSJS Object
<xp:this.resources>
<xp:script src="/com.mycompany.dominoframework.hr.training.jss" clientSide="false"></xp:script>
</xp:this.resources>
2) With SSJS we can declare and instantiate the variable in a single step as part of the aferPageLoad event.  Note: The dataSource binding the XPage to our form has been assigned the var AssociateForm
' SSJS Code
var Associate = new HRAssociate(AssociateForm);

' The resulting XP source code
<xp:this.afterPageLoad><![CDATA[#{javascript:var Associate = new HRAssociate(AssociateForm);}]]></xp:this.afterPageLoad>
In Part 4 I will start discussing how we organize a NotesDocument style class.

OOP Series
Part 1 - Wiring A View To A Class
Part 2 - Responding To An InViewEdit Event
Keine BewertungenBewertungen 0

OOP: Part 2 - Responding To An InViewEdit Event

Peter Presnell |   | Tags:  lotusscript oop inviewedit | Kommentare (0)  |  Besuche (738)
 In Part 1 I outlined one of the ways a view could be wired to a class for the purposes of implementing OOP.  This included the code necessary to trap the InViewEdit event to process as part of your class.  Today I will look at how we might choose to develop an OOP approach to respond to an InViewEdit.
.
I still remember InViewEdit being added to Lotus Notes back in the days of R5.  My thoughts at the time were, what a great new feature.  It allowed me for the first time to build applications in which users could edit documents from the view, negating the need to constantly open and close documents.  A feature common to most other development platforms.  Despite its potential, I rarely see many Notes applications put this capability to work.  I think part of the reason is the complexity of understanding the way the InviewEdit event itself works.  You don't just check a box and it happens.  It requires some pretty complicated LotusScript to be written.  Rather than fire a separate InViewEditQuery and InViewEditSave events etc. Notes wraps everything up into a single call in which any number of columns might get processed multiple times by the same event handler.  At the time I couldn't believe this would be the model that IBM would eventually use for this event.  Surely this was a rush job to get InViewEdit out the door.  But 10+ years later we still have the same process.  And it has taken me almost all of those 10 years to devise a logical way to process these events.  I guess I'm slow, but I have always wanted a way in which I could share the same logic for editing a document via a form and apply that logic to any attempt to edit the same document via an InViewEdit event.  So lets have a look at how this can be done.  (Note this gets somewhat complicated so if i lose you just skip to Part  3 and save this for another day)
.
Yesterday we wired our view(s) to intercept the InViewEdit event.  This allows us to place the code to respond to this event inside our View-type Class as a delagate - onInviewEdit.  The first thing we must now do is determine which columns are being "Edited" and which stage in the InViewEdit process we are in.  Ignoring Inserting new records, we have two request types to deal with.  The first request is to validate that the value entered is acceptable.  The second event is to save the change.  To do this I try to ensure the same column is always assigned the same programmatic name on all the views in the group.  We only need to do this for columns that are designated as editable.  It is the programmatic name of the columns that is passed by the InViewEdit event.  So here is the complete View-type class from yesterday with the onInviewEdit method added:-
'/**
' * Views displaying design documents
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoDesignDocumentView As DominoView
'/**
' * Constructor
' * @param Source NotesView being extended by this class
' * @unittest Manual
' */
Sub New(Source As Variant)
Select Case TypeName(Source)
Case "NOTESUIVIEW": On Event InViewEdit From Source Call OnInviewEdit
End Select
End Sub
'/**
' * Event: In-View Editing
' * @param Source View being edited
' * @param RequestType 2 = Validate, 3 = Save, 4, Insert New Record
' * @param ColProgName Programatic names of columns being edited
' * @param ColumnValue New value(s) entered
' * @param Continue Should event continue to completion
' * @unittest Manual
' */
Sub onInviewEdit(Source As NotesUIView, RequestType As Integer, ColProgName As Variant, ColumnValue As Variant, Continue As Variant)
Dim PropertyName As Variant ' The property being updated
Try:
On Error GoTo Catch
ForAll ColumnName In ColProgName
Set PropertyName = Nothing

' Identify those properties for which inview Editing is supported

Select Case ColumnName
Case "$CIAO": Set PropertyName = New DominoDesignDocument∙CIAO(Source)
Case "$Class": Set PropertyName = New DominoDesignDocument∙Template(Source)
Case "$Comment": Set PropertyName = New DominoDesignDocument∙Comments(Source)
Case "$Watermark": Set PropertyName = New DominoDesignDocument∙Watermark(Source)
End Select

' Call the appropriate event handler for the request being made

If (Not PropertyName Is Nothing) Then
Select Case RequestType
Case FRAMEWORK∙INVIEWEDIT∙VALIDATE_REQUEST: Continue = PropertyName.onValidate(ColumnValue)
Case FRAMEWORK∙INVIEWEDIT∙SAVE_REQUEST: Continue = PropertyName.onSave(ColumnValue)
End Select
End If
End ForAll
Exit Sub
Catch:
Stop
DominoException.throw Me,iView
Exit Sub
End Sub
End Class
What I have done is create a separate class for each editable column and provided each with an onValidate and onSave event handler to process each of the requests for each of the columns.  In doing this I have created my own variation of the Model View Presenter (MVP) design pattern that is used to separate business logic from the presentation layer.
'/**
' * Controls inview editing of CIAO flag in design views. This allows developers to manually control the checkin/checkout flags used by CIAO.
' * Note: This does not replace the checkin/checkout feaure, but can be useful as a quick fix for some scenarios
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoDesignDocument∙CIAO As DominoViewColumn
Private iDesignDocument As DominoDocumentDesign

' C O N S T R U C T O R _______________________________________________________________________________
'/**
' * Constructor
' * @param Source Reference to document being edited
' * @unittest Manual
' */
Sub New(Source As Variant)
If (Not iDocument Is Nothing) Then Set iDesignDocument = New DominoDocumentDesign(iDocument)
End Sub

' C O N T R O L L E R _________________________________________________________________________________

'/**
' * Toggle document between checked-out and checked-in
' * @param Source New value
' * @return True = value successfully saved
' * @unittest Manual
' */
Function onSave(Source As Variant) As Boolean
OnSave = False
If (iDesignDocument Is Nothing) Then Exit Function
If (iDesignDocument.IsCheckedOut) Then Call iDesignDocument.undoCheckout() Else Call iDesignDocument.checkout()
If iDesignDocument.HasChanged Then Call iDesignDocument.Save(False,False,True)
End Function
'/**
' * Validate value being edited
' * @param Source New value
' * @return True = Value is acceptable
' * @unittest Manual
' */
Function onValidate(Source As Variant, Message As String) As Boolean
OnValidate = False ' For computed field this event should never be called
End Function

End Class
The constructor of the above class provides me with access to the DominoDocumentDesign class (Model) in which all the logic is stored for processing and storing a design document.   In this example I am displaying an icon in my design views which allows me to "Check-In" or "Check-Out" a design element each time I click on the icon for that design element.  because of this, there is no validation to be done.  I use the IsCheckedOut property to establish the current state of the document and then invoke the undoCheckOut() or checkOut() methods accordingly.  These methods are common to the entire application and not specific to InViewEdit event so I have avoided the need to duplicate logic by adopting a MVC/MVP design pattern.  The following is another example in which I allow the developer to add/update the comments for a design document.
'/**
' * Controls inview editing of comments in design views, allowing comments to be added/updated
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoDesignDocument∙Comments As DominoViewColumn
Private iDesignDocument As DominoDocumentDesign

' C O N S T R U C T O R _______________________________________________________________________________
'/**
' * Constructor
' * @param Source Reference to document being edited
' * @unittest Manual
' */
Sub New(Source As Variant)
If (Not iDocument Is Nothing) Then Set iDesignDocument = New DominoDocumentDesign(iDocument)
End Sub

' C O N T R O L L E R _________________________________________________________________________________

'/**
' * Save changes made to field
' * @param Source New value
' * @return True = value sucessfully saved
' * @unittest Manual
' */
Function onSave(Source As Variant) As Boolean
If (iDesignDocument Is Nothing) Then Exit Function
iDesignDocument.Comments = join(Source)
If (iDesignDocument.HasChanged) Then Call iDesignDocument.Save(False,False,True)
 onSave = True
End Function
'/**
' * Validate value being edited
' * @param Source New value
' * @return True = Value is acceptable
' * @unittest Manual
' */
Function onValidate(Source As Variant, Message As String) As Boolean
onValidate = True ' we really don't care what new value is entered
End Function

End Class
Note: The DominoViewColumn class adds one important piece here.  It takes the current row in the view (as established by the NotesUIVIEW.CaretNoteID property and converts that into a NotesDocument (iDocument)
'/**
' * Extends the NotesViewColumn object
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoViewColumn
Private iDocument As NotesDocument
'/**
' * Constructor
' * @param Source NoteID of Document
' * @unittest
' */
Sub New(Source As Variant)
Select Case TypeName(Source)
Case "NOTESDOCUMENT": Set iDocument = Source
Case "NOTESUIVIEW": If (Source.CaretNoteID <> "0") Then Set iDocument = Source.View.Parent.GetDocumentByID(Source.CaretNoteID)
End Select
End Sub
'/**
' * Save column value
' * @param Source New value being saved
' * @unittest
' */
Function onSave(Source As Variant) As Boolean

End Function
'/**
' * Validation for new entry
' * @param Source New value to be validated
' * @return True = Continue to allow edit
' * @unittest
' */
Function onValidate(Source As Variant, Message As String) As Boolean

End Function

End Class
In Part 3 I will discuss the concepts associated with wiring NotesUIDocuments to a class for the much simpler processing of Notes documents via a Form.
Keine BewertungenBewertungen 0

OOP: Part 1 - Wiring A View To A Class

Peter Presnell |   | Tags:  oop .dominoframework | Kommentare (0)  |  Besuche (1.055)
This is the first in a new series of technical articles designed to help those starting out in the world of Object Oriented Programming.
.
When adapting Notes applications to OOP there are perhaps three class types that tend to get used over and over.  These represent the Database, Views, and Documents in the application.  Each application will typically have one Database-type class for each database in the application.  There will normally be one Document-type class for each and every Form/Object in the application.  For views you can either have one class for the entire application (if any), one class for each group of views designed to display a specific document type, or even one class for each and every view.  It really depends on how extreme you wish to implement OOP concepts.  I usually try to take a practical line between the theory and keeping with the RAD-style of Notes development.  As a result, I take different approaches with each application depending on the type of logic I am adding to views.  Where views are simply used to display lists of documents and provide access to opening those documents I don't need a View-type class at all.   When supporting view actions that run against multiple documents, drag/drop into folders, and inview editing I usually create a separate class for each group of views used to display a particular Form/Object.
.
Organizing Libraries And Classes
It is often a good idea to have a base class in which you implement all the properties and methods that are common to many views across many applications and inject that class into your application via a shared LotusScript Library.  You could have one library for all classes or one library for each and every class.  Having one library for each class reduces the amount of code that is loaded, but also increases the number of moving parts that must be managed across your applications.  e.g. Within My ∙dominoFramework 2.0 I have a single LotusScript library called "∙domino" which contains around 30 generic classes I wish to use in all my applications.  One of these classes is called DominoView, which contains all the generic properties/methods I always find myself using when developing applications.  Within my new application I build a new LotusScript library containing a new View-type class that extends the DominoView class.  Now all I must do is write the new code that is specific to this one set of views. 
.
Example:
The ∙dominoFramework provides a series of views that display information about all the design documents contained within the application.  This is especially useful when working with applications in which the developer does not have Designer access to the production database.  I can use these views to see what design elements are present (or missing) or if a specific agent has been enabled (and on which server).  The following code is part of the class definition created for these views.  In this example I am wanting to support InviewEdit capabilities for all these views using a single set of business logic so I am creating a delegate for the InViewEdit event.  Note: I could also wire any of the other events such as QueryDragDrop or QueryPaste.
'/**
' * Views displaying design documents
' *
' * @author Peter Presnell
' * @version 2.0.0
' * @since 2.0.0
' */
Class DominoDesignDocumentView As DominoView

'/**
' * Constructor
' * @param Source NotesView being extended by this class
' * @unittest
' */
Sub New(Source As Variant)
Select Case TypeName(Source)
Case "NOTESUIVIEW": On Event InViewEdit From Source Call OnInviewEdit
End Select
End Sub

End Class
Wiring The Views
Having created my class the process of wiring my views to this class comes down to adding three LotusScript statements to each of my views.
.
1) In the Global Options I add the following to allow the view to locate my code:-
Use "∙domino"
2) In The Global Declarations I define a global variable to represent this class
Dim View As DominoDesignDocumentView
3) And finally, in the View's QueryOpen event I add the following to instantiate the global variable with the NotesUIView I am operating against.
Sub Queryopen(Source As Notesuiview, Continue As Variant)
Set View = New DominoDesignDocumentView(Source)
End Sub
.
I now have a global variable called View in which I can access all the properties/Methods defined in both the DominoView and DominoDesignDocumentView classes.  In addition, I have wired the InViewEdit event of the view to call my own delegate (OnInviewEdit).  So I do not need to place any more LotusScript code on my view.  This achieves one of my goals of implementing an n-tier architecture - separating all my business logic from the presentation layer.  All the logic is now contained in a single LotusScript library making it much easier to locate, trace, document, and test.
.
In Part 2 I will continue with this example to demonstrate how I have managed to organize the inview editing of information in these views.
.
Note: The above examples are based upon LotusScript but they can easily be applied to JavaScript, SSJS, and Java.  ∙dominoFramework 3.0 will expand the LotusScript base into XPages and SSJS/JS while maintaining much of the same structure as used with LotusScript.
Keine BewertungenBewertungen 0

OO-SSJS: Object Oriented Server-Sided JavaScript

Peter Presnell |   | Tags:  oop ssjs | Kommentare (0)  |  Besuche (841)
 In XPages kindergarten we started writing our code with crayons.  We were all just starting to learn a complex subject and so our teacher would let us find any place within an XPage  to scrawl out code.  It wasn't pretty, but as long as it worked nobody seemed to mind....

Now in XPage first grade the rules are changing.  The crayons have been taken away and we are being asked to write code that can be easily read (and shared) by others.  This means our teacher is starting to pay more attention to the form of the code and not just what it does.  Having learnt to write LotusScript using OOP I am naturally looking to do the same with SSJS (and even JavaScript).  The following are some of the lessons I have learnt so far.
...
Notes:
  1. I am still a long way from university and writing a thesis on the subject but this is a first graders effort at OO-SSJS.  2nd graders please point out my mistakes.
  2. OOP is almost identical for JavaScript and SSJS so I will simply refer to SSJS but the following also applies when developing client sided Xpage code using traditional JavaScript (aka ECMAscript).
Classes:
As a LotusScript/C#/VB.Net developer I always associated OOP with classes.  It turns out that classes are not the only way to implement OOP.  Classes are a structured way of defining objects.  In contrast, the prototypeing model supported by SSJS provides a lot more flexibility.  This is not unlike Notes development itself right!!!  When I first started writing SJS it bothered me that the language was not strongly typed and that it did not have classes.  Now I am beginning to think this could be really cool.  In relational databases we have a regimented structure where every row (document) must have a set number of columns (fields) and each column must contain data of the same datatype.  In the Notes world almost anything goes and it is up to the skill of the Notes developer to exploit this dynamic capability (eg. showing multiple document types in a single view using common field names).  So what if LotusScript & SSJS don't support abstract classes, sealed classes and interfaces like java/c#?  Are those things really needed when the majority of Notes/Domino projects have a single Notes developer?  If a useful way can be found to instantiate a base class or the need to extend a sealed class becomes necessary then why not let this happen.  I don't need a "code-lawyer" creating artificial boundaries for me.  By the time I have finished the debate with the lawyer I could have the entire app written!

In SSJS we represent objects by creating a function and then instantiating the object using the new statement.  The function is referred to as the constructor for the object as it is executed whenever the object is first invoked just like sub new in the LS class.
Example 1: Object with variables defined via the constructor:-

function
employee ()
{
var privatevariable;
this.publicvariable;
}
var emp = new employee()
Properties:
Strictly speaking SJS does not have properties.  Rather, what SSJS supports is both private and public variables.  These can be defined in one of two ways:-
  1. Constructor: The easiest way is to create private/global variables inside the constructor as demonstrated above.  This has the advantage that the definition of the property cannot be over-ridden.  Variables are made public using this.variablename whereas private variables are defined using a var statement.
  2. Prototype: The format of OOP implemented by SSJS is known as protype-based programming and uses the prototype statement.  This technique provides better support when it comes to extending a base class (inheretance is a  key principal in OOP).  It also allows properties/methods to be added dynamically, a really powerful capability not available in java or C#.
Example 2: Object with variable defined via prototype

functionemployee()
{
this.Document=source;
}
employee.prototype.publicvariable;
The difference between a variable and a property is that a property allows additional logic to be executed each time a property's value is accessed.  In the case of SSJS we are most likely to create objects to represent the content of Notes documents.  The properties corresponds to fields, so when we attempt to get a property value we need to extract the value from the Notes document.  And when we set a property value we need to ensure that value is store back in the Notes document.   Not all properties behave this need, but a great many will.  Variables do not allow us to do this...

The solution I have developed for this is to pass the XSPDocument as a parameter to the object's constructor.  This is saved in a public variable (Document).  For each property I then add a public variable to the object (using prototype).  This can be made private if you have a compelling reason, but why bother?  Who are you really protecting by doing this?   I then add two public functions to the object for the get and set (using prototype).  If you only need a get (or set) then just one function will suffice.  The get function contain the logic for extracting the data from the XSPDocument along with any other preprocessing that may be required before it is used.  The set function contains the logic needed to process the value before it is saved back in the XSPDocument.  This may include validation logic to prevent invalid values being stored.  This is the concept of encapsulation (another key principal in OOP).  I can then use the get function much like I would use the LS get property and the set function the same as a LS set property.  I also have the option of using the public variable directly.  This would be done for the purposes of efficiency where I need to refer to a property many times and I do not wish to pull the data from the XSPDocument each and every time.
Example 3: Notes Field Represented as a Property

Employee.EmployeeID;
Employee.prototype.getEmployeeID=function()
{
if ( this.Document.hasItem("Emp_No")) {this.EmployeeID = this.Document.getItemValueString("Emp_No")}
return this.EmployeeID;
}
Employee.prototype.setEmployeeID= function(value)
{
this.EmployeeID = value;
this.Document.replaceItemValue("Emp_No",value);
}

Note: At  this time SSJS is only supported within Xpages.  Back-end processing would be supported via Agents or Web Services which must be written using either LotusScript or Java.  It therefore seems to make more sense to develop SSJS classes to interact with the XSPDocument class than it does the NotesDocument class.  If at a later time IBM provides a clearer strategy for SSJS this decision may need to be revisited.

To simplify the task of creating these property statements I have added a tool to the .Domino Framework as outlined in my previous blog.
Keine BewertungenBewertungen 0

  • Anzeigen:   10
  • 20
  • 50
  • 100
  •  Elemente pro Seite
Zu Seite von 2 wechseln
Skip to main content link. Accesskey S
IBM Lotus Connections Hilfe Tools Produktinformation