Modernizing a Notes application

Introduction

The quickest way of modernizing your application build on Notes is probably by keeping it on that sublime application platform and provide a new user-interface and updated business logic to it.

As a demonstratable example I would like to bring up my own Bildr project on OpenNTF.

A brief history

The application started initially as a web browser display only and create content via the Notes client application in the early 2000’s. From that the application was updated in a create content via the web browser too features.

Later the oneUI was implemented in a later step also utilizing the Application Layout control. With the Bootstrap4XPages plugin a responsive UI could be delivered a couple of years ago.

Nowadays the Application Layout control is no longer used but the Bootstrap responsive features and components are used further through the application.

The application still relies on XPages and Notes data (allthough used mainly in JSON format). The business logic resides mainly in Java Classes. With the separation of design and data the application could be hosted on Bluemix (not tested, please do).

Modernization

Looking back at this application story I believe the application will change also in the future (or die) perhaps a challenge could be to have the data optional in a different source (e.g. MongoDB) or exchange XPages for Angular and run it on Node.js.

So if you have experience moving data from Notes to MongoDb, applying a similar ACL and Roles security model on your application I would be happy to hear your experiences.

Or if you are still on the Notes client only level and want to bring your application to a (mobile) browser I am happy to exchange ideas with you and learn from your situation.

New release

Why am I saying this? Today I uploaded a new version on OpenNTF and for many Notes developers who a) not have taken the XPages path yet b) unfamiliar with Java and JSON the application could be a great starter example.

Lately there are not that many (new, updated) projects (applications) available at OpenNTF so the chance to find a working demo to see and understand the code and data running are a bit scarce (I am sorry).

I remember in the days of Superhuman Software a quote about Notes and collaboration was “dare to share” so I would challenge more developers to do so. We can learn a lot from examples from others and I thank those people who (still) do and from which I can learn from!

I wish you a wonderful learning experience at IBMConnect 2016!

Capture04

 

Bildr 5 released on OpenNTF

Announcement

Today I released a new version of Bildr on OpenNTF ! There have been spent quiet a few long evenings coding but it was fun (I may drink at home) and interesting!

So just before the holiday season and January’s  IBM Connect buzz I thought it would be a proper moment for a release. So consider it my Xmas present to the community:-)

Modernization

I label this version 5.0 since I (almost) completely rewrote the application from scratch. Lot of the logic I have placed in Java classes and the data format is mainly JSON.

I also separated the design from the data so in principle you should be able to place the app in the IBM Bluemix environment.

Because of these decisions there is no backward compatibility so a simple replace design will not work for your existing installation.

Below you can find some screenshots of the new interface. I hope you like it.

Screenshots

startpage

uploadprofilepicture

Thank you

I would like to thank all the teachers in our community who post their presentations, code samples and answers on the platforms out there.

I have tried to post the code on Github but this failed with the Github desktop client. If some can get me started with that I will distribute the project there too.

For now:

A Merry Christmas and fortune in 2016 !

Quick way to get dynamic (BS) list groups from Notes views

Introduction

For an app I needed lists to let users quickly to have access to a subset of documents. Bootstrap has a nice feature called List Groups which gives you a nice basic UI for unordered lists with list-items.

A list group can contain badges which I find a nice alternative for a tag cloud which to me are not very mobile friendly.

Before we get started let’s show how the end-result could look like:

list-group computed

So how do we do this in XPages & Java?

First I want to point to a snippet Oliver Busse posted about Iterating through a HashMap in a repeat control. If you use that basic principle of binding a HashMap to a Repeat Control  and use a categorized view for filling the Hashmap you are there!

Java class snippet

Below is the snippet from my Java class:

public Set<Entry<String, Integer>> categoriesMap(String viewName) throws NotesException{
HashMap<String, Integer> categories = new HashMap<String, Integer>();
Database dataDB = dao.getDatabase();
String ViewName = viewName;
ViewNavigator nav = dataDB.getView(viewName).createViewNav();
ViewEntry entry = nav.getFirst();
while (entry != null && !entry.isTotal()){
categories.put(entry.getColumnValues().firstElement().toString(), entry.getChildCount());
ViewEntry tmpentry = nav.getNextSibling(entry);
entry.recycle();
entry = tmpentry;
}
nav.recycle();
return categories.entrySet();
}

The code will run through the provided Notes view and go through the categories and places the name/label and value/totals in a Map and returns a set view of the mappings contained in this map.

