Infinite scroll for our Domino Access Services & Jackson app

Introduction

In a previous post I demonstrated how to process JSON data from Domino Access Services with the Jackson library. In this post I follow up on that and demonstrate how you can apply an infinite scroll function to it so you can navigate through the presented list in a mobile app kinda way:

app

Class DASRest

Include Serializable

Since we are going to add more Java objects to our list we will use a scope variable to store the values in. Because of the usage we need to include serializable in our DASRest class:

package com.quintessens.FakeNames;

import java.io.Serializable;
import javax.ws.rs.core.MediaType;
import org.codehaus.jackson.map.ObjectMapper;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;

public class DASRest implements Serializable {

private static final long serialVersionUID = 1 L;

public static Person[] getPersons(String url) {
Person[] persons = null;
try {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(url);
String json = service.accept(MediaType.APPLICATION_JSON).get(String.class);
ObjectMapper mapper = new ObjectMapper();
persons = mapper.readValue(json, Person[].class);

} catch (Exception e) {
System.out.println(“catch…”);
e.printStackTrace();
}
return persons;
}
}

Managed Bean

We will also register our class as a Managed Bean in the faces-config.xml file:

<?xml version=”1.0″ encoding=”UTF-8″?>
<faces-config>
<managed-bean>
<managed-bean-name>personsBean</managed-bean-name>
<managed-bean-class>com.quintessens.FakeNames.DASRest</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>

beforePageLoad event

In the beforePageLoad event of the XPage where we will display the result list we will make the initial call to the Domino Access Service and collect the fist set of documents:

var persons = new Array();
persons = personsBean.getPersons(“http://dev1/fakenames40k.nsf/api/data/collections/name/people?count=25&#8221;)
viewScope.put(“names”,persons);

As you can see we still use the fakenames application. The received result we place in a viewScope variable. Now we need to bind this variable to a repeat control.

Repeat Control

Our repeat control looks pretty straight forward. I included already some mark-up since we will utilize Bootstrap for our UI presentation:

<ul class=”list-group”>
<xp:repeat id=”rptPersons” indexVar=”persIndex” var=”persData”>
<xp:this.value>
<![CDATA[#{javascript:viewScope.get(“names”)}]]>
</xp:this.value>
<xp:this.rows>
<![CDATA[#{javascript:var vw:NotesView = database.getView(“people”);
return vw.getAllEntries().getCount().toFixed();}]]>
</xp:this.rows>
<li class=”list-group-item”>
<xp:text escape=”true” id=”computedField1″ value=”#{javascript:return persData.name}”>
</xp:text>

</li>
</xp:repeat>
</ul>

Infinite Scroll

My infinite scroll function is inspired by the excellent snippet from Frank Kranenburg and available on OpenNTF.

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

<!– make sure ‘add rows’ component is hidden –>
<style>
.infiniteScroll {
display: none;
}
</style>

<xp:button value=”Label” id=”button2″ styleClass=”infiniteScroll”>
<xp:eventHandler event=”onclick” submit=”true” refreshMode=”partial” refreshId=”#{javascript:compositeData.repeatId}”>
<xp:this.action>
<![CDATA[#{javascript:var repeater = compositeData.repeatControlId;

var counter = getComponent(repeater).getRowCount();
var numb = compositeData.increment;
var url = compositeData.baseURL;

var scope = compositeData.scopeVarName

var currPersons = viewScope.get(scope);
var morePersons = personsBeanTest.getPersons(url + counter + “&count=” + numb);
var allPersons = currPersons.concat(morePersons);

viewScope.put(scope,allPersons);

}]]>
</xp:this.action>
</xp:eventHandler>
</xp:button>

<!– small script to check if we need to auto-click the ‘add rows’ button –>
<xp:scriptBlock id=”scriptBlock1″>
<xp:this.value>
<![CDATA[$(window).scroll(function(){
if($(window).scrollTop() == $(document).height() – $(window).height()) {
$(“.infiniteScroll”).click();
}
});]]>
</xp:this.value>
</xp:scriptBlock>
</xp:view>

Under the event handler of the button we collect the current array with objects from the viewscope, we make a new call to Domino Access Services with our current position and we join the received next set of objects with the current set and then we write things back to the viewscope.

We then partial refresh the design element (a panel) that contains both the repeat control and the infinite scroll.

I defined some properties for the custom control in case I might re-use it for other views…

infscroll

That is basically it! On my Xpage I included some text to indicate where we are in the view and that there are more documents available:

<xp:text escape=”true” id=”computedField2″><xp:this.value><![CDATA[#{javascript:var vw:NotesView = database.getView(“people”);
var currNums:Integer = getComponent(“rptPersons”).getRowCount();
var totNums:Integer = vw.getAllEntries().getCount().toFixed();

return “Displaying rows ” + currNums + ” out of ” + totNums;
}]]></xp:this.value></xp:text>

Processing JSON data from Domino Access Services with Jackson

Introduction

