Building a Live Search function with Domino Access Services & jQuery Tokeninput

Introduction

A live search is a function where get search results returned while you type. In this post I will describe how you can add such a feature using the jQuery Tokeninput plugin and perform a full text search with Domino Access Services (DAS)as your data provider.

But first let’s take a look at how the end result looks like:

searchres

As in other examples we will apply the live search to the infamous fakenames application. You can grab this directory application from Codestore.

Step 1 – Download the resources and add them to your XPage

Add the following resources to a custom control:

Step 2 – Add the filter and search field

In the result screenshot you noticed the following elements:

  • A filter (People, Groups, Holidays)
  • A search field (with typeahead).
  • A (formatted) result list.

Therefor add the following HTML on your custom control:

As you notice we will be using Bootstrap to make the appearance a bit more attractive.

search_param

The hidden input element search_param will be the container for our selected filter.  The following code will set the value:

livesearch-input

The Tokeninput plugin will inject an (initially empty) unordered result list and input field in the livesearch-input input element.

Include a function to initiate the Tokeninput plugin:

Step 3 – Activate the plugin

In order to activate the Tokeninput plugin you need to include a script block control on your custom control and ad the following script:

function getObjType() {
var val = $(“#search_param”).val();
return val;
}

var restPrefix = “./api/data/collections/name/”
var hintText = “Enter text, use asterix(*) for Fulltext wildcards”;
var searchingText = “Searching…”;
var queryParam = “search”;
var propertyToSearch = “name”;
var searchDelay = 2000;
var minChars = 2;
var resultsLimit = 5; //not working??
var noResultsText = “No matches”;
var tokenLimit = 1;
var preventDuplicates = true;
var onAdd = function(item) {
var objType = getObjType();
var docID = item[‘@unid’];
var URL = “”;
switch (objType) {
case “Groups”:
URL = “Group.xsp?unid=” + docID;
break;
case “Holidays”:
URL = “Holiday.xsp?unid=” + docID;
break;
default:
URL = “Person.xsp?unid=” + docID;
}
window.location = URL;
};
var resultsFormatter = function(item) {
var objType = getObjType();
switch (objType) {
case “Group”:
sub = “dummy group sub”;
break;
case “Holiday”:
sub = “dummy Holiday sub”;
break;
default:
sub = “” + item.job;
}
if (sub == “”) {
sub = “No title available“;
}
if (sub.length > 30) sub = sub.substring(0, 30) + “…”;
var name = item.name;
if (name.length > 30) name = name.substring(0, 30) + “…”;
var img = ‘‘;
var text = ‘

‘ + name + ‘

‘ + sub + ‘


return ‘

  • ‘ + ‘
    ‘ + img + text + ‘

‘;
};

function setPlaceHolder() {
$(“#token-input-livesearch-input”).attr(“placeholder”, “Enter text”);
}

function init() {
$(“#livesearch-input”).tokenInput(restPrefix + getObjType(), {
hintText: hintText,
searchingText: searchingText,
queryParam: queryParam,
propertyToSearch: propertyToSearch,
searchDelay: searchDelay,
minChars: minChars,
resultsLimit: resultsLimit,
noResultsText: noResultsText,
tokenLimit: tokenLimit,
onAdd: onAdd,
preventDuplicates: preventDuplicates,
resultsFormatter: resultsFormatter
});
setPlaceHolder();
}

function re_init(objType) {
$(“#token-input-livesearch-input”).remove();
$(“.token-input-list”).remove();
$(“#livesearch-input”).tokenInput(restPrefix + getObjType(), {
hintText: hintText,
searchingText: searchingText,
queryParam: queryParam,
propertyToSearch: propertyToSearch,
searchDelay: searchDelay,
minChars: minChars,
resultsLimit: resultsLimit,
noResultsText: noResultsText,
tokenLimit: tokenLimit,
onAdd: onAdd,
preventDuplicates: preventDuplicates,
resultsFormatter: resultsFormatter
});
setPlaceHolder();
}

function submitSearch() {
//not required, overwritten by onAdd function.
}

As you see the Tokeinput plugin can be steered via diverse parameters. A clear description of the available options you can read here.

In our case we determine:

Data Provider

var restPrefix = “./api/data/collections/name/”

This defines we will be using Domino Access Services as data provider. We will use the names of the views People, Groups, Holidays which correspond with the options in our filter list.

Remember you need to enable DAS for the database AND the views.

onAdd = function(item)

The variable onAdd is a function that will be called when we select an item from the result list. We have set to allow only one item to be selected and the onAdd function will open a new window location with the corresponding object/document.

URL = “Person.xsp?unid=” + docID;
}
window.location = URL;

