dominoGuru.com

Your Development & Design Resource

Creating meta-functions in IBM Notes Domino XPages SSJS for CRUD Operations

Introduction

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.

The Problem

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.

The remove() method is available on the NotesDocument class however. Simple enough really, as the NotesViewEntry class has a getDocument() method.

... 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 NotesDocument to my deleteThis() function?
  • Do I pass a NotesViewEntry to my deleteThis() function?
  • Do I pass the UniversalID to my deleteThis() function?

The answer is yes.

Our function will be smart enough to handle passing a NotesViewEntry, a NotesDocument, 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 deleteThis() function.

deleteThis();

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 NotesDocument, NotesViewEntry, or the UniversalID... and if my NotesDocument variable isn't null, then run the .removePermanently(true); on the NotesDocument variable and set the rBoolean to true.

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 NotesViewEntry.

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).

Conclusion

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).

The ds.delete() function looks at arguments[0] 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., ds._document._delete()).

All I need to do from the XPage is call ui._delete() and the business logic coded in the SSJS libraries handles the rest.


About the author: Chris Toohey

Thought Leadership, Web & Mobile Application Development, Solutions Integration, Technical Writing & Mentoring

A published developer and webmaster of dominoGuru.com, Chris Toohey specializes in platform application development, solutions integration, and evangelism of platform capabilities and best practices.



More from dominoGuru.com

dominoGuru.com is powered by IBM Notes Domino XPages & hosted by Prominic.NET

Contact Us

Use our Contact / Feedback form or one of these email addresses:

Creative Commons License

Except where otherwise noted, dominoGuru.com by Chris Toohey is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.