Your Development & Design Resource
Creating meta-functions in IBM Notes Domino XPages SSJS for CRUD Operations
10/22/2018 by Chris Toohey
If you ask ten developers how to do something, you'll often get ten different ways of doing it. Each will feel it's at least their "best practice" if not the best practice, and each developer - if presented with the other nine ways to do the very same thing - will critique each method against their own.
Good developers will walk away with new techniques to add to their arsenal.
Great developers however will evolve their code not only with any appropriate newly-required technique, but will look at the approach each developer took to craft their method.
The easiest way to explain that is to give you a "real world" example.
A fellow developer (and friend!) reached out to me recently asking if I had an example of an IBM Domino XPages "Delete" button inside of
xp:repeat control that allowed a user to delete documents from the database.
My immediate thoughts are to create a
deleteThis() function in SSJS, and I was going to walk him through how to do that.
His next comment however sparked this article:
I would think I need to get a handle to doc universalID
I was going to assume that he was using an
xp:dominoView based Data Source, then referencing said Data Source with an
xp:repeat control to build a "view" table.
This would result in a handle on a
NotesViewEntry data type entry in the
xp:repeat. In other words,
var="someDoc" means that you have a local variable someDoc that is a handle on the NotesViewEntry in memory from the Data Source.
remove() method is available on the
NotesDocument class however. Simple enough really, as the
NotesViewEntry class has a
... but the developer mentioned the UniversalID, which is a String.
So their natural thinking would lead them to an architecture which had the function lookup the NotesDocument in the Database based on the UniversalID being passed to the function via an argument. Which makes me think that all of their other functions do the same.
In an attempt to support code maintainability, do I do the heavy lifting in the functions?
Also something to consider: you happen to have a handle on the
NotesViewEntry in this use case. In another use case you might only have the
NotesDocument. In another, you might only have the UniversalID (if you're returning results from an AJAX request or Web Service call).
So I’m left with questions.
- Do I pass a
- Do I pass a
- Do I pass the
The answer is yes.
Our function will be smart enough to handle passing a
String, and more. This way we support the way the developer intends to use this function in this individual case while also supporting future potential use cases… because you never know when you’ll need to delete a document and what you’ll have a handle on when you need to delete a document.
Now that we understand the (potential) application for our function, let’s take a look at our
Now that I've written that function... I want to re-write it. Instead of calling the
.removePermanently(true); function inside of the break for each type, I'd much rather set a
NotesDocument variable and populate that from the
NotesViewEntry, or the
UniversalID... and if my
NotesDocument variable isn't
null, then run the
.removePermanently(true); on the
NotesDocument variable and set the
But then I'd find something else I'd want to re-write. And then another thing. And then I'd want to adopt the "underscore prefix for local variables" syntax. And then something else.
The point I'm trying to make is not "yeah, I know this code sucks"... but rather point out something you may not have considered: since you have create a single point of maintenance for any/all deletion functions for your application, you can evolve this single function as the needs of your application evolves. And if the occasional case of developeritis flares up, you can choose to use some of the time that you saved yourself not having to touch every single XPage and every single function that deletes your docs in all of it's various states and types to scratch that itch. You'll still be ahead of the game!
Now that we have our “metafunction”, let’s look at three use cases:
Use Cases: Repeat Control / dominoView Data Source vs. "form" dominoDocument Data Source
Let’s take a look at a simple XPage that uses an
xp:dominoView Data Source. We’ll use an
xp:repeat control to create a simple “view” table where the first column of each row of our “view” will display a “delete” button for that row
You could alternately use this same code in a "form"-style XPage like this:
The result is the same: we're going to delete the specific "row" or the "document", but all using the same "metafunction" to handle the operation.
And while these two examples are using the same "metafunction" to delete the "document", they're handling the results differently based on their individual use case.
With our repeat control/dominoView data source example, we're simply repainting the grid to show that an entry has been deleted. However we're going to need to redirect away from the current XPage once we've successfully deleted the existing NotesDocument, otherwise we'll get an error (since the NotesDocument is no longer available to "edit" after we've deleted it).
In one of the current projects I'm working on, I have both UI and data source/backend controller metafunctions to handle everything. Very little is done on the individual XPage, as the "narrative flow" of the application UX (in non-buzzword-fu "the way the app goes from page-to-page") is controlled from - for example -
ui._delete(); which is the UI "delete" function used on the document.xsp XPage. After some validation (eg., "can you do that?", "should you do that?" etc. business logic evaluation), we throw to
ds._delete() (which is passed the data source that was used to start the whole thing).
ds.delete() function looks at
arguments not only to find the type (eg., NotesViewEntry, NotesXspDocument, NotesDocument, etc.), but also what that data is (eg., "document", "person", "invoice", "event"). Once it sniffs out exactly what that data is, the metafunction calls the individual deletion function for that specific data source (eg.,
All I need to do from the XPage is call
ui._delete() and the business logic coded in the SSJS libraries handles the rest.