Custom Control

Next step is to place the list-group code from Bootstrap in a custom control and make it dynamic by generating the list-items via a Repeat control and bind that control to my Java code:

<?xml version=”1.0″ encoding=”UTF-8″?>
<xp:view xmlns:xp=”http://www.ibm.com/xsp/core”&gt;
<h2>
<xp:text
escape=”true”
id=”computedField1″
value=”#{javascript:compositeData.header}”>
</xp:text></h2>
<ul class=”list-group”>
<xp:repeat
id=”repeat1″
rows=”30″
var=”obj”
indexVar=”idx”>
<xp:this.value><![CDATA[#{javascript:var viewName = compositeData.viewName;
Picture.categoriesMap(viewName);}]]></xp:this.value>
<li class=”list-group-item”>
<span class=”badge”>
<xp:text
escape=”true”
id=”computedField2″
value=”#{javascript:obj.getValue()}”>
</xp:text>
</span>
<xp:link
escape=”true”
text=”#{javascript:obj.getKey()}”
id=”link1″>
<xp:eventHandler
event=”onclick”
submit=”true”
refreshMode=”complete”>
<xp:this.action>
<xp:openPage>
<xp:this.name><![CDATA[#{javascript:var target = compositeData.targetPage;
return target + “?filter=” + obj.getKey();}]]></xp:this.name>
</xp:openPage>
</xp:this.action></xp:eventHandler>
</xp:link>
</li>
</xp:repeat>
</ul>
</xp:view>

Properties

The custom controls takes in some properties set in the Property Definition section. Via this way I can re-use the Custom control for multiple dynamic list groups:

Conclusion

As you see, with a minimal amount of code you can create dynamic lists for your apps. In the sample above I do not handle views with more than 30 categories but I am sure you fix this yourself. Also the active class property is not implemented.

Happy development =)

 

Managed properties

For an application we aggregate the data from several servers and databases. In SSJS applications I tend to save such properties in Notes configuration documents and store them in scoped Variables during initialization. Something like was provided in the XPages Framework a while ago.

In episode 182 of Notes in 9 David Leedy gives a great demonstration how to work with Notes documents via Managed Beans. However for properties that should not be altered likely by an application administrator (could be the application owner, a regular Notes user) I tend to work with managed properties. With managed properties you more or less configure your managed bean.

So how could this look like?

In the faces-config.xml I set the properties for a managed bean e.g.

<?xml version=”1.0″ encoding=”UTF-8″?>
<faces-config>
<managed-bean>
<managed-bean-name>dataBean</managed-bean-name>
<managed-bean-class>com.wordpress.quintessens.ConfigBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>

<managed-property>
<property-name>dataSource</property-name>
<value>dev1.quintessens.com</value>
<property-class>java.lang.String</property-class>
</managed-property>

</managed-bean>
</faces-config>

which I can use then in my java code:

package com.wordpress.quintessens;
import java.io.Serializable;
public class ConfigBean implements Serializable {
private String dataSource;
//… more variable declarations

public JsonJavaObject loadBackEndConfig(String Key) throws NotesException {
JsonJavaObject json = null;
NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();
String DatabaseName = session.getCurrentDatabase().getFilePath();
String ViewName = “(LookUpBackEndConfig)”;
try {
json = loadJSONObject(this.dataSource, DatabaseName, ViewName, Key, 1);
} catch (NotesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return json;
}

public void setDataSource(String dataSource{
this.dataSource = dataSource
}
public String getDataSource() {
return dataSource
}

}

You can check if the value is passed correctly e.g. via Expression Language:

<xp:text escape=”true” id=”computedField1″
value=”#{ConfigBean.dataSource}”>
</xp:text>

I am curious how you prefer to configure your applications? In some cases I find it perhaps an overkill to store the configuration in Notes documents and publish the values via a managed bean.

Happy development =)

 

prettytime for your XPages

Introduction

I guess most of us believe that not all users are robots, and especially in social applications where you try to approach humans as human friendly as possible the display of date and time in such format can be off added value.

Timeago

There are many scripts to display date and time in a human friendly format. For a project we are using timeago, a jQuery plugin that makes it easy to support automatically updating fuzzy timestamps (e.g. “4 minutes ago” or “about 1 day ago”).

However, in combination with an infinite scroll feature to navigate through document collections we notice that the script not always return instantly the transformed date/time.

That is why I looked at a server-side transformation.

PrettyTime

PrettyTime is an OpenSource time formatting library. Completely customizable, it creates human readable, relative timestamps like those seen on Digg, Twitter, and Facebook.

It turned out the implementation of PrettyTime was quiet simple. I will describe the steps for you.

Install the jar & call the Java class

Download the project jar file and place it in the WebContent \ WEB-INF \ lib folder.  For a computed field on a custom control in a repeat control I have as SSJS code to call the Java class:

<xp:text>
<xp:this.value><![CDATA[#{javascript:var theDate = compositeData.Date;
p = new org.ocpsoft.pretty.time.PrettyTime();
p.format(new Date(theDate))}]]></xp:this.value>
</xp:text>

