Angular.js in XPages #5 – Routing

In this article I will show how angular using routing to create a bookmarkable URL string and how that fits together with additional controllers. This will start us on the path to creating our basic CRUD application. This article is based on the Routing and Multiple views tutorial 


Way back when the world was young (in internet terms a couple of years ago), when single page apps were all the rage, it was noticed that there was a significant drawback to only having one URL index.html; it was useless as a bookmark. Even worse it was impractical to send a link to a page within the application, and that made for some serious limitations.

Routing was created as a manner to not only provide a URL which could be referenced within the application, but it also helped to separate the code into discrete functional areas within the application. This route, this controller, this model and so on.

In Angular –“Providers are objects that provide (create) instances of services and expose configuration APIs that can be used to control the creation and runtime behavior of a service. In case of the $route service, the $routeProvider exposes APIs that allow you to define routes for your application.”

So we add the angular-route.js file which was in the original download to our database in the angularjs folder


Beginning our path towards true code separation we are going to create a new app.js file which will contain the architecture for the application. Our app.js file will contain the following:

  • The angular module and the dependencies for that module (previously we had a module with no dependencies)
  • The route configuration for the new application

In this way we are defining what our application needs to run (as an Angular app) and the route mapping (as we will see) for “this URL – this Template”

Because we are moving the module code up to the app.js we also need to create a ngView for the app rather than the ng-controller like we had before.

This will mean:

  • a change to the container DIV within index.html
  • moving the HTML to a template and not in the index.html itself

Overall we are not going to change that much code, we are just going to move it around to make more sense from an application perspective rather than a demo.

 * Created by mroden on 5/4/2014.

/* App Module */