In order to separate the data model from the business logic you could use Domino Access Services as your default data provider and process the JSON client- or server-side.

In this blog I demonstrate how you can use the Jackson library to process the incoming JSON and bind it to a repeat control.

About Jackson

Jackson is a multi-purpose Java library for processing JSON data format. Jackson aims to be the best possible combination of fast, correct, lightweight, and ergonomic for developers.

Find out more about the library here.

Sample – Fakenames application

In this example we will use the infamous fakenames application from codestore (grab it while it still out there). I have altered the data in my example a bit more so it contains company and job title information.

If you happen to have a script who can fill in the database with more sensible data please drop a line here.

People view

We will use the people view as our data-source  and access it via Domino Access Service. The  URI for the view is something as followed (depending on your installation):

http://server1/fakenames.nsf/api/data/collections/name/people

XPage

We will use and XPage to display the data from the view. The xpage contains a repeater and a couple of fields e.g.:

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

<xp:panel>
<xp:repeat id=”rptPersons” rows=”10″ var=”persons”
value=”#{javascript:com.quintessens.FakeNames.DASRest.getPersons(‘http://server1/fakenames.nsf/api/data/collections/name/people?count=100&#8217;)}”>
<xp:panel id=”personsPanel”>
<h1>
<xp:text escape=”true” id=”computedField1″
value=”#{javascript:persons.getName()}”
>
</xp:text>
</h1>
<h3>
<xp:text escape=”true” id=”computedField2″
value=”#{javascript:persons.getCompanyname()}”
>
</xp:text>
</h3>
<xp:text escape=”true” id=”computedField3″
value=”#{javascript:persons.getJobtitle()}”
style=”font-style:italic”>
</xp:text>

</xp:panel>
</xp:repeat>
<xp:pager layout=”Previous Group Next”
partialRefresh=”true” id=”pager1″ for=”rptPersons”>
</xp:pager>
</xp:panel>
</xp:view>

Note I call in the repeater control a method getPersons in the class DASRest and provide an URL of the data-source.

I add the count parameter to get more initial results than set in the server configuration.

I also have added a pager control to navigate through the returned collection.

DASRest class

The DASRest class looks as followed:

package com.quintessens.FakeNames;

import javax.ws.rs.core.MediaType;
import org.codehaus.jackson.map.ObjectMapper;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;

import com.quintessens.FakeNames.Person;

public class DASRest {

public static Person[] getPersons(String url) {
Person[] persons = null;
try {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(url);
String json = service.accept(MediaType.APPLICATION_JSON).get(String.class);
ObjectMapper mapper = new ObjectMapper();
persons = mapper.readValue(json, Person[].class);
} catch (Exception e) {
e.printStackTrace();
}
return persons;
}
}

Jersey

This class uses the Jersey library to setup a client to contact the Domino Access Service.

Developing RESTful Web services that seamlessly support exposing your data in a variety of representation media types and abstract away the low-level details of the client-server communication is not an easy task without a good toolkit. In order to simplify development of RESTful Web services and their clients in Java, a standard and portable JAX-RS API has been designed.

The received JSON is then converted into a Java object. I find this a good site to learn more about mapping JSON and Java.

Person class

The class also uses a Person class. In this class we specify which fields we want to map and how they should look like:

package com.quintessens.FakeNames;

import javax.xml.bind.annotation.XmlRootElement;

import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;

@XmlRootElement
@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
private String name;//$17
private String companyName;
private String jobtitle;

@JsonProperty(“$17”)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@JsonProperty(“CompanyName”)
public String getCompanyname() {
return companyName;
}
public void setCompanyname(String companyname) {
this.companyName = companyname;
}
public String getJobtitle() {
return jobtitle;
}
public void setJobtitle(String jobtitle) {
this.jobtitle = jobtitle;
}
}

As you can see @JsonProperty(“$17”) makes the code a bit more user-friendly. In this case it maps the programmatic column name with a variable name.

The result

The following image shows the result of the code above:

jackson

As you see the markup is still basic but you can easily beautify it with Bootstrap or preferred CSS framework of choice.

The purpose of this blog was to demonstrate separating the data model and the business logic. You could have received the same result just using a repeat control and server-side javascript. But since the JSON & Java combination is more hype in the Domino world I guess it is interesting to take a look at the possibilities.

Wrap up

Some thoughts I have after this experiment:

  • What about the other documents?

In my example I make a call to DAS and include the count = 100 parameter. The number 100 is the limit that is set on my server. I have not figured out yet how to load a next set of documents and include it to the existing collection (please drop a note in case you have an answer to this).

  • What about performance?

Is Domino Access Services faster than e.g. defining my own data provider e.g. via a viewnavigator? I don’t know. In case you have suggestions on the preferred or fastest way to provide the data then let me know.

  • What about the language?

Java is not my native language in the Domino world. But the example above is understandable for a lot of us I would think. But who died out of a bit of curiosity?

Thanks for reading. Happy coding!