As a result I get to date displayed as:

prettytime

Pro’s & Con’s

In comparison with client-side solutions I have detect some con’s and pro’s for a server side solution:

  • Static; the client side solutions are capably of updating the date/time value on the fly.
  • Speed; the server side generated human friendly date is there when a document row is presented.
  • Extensibility; the number of display languages is more numerous for most client-side solutions. Also you can often “configure” the text format when desired.
  • Consistency; having a human friendly date in a local language may be in conflict with internationalization of your XPages app since that might require more work.

Anyway: I happy to hear how you have solved the human friendly date issue.

Happy development!

new release XPages OpenLog Logger and custom runtime error page

Introduction

Good news for me! In an XPage application I wanted to provide an error logging function and preferably to OpenLog. I had problems with implementing the previous version of OpenLog logger but yesterday a new version is released on OpenNTF which took away that pain.

OpenLog logger

The OpenLog logger has some powerful features:

  • It can be used in your managed beans and other Java classes.
  • It can be used directly from SSJS with as little as openLogBean.addError(e,this);.
  • From SSJS all caught errors on the page are logged out together, at the end of the request.
  • Only two method names are used from SSJS, one to add an error, one to add an event, making it easy to pick up
  • From SSJS you only need to pass the information you wish. There is no need to pass nulls or empty strings.
  • From SSJS only one unique error per component is logged, regardless of how many times the error is encountered during the refresh lifecycle.
  • In SSJS, if you use a custom error page, uncaught errors will also be logged.
  • Uncaught errors will be logged for the page the error occurs on, not your custom error page.
  • You can define the OpenLog path and a variety of other variables without needing to change the code.
  • The functionality and code are available as an OSGi plugin (the best practice approach) but can also be included in individual NSFs.

Custom Runtime Error Page

In case you want to implement such functionality then I recommend you also take a look at how to define a custom run time error page. In my project I am using Bootstrap for UI look & feel, so I followed the steps Erick McCormick provides in this blog post.

code-prettify

In Erick’s post you will find that he uses Google’s code-prettify project. A nice feature is to ability to set a skin for the error message box. There are plenty of color themes available for the prettify project but if you use Bootstrap like I do you could try this theme.

A big thank you to the OpenLog logger project members and Erick!

custom_error_page

 

Working with JSON in your XPages application (3) – getEntriesByKey

Introduction

In our third example we will extend the loadPictures method in the first blog entry in this serie to detect whether or not we want to load just all the entries in a view or entries that match a key in the first sorted column.

Use case

In the following simple scenario a user makes an selection (e.g. from a combobox) and corresponding picture documents will be displayed, as in the image:

sample3json

Whenever a different value is selected the list with corresponding documents will be updated.

So how do we do this?

Switch between Notes views

The main difference is the key send if we do or don’t (empty value) want a filtering of the result list. To collect this key we provide a combobox:

</div>

The loadCategories method provides an ArrayList of String values:

public ArrayList<String> loadCategories(String ViewName) throws NotesException{
System.out.println(appRef + “.loadCategories()”);

NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();

String ServerName = “dev1”;
String DatabaseName = session.getCurrentDatabase().getFilePath();
Database DB = session.getDatabase(ServerName, DatabaseName);

View luView = DB.getView(ViewName);
ViewNavigator vwnav = null;
ViewEntry vwentry = null;
ViewEntry vwentrytmp = null;

// all results will be added to this
ArrayList<String> categories = new ArrayList<String>();

// disable autoupdate
luView.setAutoUpdate(false);

vwnav = luView.createViewNav();

// setting buffer for fast view retrieval
vwnav.setBufferMaxEntries(400);

// perform lookup
vwentry = vwnav.getFirst();
while (vwentry != null){

if(!categories.contains(vwentry.getColumnValues().elementAt(0).toString()))
categories.add(vwentry.getColumnValues().elementAt(0).toString());
// Get entry and go recycle
vwentrytmp = vwnav.getNext(vwentry);
vwentry.recycle();
vwentry = vwentrytmp;
}
luView.setAutoUpdate(true);

return categories;
}