resultsFormatter = function(item)

This variable defines how the items in the collection provided by Domino Access Services will be presented in the UI. You can be as creative with it as you like. I choose for the option for an image, distinguished name and a sub-title e.g. the function for a person.

Ready

Are we done already? Yes we are! All the heavy lifting is done by the Tokeninput plugin and Domino Access Services.

Notice that Full Text search in Domino Access Services supports the usage of an asterix. Otherwise you get only results returned with an exact match.

firebugUse a plugin for your browser to check the URL call and response to DAS.

Some final thoughts

Do I found the live search feature useful?

– Yeah. Especially in mobile web applications you want to avoid that a user has to go back to home navigation each and every time. And with large data-sets infinite scrolling is a good option.

Do I find the Tokeninput plugin valuable?

– I find the plugin easy to understand but I noticed not all properties are implemented :-/  An example of something what I expected to be implemented is the number of returned results (fixed to max 10 items).

Can I recommend the plugin?

– In case you use Bootstrap applying a bootstrap theme meant mostly stripping the default style sheet. In case you use the Application Layout control from the Extension Library you will discover some problems:

  • When applying the live search in the search bar you get overlay with the Utility links.
  • When applying a filter option you get scrolling issues when opening the dropdown menu in the navbar section.
  • The result list can not be displayed direct under the search field due to the overlay of the navbar and it’s margins.

The consequence of these problems above made me decide to place the search bar in the main column section instead of the search bar and align it to the right via the pull-right class in Bootstrap. The result looks similar but I loose some white space. (perhaps the solution lies in understanding Bootstrap a bit more).

In case you can live with these conditions or restrictions I think you have an awesome desired feature!

Your turn!

What are your thoughts? In case you have provide a live search feature via some other plugin or custom code I am happy to hear from you.

I have uploaded a sample on Dropbox. I am happy to hear the improvements you have made.

Happy coding =)

Domino Access Service – Posting a document

Introduction

In our app described in previous posts I have included an interface to post a new document using Domino Access Services. Here is how I did it.

HTML structure

The main part of the ‘form’ is the HTML structure.

form

Since we use Bootstrap I included form-groups to layout the fields. In my example I still use XPages, but the inputfields you can replace with HTML input elements.

<form role=”form”>

<h2>Registration</h2>
<div id=”entryForm”>
<div class=”form-group”>
<xp:label value=”Firstname:” id=”label2″></xp:label>
<xp:inputText id=”inpFirstname” styleClass=”firstName”>
<xp:this.attrs>
<xp:attr name=”placeholder” value=”Enter firstname”></xp:attr>
</xp:this.attrs>
</xp:inputText>
</div>
<div class=”form-group”>
<xp:label value=”Lastname:” id=”label3″></xp:label>

<xp:inputText id=”inpLastname” styleClass=”lastName”>
<xp:this.attrs>
<xp:attr name=”placeholder” value=”Enter lastname”></xp:attr>
</xp:this.attrs>
</xp:inputText>
</div>
<div class=”form-group”>
<xp:label value=”Email:” id=”label1″></xp:label>
<xp:inputText id=”inpEmail” styleClass=”email” type=”email”>
<xp:this.attrs>
<xp:attr name=”placeholder” value=”Enter email”></xp:attr>
</xp:this.attrs>
</xp:inputText>
</div>
<div class=”form-group”>
<xp:label value=”Password:” id=”label4″></xp:label>
<xp:inputText id=”inpPassword” type=”” styleClass=”wannebepassword”>
<xp:this.attrs>
<xp:attr name=”placeholder” value=”Enter password”></xp:attr>
</xp:this.attrs>
</xp:inputText>
</div>
<xp:button value=”Submit” id=”button1″ styleClass=”btnRegister btn btn-default”>
</xp:button>
</div>
</form>
<div id=”formResponse”></div>