var personApp = angular.module('personApp', [


    function($routeProvider) {
            when('/people', {
                templateUrl: 'partials/people-list.html',
                controller: 'PeopleListCtrl'
            when('/person/:docId', {
                templateUrl: 'partials/person.html',
                controller: 'PersonDetailCtrl'
                redirectTo: '/people'

You can see from the code above that we are going to create two new files – people-list.html and person.html. These are the two templates we are going to “route” to in this part of the application


The people list template is replacing the HTML code we had before in the index.html. This time though instead of copying the mustache layout as we did before we are actually going to go for a simple table. In an analogous manner to using a repeat control in XPages we create the shell of the table and use the rows as the repeating element wrapper.

<table class="table table-striped">
        <th>First Name</th><th>Last Name</th><th>Zip</th><th></th>
    <tr ng-repeat="person in people">
        <td><a class="btn btn-info" href="#/person/{{person['@unid']}}">Edit</a></td>


If you look closely at the Edit button code here you will see that we are manually creating a route map for the person. The {{person[‘@unid’]}} template pointer is going to be filled with the JSON Object nth value and the “@UNID” parameter therein. Remember that @ symbols are classed as parameters and not real key pair values within JSON (*again dumb dumb idea IBM*). So in this case we cannot refer to it as person.@unid that would fail.

Looking at the HTML generated you will see that this creates a URL which may seem strangely familiar.

And that was constructed from the DDS JSON data as we saw before


In the person template we are going to display the 3 fields as if they were editable. The analogy here is between an XPage and the data binding to Document Source. In this case the source is the JSON string data retrieved from the DDS.

<legend>A Person</legend>

<div class="row" style="width: 50%">
    <div class="col-sm-4">
        <label>First Name</label><br/>
        <input class="form-control" name="firstname" type="text" value="{{person.firstname}}">
    <div class="col-sm-4">
        <label>Last Name</label><br/>
        <input class="form-control" name="lastname" type="text" value="{{person.lastname}}">
    <div class="col-sm-4">
        <input class="form-control" name="zip" type="text" value="{{}}">
<div class="row" style="width: 50%">
    <div class="col-sm-12">
        <a class="btn btn-default" href="#/people">Back</a>

<hr />

The controller

Within the controller.js file we now need to manage not only the download of data for the people, but also for the person who will be subsequently opened. In this case we need two controllers to handle the People and the Person. Both controllers access the DDS data, first for the view information and secondly for the document information.

If you look at PeopleListCtrl you will see only two dependencies ($scope and $http), whereas PersonDetailCtrl needs 3 ($scope, $http and $routeParams). This is because the People feed is always at the root of the application and therefore does not need Route information. The “Person” (as we saw above) is generated from the URL #/person/UNID.

Once the person and UNID is opened as a URL the controller then creates another ajax call to the DDS document service corresponding to the UNID in the route map. The JSON string downloaded is then turned into an object and $scope.people turned into a scoped object.

var peopleControllers = angular.module('peopleControllers', []);

peopleControllers.controller('PeopleListCtrl', ['$scope', '$http',
    function ($scope, $http) {
        $http.get('api/data/collections/name/byFirstName5Col?open&').success(function(data) { //DDS View feed
            $scope.people = data;

peopleControllers.controller('PersonDetailCtrl', ['$scope', '$routeParams', '$http',
    function($scope, $routeParams, $http) {
        $http.get('api/data/documents/unid/' + $routeParams.docId).success(function(data) { //DDS Document Feed
            $scope.person = data;

 The result

We have created a simple demonstration of how we can using routing to create two different pages within an application





In this article we have seen the beginnings of a real application. The application is taking shape in an easy to manage , separated, set of code modules which reflects out MVC approach to the data, the HTML and the control thereof.

  • app.js
    • Provides the architecture for the application by defining the routing and the module dependencies
    • The routing map defines:
      • the URL to look for
      • the template to use to display the data
      • the controller to use to manage that data
  • controller.js
    • Contains the controllers which determine what happens based on the definitions as described in the route map
  • templates
    • Simple HTML templates which are used to display the bound data on the screen for the user


In the next article we will look at expanding this display application into a full CRUD capable application.
Fixed the routing code for a person thanks to comment from Angelo

Angular.js in XPages #4 – Using Domino Data

In this article I will demonstrate how to use Angular.js to access Domino Data and display it in place of the hard coded data we used in the previous article.


This article is based off the Angular developer tutorial on XHR and dependency injection . It is essential you go and read that article before continuing. It explains how dependency injection works, why it is used and about Angular services.

Modifying the controller

In our previous article we used a hard coded json string of data directly within the controller. In a real application though, the data will be dynamic and need to be pulled directly from the data repository as and when needed.

Injecting the $http service

When the controller is constructed we are able to pass to it an unspecified number of services which the controller code are dependent on. In a sense it is just like creating your XPage, needing to use jQuery and making sure the jQuery library is loaded on the page before you use a $(‘selector’). The jQuery library is a dependency of the XPage code running the plugin.

The previous controller looked like this

 * Created by mroden on 4/25/2014.

var personApp = angular.module('personApp', []);

personApp.controller('PersonCtrl', function ($scope) {
    $scope.people = [
            "firstname":"Agnes C.",
            "address":"24256 Leisure Lane"
            "address":"3525 Koontz Lane, second"
            "address":"3044 Simpson Avenue"

And out new controller looks like this

 * Created by mroden on 4/25/2014.

var personApp = angular.module('personApp', []);

personApp.controller('PeopleCtrl', function ($scope, $http) {
         .success(function(data) {
            $scope.people = data;

So you can see that the $http service has been added as a service added to the instantiation of the personApp controller PeopleCtrl. The code has also obviously got much shorter because he are not hard coding the data in here.


In Domino (R9) we have a number of methods available to us to create JSON data securely:

  • LotusScript/Java agent
  • Form with dataType set
  • XAgent (SSJS and/or Java)
  • ExtLib REST service (out of the box and custom REST)
  • Domino Data Services

For the sake of these examples I am going to use Domino Data Services (DDS). For more information on what they are and how to set them up securely in your database check out these links:

In the example above we used the Domino Data Services the URL for provides the following;

      "address":"2519 Custer Street",
      "city":"ROCKWOOD (SOMERSET)",

And as you can see from this the firstname, lastname and address fields are supplied in the REST JSON feed.

Note on testing

If you try and load the code up in the Webstorm debugger you will get the following CORS problem – nothing works…..


When your Browser is in the “localhost” domain it cannot pull ajax requests from the domain – it is a security feature of the browser and server to prevent cross site scripting. Read more about CORS here.

This is why (as we saw in the previous article) we integrated the development environment with the Domino ODS. Because I am working on a sync’d version of the database – all I have to do is go over to the index.html within the Domino database on my server and it works (because we are in the correct domain)




What we have seen in this article is a simple evolution of the controller to allow it to pull real data from the server rather than using hard coded Data. In the next article we will look at further CRUD operations.


WebSockets comes to XPages!! Awesome – Awesome – Awesome

In this article I will introduce and discuss the OpenNTF WebSockets OSGI plugin by Mark Ambler. The websocket plugin posted to Mark Ambler is based on the project.

I realize I am not the first person to play with a websocket server on top of a domino database. I know of at least 4 other people who have at least done their own POC for this. But this is the first time I have got my hands on it 🙂 I’m a little excited……

Brief overview of WebSockets
I already wrote about the reasons for websockets in XPages. But to recap, WebSockets allows me the developer to PUSH messages out to logged in users without them asking for it. This reduces network traffic and creates a new paradigm for fluid data update.

What I will be doing over the next few blog posts is demonstrate examples of why WebSockets fundamentally changes how we architect our web apps in the future. Yes it is that significant in my mind.

How does the Domino integration work?

  • Download the OpenNTF project, unzip and follow the instructions in the PDF file.
  • Make sure you sign the update site before deploying
  • Install the update site in Domino Designer and restart
  • Understand that this is not port 80….this is demonstration right now and not production ready.

Once you have the plugin installed and up and running application you will be able to use the chat.nsf example to demonstrate for yourself what is going on. This is only intended to be an example of what is possible, not a production app. replacing sametime 🙂

From a Domino perspective, The core of the application is the websocket.nsf database. In there if you look at the Broadcast Example Agent you will see how it works.


This article was written based on code release 1.0.7

By creating a document in the database with the appropriate fields, the websocket OSGI code picks up the document from the view and routes it out to the users.

The websocket.nsf database tracks users in the vUsers view and from there we can ascertain the unique code assigned to each logged in user.

Using that websocketID listed in the view we are able to ensure that the appropriate message is transmitted to the appropriate user.

To extend this into something I can use I create an SSJS function which will look up the socketID – in this case I do not need to know the ID – just the username.

function createMessage(socketID, msgObj){

	var db:NotesDatabase = session.getDatabase("","websocket.nsf",false);
	var doc:NotesDocument = db.createDocument()
	var online = ""
	if (!socketID){
		var view = db.getView("vUsers")
		println("rec: "+msgObj.recipient)
		var user:NotesViewEntry = view.getEntryByKey(msgObj.recipient)
		if (user){
			socketID = user.getColumnValues()[1]
			online =  user.getColumnValues()[2]

	if (socketID && (online=="ONLINE")){
		doc.replaceItemValue("Form", "fmSocketMessage")
		doc.replaceItemValue("from", msgObj.from)
		doc.replaceItemValue("to", socketID)
		doc.replaceItemValue("sentFlag", 0)

		//The msgObj will come in as an Array of Json - we need to convert that into a multi-value notes field
		//This is planned to be fixed to apss the JSON straight through in a later release

		for (var i=0; i<; i++){
			var temp =[i]
			for (key in temp){
				if (i<1){
					var item:NotesItem = doc.replaceItemValue("data", key+"="+temp[key])
				} else {
		doc.replaceItemValue("text", "")
	} else {
		println("WebSocket messgae not sent")

I can then call this function from an XPages button (or anywhere else for that matter) and pass in the data as an object.

	<xp:button value="Send Message" id="button1">
		<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
						var msgObj = {}
						msgObj.recipient = context.getUser().getDistinguishedName()
						msgObj.from = "Marky demo" = [{"beer": "english"}, {"lager": "french"}]
						createMessage("", msgObj)

Looking at the chat example below which I have also slightly modified to console.dir the output so that it is readable we can see the message is sent to the client



In a more advanced demonstration I added a field to the right hand form so that i can pass through my own JSON – the video below shows this best 🙂

This was recorded at 1920 resolution so it appears blurred on youtube – CHANGE THE QUALITY to 720HD and that will fix it

OpenNTF WebSockets OSGI plugin

Mark Ambler


Please bare in mind this is not production ready code – and it may never be……

Because there are two servers running (domino and websocket) the websockets are current running over a non-standard port. I believe IHS can be used as a proxy to fix that.

As a proof of concept though and to get the information out to the community and IBM (hint hint) this is a great step forward in capabilities.

This is a great example of using and OSGI plugin and the Java code in Domino to access a Java server and transmit data – very cool !


There is no polling of the server by the client. There are zero unnecessary http calls

We can have any application with an appropriate update, review the users who are logged in and update their web page with the appropriate data.

THIS IS AWESOME!!!!!!!!!!!!


Next – more examples

I feel a series coming on…….:)



You know a project is moving along when this blog post was already out of date by the time I am ready to post it – but rather than waste it because there is good information in here – here’s the disclaimer.

This post is based on v1.0.7 of the webshell code release – as of v1.0.8 the JSON data is being passed via rich text which allows for pre-formatting server-side before sending it out. This also allows for more complex JSON creation than multi-value text fields can handle – thanks Mark – more about that soon !!

This change was based on my request for a feature on the github site

If you want to see this improve and progress – get involve – play with the code and make requests for changes – like everything OpenNTF – the more you play with it and the more people are involved – the better it will become !


Angular.js in XPages #3 – The first app

In this article I will recreate one of the simple developer tutorials and relate it to how we would build an equivalent XPages application.

Create the database

As described in the previous article, set up a Domino database with an ODS. Then link your Webstorm to the new ODS structure.

Download the Angular code from it will be an 8M download. Opening the zip file you will see a lot of “angular files”. These are “Dependency files” and are separate functional areas of Angular which can be included within your application as necessary. Although programatically not the same, the concept is similar to the way we use Dojo in XPages.


For the moment we are going to add the following to our database. for the sake of clarity I have added them in an angularjs folder in the webContent folder of my database. I have also added bootstrap (to keep Mark Leusink happy).


I then move over to my Webstorm and add the project



CDN alternate

You absolutely do not have to download the files and have them reside locally – you can link to the angular code through the Google CDN site. This is entirely up to you.

Quick MVC

MVC stands for “Model View Controller” and unlike traditional Domino development, forces good coding practices and separates UI components from application logic. This methodology has been brought closer to reality in XPages development using good Java coding practices but is still rather elusive for those using SSJS.

  • The Model is the data created by the application
  • The View is the HTML representation of the data through the use of Templates
  • The Controller is  the application code which is used to determine the way the Model is populated and displayed.


Our first Angular app

I am going to recreate the Mustache.js article I wrote earlier this year and show how to use a front end Angular repeat to create a basic page of data.

I am not going to repeat everything which you can read for yourself in the Angular tutorial. The tutorials are great and frankly better written than I can. So with the assumption that you at least understand the terms I will attempt to explain what I am doing.

Create Our HTML File 

I then create a new angular HTML file in Webstorm by right clicking on the WebContent and creating new HTML file. (You will notice that you can actually create your own Template so that in the future you can have a preset angular shell set up and ready to go if you like !)



The View and Template

Within the HTML file we are going to create a simple DIV and manually attach a controller to it using an angular directive. The ng-controller in this case tells the Angular code to “Do your stuff Here”. Kinda like an <xp:panel> where we control a section of the functionality without affecting the rest of the XPage.


You will also notice that I added ng-app =”personApp” to the HTML tag at the top of the page. That associates the application with this whole page. More on that later.

Within the PersonCtrl we are going to add our template code (once again akin to what we did in the mustache example except there we used a <script> tag to contain our template HTML)


So there is a little to talk about here.

The Repeat control

The Angular “repeat control” is an element (any HTML element) with the ng-repeat in it. In this case we are looking to repeat all Person within People (coming from the controller in a minute). Seems simple enough to read and understand

        <div ng-repeat="person in people">

 The data binding

                    {{person.firstname}} {{person.lastname}}

The data-binding using the same {{ STUFF }} notation as the mustache example. Double Curly Brackets denotes the template placeholder for *DATA HERE*

The Model and Controller

We are going to create a new directory and within it the controller.js file. That file contains the information necessary to create the page we are looking for.

The controller contains two parts

  1. The code which runs Angular in the first place
  2. The controller code which says put this data *there*


 * Created by mroden on 4/25/2014.

var personApp = angular.module('personApp', []);

personApp.controller('PersonCtrl', function ($scope) {
    $scope.people = [
            "firstname":"Agnes C.",
            "address":"24256 Leisure Lane"
            "address":"3525 Koontz Lane, second"
            "address":"3044 Simpson Avenue"

Creating an angular module

The angular.module code in the example above makes the HTML tag with ng-app=”personApp” (The HTML tag) into the module we are creating. In more complex applications there are going to be multiple modules in multiple areas of the page but in this case nice and simple just the one.

The controller binds to PersonCtrl (our DIV ng-controller=”PersonCtrl”) and then returns the $scope.people data to it as a function.

$scope is a special name for the angular scope within the defined module. It contains all the information abou tthe module but does not have access to anything outside of it. In this case we are injecting the data into the application at load time so that it can be displayed.

The results

Loading the application up in the debugger we can see the result we are looking for


Back in Domino ?

I went back over to DDE and it has refreshed automagically – There are my new files on the ODS and synced to the server database


I opened the index.html on my server and with ZERO additional effort I now have my simple app in a secure database requiring login – nice eh 🙂



In this short article I have recreated one of the simple examples from the Angular.js developer tutorials and tried to relate it to the XPages development environment. In this case we have static JSON which is a nice example to start with, but not really application-worthy. in the next article we will look at getting real data from the application.



Yeah I noticed in review that “Hello My is Mark Roden” is not the most correct English I have ever used in an example – but the code still works 🙂

jQuery Promises – Taking action .when() multiple ajax calls are complete

jQuery.when() – “Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events”

Wouldn’t it be nice to be able to know when two ajax calls are both complete – and then process the data returned from both, at the same time ?


Off the top of my head I would normally achieve this using a callback – and I have done so in some of my ExtJS in XPages articles. The following code snippet creates a callback that once the $.ajax call the url is complete take the data and pass it to the createGrid function. In there I could make another ajax call and then when that is complete I could process both data sets.

function getGridData(url, callback){

	var dataURL = url
		  url: dataURL+"&rand="+aRandomNumber()+"&"
		}).done(function ( data ) {
			//one the data is loaded then pass it to the callback function
			//passed in as one of the function arguments

var url = location.href
url = (url.indexOf('5Col')>-1) ? '5Col' : ""

url = 'xRestService.xsp/byFirstNameFlat'+url+'?count=100000'
var theData
var theCallBack = function(theData){
getGridData(url, theCallBack)

This kinda makes an synchronous linear usage of asynchronous calls – do this then do that

As inelegant as that it, it works. The problems really start when you have 3, 4 or more callbacks and you get a callback hell of what if something fails in the middle and you have to run back up the tree looking for a fail – not pretty.

Using jQuery.when()

Modern browsers support the Promises concept which natively allows us to do the same thing but we know what that means and not the browsers we code for today.

.when() gives us the ability to pass as many ajax calls as we like, and then create one line of code which processes all the answers together.


Here is a simple REST service feed from an XPages URL (fakenames data)

      "address":"2139 Jail Drivedd",

Which is nice ‘n all, but if we then wanted to know what State FL was (remember this is an example)

At the same time as pulling in the person you could also pull a list of US states

    "AL": "Alabama",
    "AK": "Alaska",
    "AS": "American Samoa",
    "AZ": "Arizona",
    "AR": "Arkansas",
    "CA": "California",
    "CO": "Colorado",
    "CT": "Connecticut",
    "DE": "Delaware",
    "DC": "District Of Columbia",
    "FM": "Federated States Of Micronesia",
    "FL": "Florida",
    "GA": "Georgia",

From that we can then do a cross reference and display something useful.

The $.when returns each element as an Array of data so we have to get the zeroth value of each object returned to access it’s key/pairs

        url: "xRestService.xsp/byFirstNameFlat?count=5",
        dataType: "json"
        url: "states.json",
        dataType: "json"
).done(function( people, states ) {
    for (var i=0; i < people[0].length; i++){
        var person = people[0][i]
        var temp = ""
        temp=temp+person.firstname+" "
        temp=temp+person.lastname+" lives in "


As you can see from the screenshots below – the ajax calls can be executed in either order and still the result stands because the .done() function is not run until both are complete







This is a very interesting capability which has apparently been around for ages and I wish I had known about it earlier (added jQuery v1.5)


Angular.js in XPages #2 – Setting up a Webstorm / Domino development environment

In this article I will show how to set up Domino in a flexible manner to store our Angular applications, while still being able to use Webstorm as our development platform.

Creating an Angular App in a Domino database

All credit for this idea goes to my boss Andrew Barickman – he came up with the idea and it is genius 🙂

Ultimately we want to create an application which resides within the security model of a Domino database with as little, if any, effort as possible. In a large part this means access control. We are going to look at creating an HTML file within the WebContent area, within a Domino database – but that causes pain and suffering if I want to use Webstorm. Webstorm cannot access a file which is held within the database directly.

So what we are going to do is create an On Disk project for the database as if we were going to use Source Control (which you should do anyway).


Once located on the disk we can then point Webstorm at the On Disk Project


In Domino Designer make sure the following is set

  • Build Automatically
  • Auto refresh set to be on
  • AutoSync Source Control



Starting with this page


We add “Marky” to Webstorm and SAVE


Note: If you do not have refresh automatically turned on and you actually try and open the file through DDE you will see an error – if you see this, turn on refresh automatically 🙂



If you see the error – Hit F9 – which cause the refresh / sync to occur


And it is pushed up to your database copy


With auto refresh and sync turned on it may take a few seconds to make it up to your dev server. It then properly resides within Domino’s access security model.

This method also works from DDE back to Webstorm – if you make a change in DDE it will automatically update the Webstorm file if it is open.

Other approaches

I know Mark Barton has a different Domino/Webstorm setup and he has blogged about that setup himself….. 🙂

In the mean time in the next article we will look at creating our first very basic app.

Mastering XPages v2 – Review

In this article I will put forth my case as to why you need to buy Mastering XPages v2 – even if you already own v1


Maybe this book should be entitled – “So you think your an XPages expert? You are not a Master !”

Clearly I am not a Master – and couldn’t possibly hope to be one and that is why *I* need this book…..

The writing style

Perhaps not the most obvious place to start but to me a significant one. There are many styles of writing for technical publications; from the utterly droll and boring to the comical and often patronizing. What I really enjoyed about reading the book was the almost irreverent conversational manner in which it comes across. If you have met Tony or Martin you will be able to hear their voices as they are talking to you about it in the Connect Developer lounge. A couple of examples are:

Thankfully, there are a couple of ways to compute the client ID”  


“The egregious offender is the column value computation” .

I am normally a visual learner and reading books is hard for me – but this style appeals to me. That helped a lot. Frankly 1100 pages (even skimmed in places) is a tall order for any man/woman (even Brad).

The content

Well here’s the thing – I have owned the v1 book for about 2 days longer than I have been programming XPages and I admit I never read the whole thing. As I am reading through the v2 book I am finding lots of cool stuff I didn’t know and I sneak a peak over at the v1 book and “OOOO there is it” – Like the XSP style guide for example – “oo I didn’t know that” !

There were too many “ooo I didn’t know that” moments for my ego but at the same time that also reflects what a massive, flexible tool which has been created. All credit to the boys for have the staying power to write this book. If you are a novice, expert or XPages god there is something to learn from this book.

There is no way anyone could possibly remember every property of every control and what it does. Simply put, if you use XPages and even if you own Mastering XPages v1, this book is an excellent reference manual and will help you learn more.

The new stuff

So the new book has been updated from 8.5.2 to 9.0.1 and with it that means a significant amount of new XPages capabilities. Another 400 pages give or take !

  • Updating everything for 9.0.1
    • New controls
    • New control properties
  • Chapter 14 XPages Mobile Application Development
    • This is really Paul DN’s realm and I am not really a fan of Dojo Mobile but this whole section does a great job of explaining all the ins and out of what you can do to build an XPages mobile app very easily.
    • There is even a shout out for which is fantastic
  • Chapter 15 XPages Unplugged and Debugged
    • An entire section on how to debug your applications using various client side and server side techniques
    • Using the Java Debugger
    • Using Firebug Lite
    • Using the SSJ Debugger
  • Chapter 20 Advanced Performance Techniques
    • XPages Toolbox setup and explanation of how to use it effectively
  • And these performance things popping up all over the book
    • Single Copy XPage Design (SCXD)
    • PreLoad
    • XPages RunOnServer
    • Runtime optimized JavaScript and CSS
  • And much, much more – I am still learning !


What can I say other than “wow”. Being a community blogger/contributor, an R9 certified developer, and  so called “expert” is nothing compared to being an XPages *Master*. There is no way you can possibly hope to remember everything this book has to offer – which immediately makes it a very valuable reference book.

What you can hope to achieve is an understanding of what is possible, so that down the line when you are trying to address problems with your application you can at least know there are options and where you can find them.

If you are a serious XPages developer you cannot really be without this book.