The xp:Combobox control is bound to a viewScope variable and it’s onChange event triggers a partial refresh on a panel:

<xp:panel id=”pictures”>
<xp:pager layout=”Previous Group Next” partialRefresh=”true” id=”pager1″ for=”repeat1″></xp:pager>
<table class=”table table-hover”>
<thead>
<tr>
<th>Thumb</th>
<th>Subject</th>
<th>Category</th>
<th>Description</th>
<th>Author</th>
</tr>
</thead>
<xp:repeat id=”repeat1″ rows=”15″ var=”pix”>

<xp:this.value><![CDATA[#{javascript:var active = viewScope.get(“activeCategory”);
if (active == “All”){
PictureProvider.loadPictures(“”);
}
else{
PictureProvider.loadPictures(active);
}}]]></xp:this.value>
<tr>
<td>
<xp:text escape=”false”>
<xp:this.value><![CDATA[#{javascript:/*
getAttachmentURL and getBaseURL from:
http://www.wissel.net/blog/d6plinks/SHWL-86QKNM
*/

function getAttachmentURL(docID:java.lang.String, attachmentName:java.lang.String) {
var base = getBaseURL();
var middle = “/xsp/.ibmmodres/domino/OpenAttachment”;
if (base.substr(0,4) == “/xsp”) {
middle += base.substr(4);
} else {
middle += base;
}
var result = base + middle + “/” + docID + “/$File/” + attachmentName + “?Open”;
return result;
}

function getBaseURL() {
var curURL = context.getUrl();
var curAdr = curURL.getAddress();
var rel = curURL.getSiteRelativeAddress(context);
var step1 = curAdr.substr(0,curAdr.indexOf(rel));

// Now cut off the http
var step2 = step1.substr(step1.indexOf(“//”)+2);
var result = step2.substr(step2.indexOf(“/”));
return result;
}
var thumb = pix.imgthumb;
var id = pix.docUNID;

return “<img src='” + getAttachmentURL(id, thumb) + “‘>”;
}]]></xp:this.value>
</xp:text>
</td>
<td><xp:text escape=”true” value=”#{pix.subject}”></xp:text></td>
<td><xp:text escape=”true” value=”#{pix.category}”></xp:text></td>
<td><xp:text escape=”true” value=”#{pix.descr}”></xp:text></td>
<td><xp:text escape=”true” value=”#{pix.author}”></xp:text>
</td>
</tr><!– /.row –>
</xp:repeat>

</table>
</xp:panel>

Depending on the value in the viewScope we call the loadPictures method empty or with a key.

private ArrayList<JsonJavaObject> loadJSONObjects(String ServerName, String DatabaseName, String ViewName, String Key, Integer ColIdx) throws NotesException {
ArrayList<JsonJavaObject> JSONObjects = new ArrayList<JsonJavaObject>();

NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();
Database DB = session.getDatabase(ServerName, DatabaseName);

if (!(DB==null)) {
View luView = DB.getView(ViewName);

if (!(luView == null)) {
JsonJavaFactory factory = JsonJavaFactory.instanceEx;

ViewEntryCollection vec = luView.getAllEntriesByKey(Key, true);

ViewEntry entry = vec.getFirstEntry();
while (entry != null) {

Vector<?> columnValues = entry.getColumnValues();
String colJson = String.valueOf(columnValues.get(ColIdx));
JsonJavaObject json = null;

try {
json = (JsonJavaObject) JsonParser.fromJson(factory, colJson);
if (json != null) {
JSONObjects.add(json);
}

} catch (JsonException e) {
// TODO:
}

ViewEntry tempEntry = entry;
entry = vec.getNextEntry();
tempEntry.recycle();
}
luView.recycle();
}
DB.recycle();
}
return JSONObjects;
}

Similar as in the previous examples the JsonJavaObject class from the com.ibm.commons.util.io.json package is used to return an arraylist of json objects.

In the next post I will describe how we can manipulate the JSON objects.

Working with JSON in your XPages application (2) – getEntryByKey

Introduction

In the previous post we started with the first steps to modernize our xpages application by defining our Notes view data in JSON format, create objects from them in Java with help from  com.ibm.commons.util.io.json and return them to the xpage (repeat control).

In our example we used the AllEntries method which is well known across the languages Domino supports (LotusScript/JavaScript/Java).

getEntryByKey

As Domino also supports other methods to work with Notes view data we have to extend our Java class with them. Next in line lies the getEntryByKey method.

Changes in setup

Removed Content Delivery Network

In the previous post we used CDN for loading Bootstrap. Since we work with XPages and the latest and greatest Extension Library at our service we simple apply the Bootstrap theme for our application.

New Notes view

In our *NonSQL* database we will add a new view with the first column categorized. I have chosen the following document properties:

varUNID:=@Text(@DocumentUniqueID);
varAuthor:=Au_Author;
varCategory:= Photo_Category;
varCreated:=@Text(@Date(@Created));
@Return(Photo_Title:varUNID:varAuthor:varCategory:varCreated)

I also extended the second column compared with the first view. The column formula is as followed:

REM {This column builds a JSON string};

varUNID := @Text(@DocumentUniqueID);
varCategory:= Photo_Category;
varTitle := Photo_Title;
varAuthor:=Au_Author;
varDesc:=PhotoDescription;
varCreated:=@Created;
varThumb:=Photo_ThumbFilename;
varSmall:=Photo_SmallFilename;

jsonOpener := “{“;
jsonClosure := “}”;
jsonSeparator := “\”,”;
jsonLastItem := “\””;

@Return(
jsonOpener +
“\”docUNID\”: \”” + varUNID + jsonSeparator +
“\”author\”: \”” + @Name([CN]; varAuthor) + jsonSeparator +
“\”category\”: \”” + varCategory + jsonSeparator +
“\”subject\”: \”” + varTitle + jsonSeparator +
“\”descr\”: \”” + varDesc + jsonSeparator +
“\”created\”: \”” + @Text(@Date(varCreated)) + jsonSeparator +
“\”imgthumb\”: \”” + varThumb + jsonSeparator +
“\”imgsmall\”: \”” + varSmall + jsonLastItem +
jsonClosure
)

Notice I added an additional key:pair for a smaller (preview) image. I also find the @Return function a good best practice to explicit indicate something is being returned.

With this new view available we will update our Java class.

Java class additions

Our Java class will have two new functions:

  • loadPicture(Key)
  • loadJSONObject

The first indicates we will send along a unique identifier and the second indicates we will return a single JSON object.

public JsonJavaObject loadPicture(String Key) throws NotesException {
JsonJavaObject json = null;

NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();

String ServerName = “dev1”;
String DatabaseName = session.getCurrentDatabase().getFilePath();
String ViewName = “$v-pixJSONCategories”;

try {
json = loadJSONObject(ServerName, DatabaseName, ViewName, Key, 1);
} catch (NotesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return json;
}

and

private JsonJavaObject loadJSONObject(String ServerName, String DatabaseName, String ViewName, String Key, Integer ColIdx) throws NotesException {

JsonJavaObject json = null;

NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();
Database DB = session.getDatabase(ServerName, DatabaseName);

if (!(DB==null)) {

if (!(DB.isOpen())) {
DB.open();
}

View luView = DB.getView(ViewName);
if (!(luView == null)) {

ViewEntry entry = luView.getEntryByKey(Key);
if (!(entry == null)) {

Vector<?> columnValues = entry.getColumnValues();
String colJson = String.valueOf(columnValues.get(ColIdx));

try {
JsonJavaFactory factory = JsonJavaFactory.instanceEx;
json = (JsonJavaObject) JsonParser.fromJson(factory, colJson);
} catch (JsonException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

entry.recycle();
}
luView.recycle();
}
DB.recycle();
}
return json;
}

Probably during development (and acceptance) you would like include a logging mechanism. I have left the System.out.println statements out of the examples…

A new XPage

Since we will call the loadPicture method with a key we need to provide a mechanism to capture this key. In our first example we capture the key from the unid parameter that we attach to the URL e.g. page2.xsp?unid=1234. Notice that the unid has to respond to a category value set in the category column in the Notes view. So my proposal is to use categories that are sufficient distinguished from each other.

In my xpage I drag in a panel and define a dataContext for it:

<xp:this.dataContexts>
<xp:dataContext var=”data”>
<xp:this.value><![CDATA[#{javascript:var paramUNID = paramValues.get(“unid”);
strUNID = paramUNID.toString();
PictureProvider.loadPicture(strUNID);}]]></xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>

Notice I am using the paramValues global object available in XPages to capture the key. With this key I call the loadPicture method.

Within the panel I define a Bootstrap form:

https://www.dropbox.com/s/gxfofuu6ujec2qt/bootstrap%20form.txt?dl=0

(for some reasons WordPress want to transform my text into HTML)

Data is the variable I have defined for the dataContext. Hereby I can easily access the JSON object properties and collect a value by accessing a key e.g. data.docUNID.

With these new additions our result looks and followed:

isn't she lovely?

Working with JSON in your XPages application

Introduction

In order to develop more inline with leading web standards more and more Domino and XPages developers find themselves attracted to use JSON as the standard to describe and transport their application data.

There are many excellent JSON libraries out there. With XPages we have immediately access to com.ibm.commons.util.io.json.

I intend to write a series of posts and tell how I will rewrite an existing XPages application and introduce Java and JSON as the new working horses in this application.

JSON and XPages

More people have written about JSON in XPages so I do not have to deep dive in that. I especially like Jeff Byrd’s post on using the JSONJavaObject class to create a JSON object from an array.

A simple first example

First let me demonstrate a simple example. We setup a simple construction we will extend in the following steps of our application modernization.

Building blocks

Here is what our construction will contain:

  • a Notes view with a first column for sorting and a second column containing a JSON string
  • a Java class defined as a managed bean in the faces-config file
  • an XPage to display the received ArrayList with JSONObjects

Notes view

Our view looks nothing spectaculair:

notesview

Basically:

  • The first column is used for sorting purpose.
  • The second column constructs the JSON string.

The formula for the second column can be as followed:

REM {This column builds a JSON string};

varUNID := @Text(@DocumentUniqueID);
varCategory:= Photo_Category;
varTitle := Photo_Title;
varAuthor:=Au_Author;
varDesc:=PhotoDescription;
varCreated:=@Created;
varThumb:=Photo_ThumbFilename;

jsonOpener := “{“;
jsonClosure := “}”;
jsonSeparator := “\”,”;
jsonLastItem := “\””;

jsonOpener +
“\”docUNID\”: \”” + varUNID + jsonSeparator +
“\”author\”: \”” + @Name([CN]; varAuthor) + jsonSeparator +
“\”category\”: \”” + varCategory + jsonSeparator +
“\”subject\”: \”” + varTitle + jsonSeparator +
“\”descr\”: \”” + varDesc + jsonSeparator +
“\”created\”: \”” + @Text(@Date(varCreated)) + jsonSeparator +
“\”thumb\”: \”” + varThumb + jsonLastItem +
jsonClosure

Alternatively you can use a normal view (plain column values) and construct the JSON string later, something described here.

JAVA class

Our class has one public method called loadPictures which will call a private method that will return an arraylist of jsonjavaobjects. You notice the com.ibm.commons.util.io.json library will be used.

Don’t worry about the hard-coded server and notes view references now. This will be polished later.

package com.quintessens;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Vector;

import lotus.domino.NotesException;

import lotus.domino.Database;
import lotus.domino.Session;
import lotus.domino.View;
import lotus.domino.ViewEntry;

import lotus.domino.ViewEntryCollection;

import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.domino.xsp.module.nsf.NotesContext;

public class SimplePictures implements Serializable{
public static final long serialVersionUID = 1L;
public SimplePictures(){
}

public ArrayList<JsonJavaObject> loadPictures() throws NotesException{
ArrayList<JsonJavaObject> PictureCollection = new ArrayList<JsonJavaObject>();

NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();

String ServerName = “dev1”;
String DatabaseName = session.getCurrentDatabase().getFilePath();
String ViewName = “$v-pixJSONSingle”;
String Key = “”;
Integer ColIdx = 1; //0 means first column

try {
PictureCollection = loadJSONObjects(ServerName, DatabaseName, ViewName, Key, ColIdx);

} catch (NotesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return PictureCollection;
}

private ArrayList<JsonJavaObject> loadJSONObjects(String ServerName, String DatabaseName, String ViewName, String Key, Integer ColIdx) throws NotesException {

ArrayList<JsonJavaObject> JSONObjects = new ArrayList<JsonJavaObject>();

NotesContext nct = NotesContext.getCurrent();
Session session = nct.getCurrentSession();
Database DB = session.getDatabase(ServerName, DatabaseName);

if (!(DB==null)) {

View luView = DB.getView(ViewName);

if (!(luView == null)) {

JsonJavaFactory factory = JsonJavaFactory.instanceEx;
ViewEntryCollection vec = luView.getAllEntries();

ViewEntry entry = vec.getFirstEntry();
while (entry != null) {

Vector<?> columnValues = entry.getColumnValues();
String colJson = String.valueOf(columnValues.get(ColIdx));

JsonJavaObject json = null;

try {
json = (JsonJavaObject) JsonParser.fromJson(factory, colJson);
if (json != null) {
JSONObjects.add(json);
}

} catch (JsonException e) {
System.out.println(“ERROR: PP.loadJsonObjects 1: colJson “);
}

ViewEntry tempEntry = entry;
entry = vec.getNextEntry();
tempEntry.recycle();
}
luView.recycle();
}
DB.recycle();
}
return JSONObjects;
}

}

Managed Bean

We register the class as a managed bean so we can call it easily from our XPage:

<managed-bean>
<managed-bean-name>PictureProvider</managed-bean-name>
<managed-bean-class>com.quintessens.SimplePictures</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

XPage

And then last but not least our XPage. Here we will display the list. The XPage contains:

  • A (Bootstrap) table.
  • A Repeat control
  • A Pager control.

By calculation the values for the Repeat control on page load and using a partial refresh for the pager pagination becomes really fast.

The result will be something as followed:

xpage

<?xml version=”1.0″ encoding=”UTF-8″?>
<xp:view xmlns:xp=”http://www.ibm.com/xsp/core”&gt;

<link href=”https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css&#8221; rel=”stylesheet” integrity=”sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc= sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==” crossorigin=”anonymous” />
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js

<xp:pager layout=”Previous Group Next” partialRefresh=”true”
id=”pager1″ for=”repeat1″>
</xp:pager>

<table class=”table table-hover”>
<thead>
<tr>
<th>Thumb</th>
<th>Subject</th>
<th>Category</th>
<th>Description</th>
<th>Author</th>
</tr>
</thead>
<xp:repeat id=”repeat1″ rows=”15″
value=”${javascript:PictureProvider.loadPictures();}” var=”pix”>
<tr>
<td>
<xp:text escape=”false”>
<xp:this.value><![CDATA[#{javascript:/*
getAttachmentURL and getBaseURL from:
http://www.wissel.net/blog/d6plinks/SHWL-86QKNM
*/

function getAttachmentURL(docID:java.lang.String, attachmentName:java.lang.String) {
var base = getBaseURL();
var middle = “/xsp/.ibmmodres/domino/OpenAttachment”;
if (base.substr(0,4) == “/xsp”) {
middle += base.substr(4);
} else {
middle += base;
}
var result = base + middle + “/” + docID + “/$File/” + attachmentName + “?Open”;
return result;
}

function getBaseURL() {
var curURL = context.getUrl();
var curAdr = curURL.getAddress();
var rel = curURL.getSiteRelativeAddress(context);
var step1 = curAdr.substr(0,curAdr.indexOf(rel));

// Now cut off the http
var step2 = step1.substr(step1.indexOf(“//”)+2);
var result = step2.substr(step2.indexOf(“/”));
return result;
}
var thumb = pix.thumb;
var id = pix.docUNID;

return “<img src='” + getAttachmentURL(id, thumb) + “‘>”;
}]]></xp:this.value>
</xp:text>
</td>
<td>
<xp:text escape=”true” value=”#{pix.subject}”>
</xp:text>
</td>
<td>
<xp:text escape=”true” value=”#{pix.category}”>
</xp:text>
</td>
<td>
<xp:text escape=”true” value=”#{pix.descr}”>
</xp:text>
</td>
<td>
<xp:text escape=”true” value=”#{pix.author}”>
</xp:text>
</td>
</tr><!– /.row –>
</xp:repeat>

</table>

</xp:view>

Next step

In a next blog I will explain some other (basic) methods that the application will be using to generate collections of documents (by key or restrictbycategory) and based upon these methods we will reconstruct our current XPages app. Untill then.