JavaScript

On the onClientLoad event of the XPage I have included the following script.

Parameters

Keep in mind to include the form name in the URL parameter (?form=Person) since you cannot include that pair in the JSON object that is send to Domino Access Service.

The &computewithform=true parameter ensures the HTTPPassword field will be translated with the @Password function.

<xp:eventHandler event=”onClientLoad” submit=”false”>
<xp:this.script>
<![CDATA[$(document).ready(function() {
$(“.wannebepassword”).attr(“type”, “password”);
$(“.btnRegister”).click(function(e) {
var firstName = $(“.firstName”).val();
var lastName = $(“.lastName”).val();
var email = $(“.email”).val();
var password = $(“.wannebepassword”).val();
var newName = $(“.firstName”).val() + ” ” + $(“.lastName”).val();
if (newName !== ” “) {
var newPersonObj = {
FirstName: firstName,
LastName: lastName,
FullName: newName,
InternetAddress: email,
HTTPPassword: password,
Form: “Person”
};
$.ajax({
url: ‘http://server/fakenames40k.nsf/api/data/documents?form=Person&computewithform=true&#8217;,
type: ‘POST’,
data: JSON.stringify(newPersonObj),
dataType: ‘xml’,
accepts: {
xml: ‘text/xml’,
text: ‘text/plain’
},
contentType: “application/json”
}).done(function(data, textStatus, jqXHR) {
var newPersonLocation = jqXHR.getResponseHeader(“Location”);
$(“#entryForm”).hide();
$(“#formResponse”).html(‘Registration successfull.<br/><a class=”btn btn-default” href=”‘ + newPersonLocation + ‘”>Check it out!</a>’);
}).fail(function(jqXHR, textStatus, errorThrown) {
alert(“Registration has failed:” + errorThrown);
console.log(‘Registration has failed.’);
});
} else {
alert(“Please enter a name”);
}
return false;
});
});]]>
</xp:this.script>
</xp:eventHandler>

Password attribute

XPages does not allow you to define the password type as attribute. I found the answer from Tim Tripcony on Stackoverflow helpful to define a work-around.

Wrap up

That is about it!

This is just a simple example. Probably you would normally include some more validation (avoid double entries) but you get the idea.

Adding search to our app

Introduction

In this post I will demonstrate how to apply a search to our application described in previous posts, in which we read the JSON from Domino Access Service and parse it with the Jackson library to display it thereafter with a Repeat control.

Since I am using in our application the Application Layout control with a responsive Bootstrap UI (shameless pitch to glory the persons who delivered this great functionality to XPages) which includes a searchBar section and scrolling through 40K of person records is not something you want to anguish your thumb with, a search functionality gives the advantage to skip scrolling through unnecessary documents.

searchBar section

Here is how I set up the searchBar section in the Application Layout control:

searchbar

As you can see I have defined a result page for the search, called search.xsp.

Here is how the search bar will look like for the user:

searchbox

XPage search

beforePageLoad event

The beforePageLoad event has slightly changed compared with event in our initial XPage. This time we search for the search parameter defined in the searchBar section:

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

Next is the result list. I have added an additional list item and added the active class:

This item will display the search query we have provided:

acgtive

The Repeat control has also changed slighty. The rows property has been adjusted. We calculate the number of documents returned by the full text search.

<![CDATA[#{javascript:database.updateFTIndex(true);
var query:string = context.getUrlParameter(“search”);
var vw:NotesView = database.getView(“people”);
var totNums:Integer = vw.FTSearch(query).toFixed();
return totNums}]]>

Also the text that indicates where we are in the result set has been modified:

Tip: since we are actually performing FT searches here perhaps it is better to put the counted results already from our initial call in a scope variable and re-use it =(

The button in our infinite scroll function/custom control has also been adapted. Here is the code:

Here is what the end result looks like after a search:

endresult

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!

Angular playground: applying an infinitescroll for Domino Access Services

Intro

Inspired by a serie of blogposts from Marky Roden and popularity in the web dev community I decided to step (once again) from the XPages path (a trend?) and feed my curiosity on AngularJS. An excellent stepping stone is the example database provided by Marky and the AngularJS Fundamentals In 60-ish Minutes presentation by Dan Wahlin.

Domino Access Services

The example database contains examples for CRUD operations via Domino Access Services (way to go!) but is limited in the display of documents from a list/view. The default number of documents returned for view/folder entries is set to 10. Not much of value for a real world application. So either I had to provide some sort of pagination or look for more smartphone/tablet common feature: infinite scroll.

The pagination examples I found only handled a one time loaded data-set and a proper application can contain thousands of records (I find it a common pattern that customers underestimate the number of documents that are being created in a Notes application). Angular can be extended with custom directives and ngInfiniteScroll is such a great example.

So what did I need to do to get this directive applied to the example database?

Implementation

index.html

First I installed the ng-infinite-scroll.min script in my database and updated the header section in index.html

I also needed to include the complete jQuery library because Angular has jQuery lite built in which doesn’t have seem to have the features for dynamic height. You should also load the jQuery script before jQlite.

app.js

Next you need to register the infinitescroll directive for my angular application:

var personApp = angular.module(‘personApp’, [
‘ngRoute’,
‘peopleControllers’,
‘infinite-scroll’
]);

factory

The examples for ngInfiniteScroll demonstrate a factory which is not used in the example database. From my limited knowledge on Angular I have understood that a factory is an injectable function.

personApp.factory(‘DAS’, function($http) {

var DAS = function() {
this.items = [];
this.busy = false;
this.after = 0;
this.count = 30;
this.order = ‘firstname’;

};

DAS.prototype.nextPage = function() {
if (this.busy) return;
this.busy = true;
var url = ‘//dev1/apps/others/angular/ainx.nsf/api/data/collections/name/byFirstName5Col?open’ + ‘&count=’ + this.count + ‘&start=’ + this.after;

$http.get(url).success(function(data) {
var items = data;
for (var i = 0; i < items.length; i++) {
this.items.push(items[i]);
}
this.after = this.after + this.count;
this.busy = false;
}.bind(this));
};
return DAS;
});

My factory is called DAS (I guess I break here the naming convention). You can store your factories in a separate (new) file e.g. main.js.

Controller.js

I removed the existing PeopleListCtrl controller and replaced it with the following one:

personApp.controller(‘PeopleListCtrl’, function($scope, DAS) {
$scope.DAS = new DAS();
});

partial-list.html

With everything in place I now needed to update the display of the list, which is defined in a partial. Besides using the factory I also wanted to add some additional features such as search and sorting. This turned out to be really simple.

At the end I wanted to have something as followed:

Screenshot_3

 

Search & Sorting

For a search feature I added an input control and used the directive ngModel called query to apply a filtering via a search query

Search:

<input ng-model=”query” placeholder=”Search for person” autofocus class=”input-medium search-query”/>

For a sorting feature I added a select control and bound that to the order property for the data via a custom directive. Further I added a radio-button group and used the directive ngModel called direction:

Sorting:
<select ng-model="DAS.order“>
First Name
Last Name
ZIP

<input type="radio" ng-model=”direction” name=”direction” checked> ascending

<input type="radio" ng-model=”direction” name=”direction” value=”reverse”> descending

Note that the default sorting is set to ‘firstname’ in the factory.

Apply infinitescroll to the data-table

Finally we need to apply the nextPage function in the DAS function by wrapping the data-table with a div and add the infinite-scroll attribute to it.

<div infinite-scroll=”DAS.nextPage()” infinite-scroll-distance=”3″>

Then we use the ng-repeat directive and for each item or person in the data we display a new row. Here is also were we apply the filter and sorting options:

…<tr ng-repeat=”person in DAS.items | filter:query | orderBy:DAS.order:direction”>

Position First Name Last Name Zip
{{person[“@position”]}} {{person.firstname}} {{person.lastname}} {{person.zip}} Edit Delete

<div ng-show=’DAS.busy’>Loading data…