JQM & Domino Data Service (2)

In a previous post I demonstrated how to use Domino Data Service (as part of Domino Access Service) to populate a listview for jQuery Mobile. Probably the real-world application you want to mobilize has multiple layers of data that is common in Notes (multiple forms & views). I followed Christophe Coenraets’s example and re-used it for the fakenames application.

First let me show you what you get:

Screenshot_1

 

Screenshot_2

 

At first an overview of the People’s view is presented, from here the user can open the Person details page. In Christophe’s example you can find a more nested navigation but our fakenames application is not setup like that.

What this demo includes:

  • 2 XPages; eg people.xsp and person.xsp.
  • Your modified version of employeelist.js and employeedetails.js in the download from Christophe page.
  • The CSS files in the download from Christophe page.
  • The jQuery JS files in the download from Christophe page.

For convenience I have dropped the files from the download via the Package Explorer in the WebContent folder of the fakenames application. In case you want to mobilize your fakenames application a step further e.g. by packaging it via PhoneGap to access device native api’s you don’t want to store these files here and you don’t want to use XPages to render the HTML.

Of course we assume you have Domino Access Service enabled for the Domino server and the fakenames application.

XPage people.xsp & person.xsp

These files do not differ from the index.html and employeedetails.html files in the download from Christophe page. In case you use XPages you should consider to turn of Dojo and Themes.

JS files employeelist.js & employeedetails.js

Since we will provide the information via Domino Data Service these JS files will be altered. Below you find their code:

employeelist.js

var serviceURL = “http://dev1//apps/fakenames.nsf/api/”;

var employees;

$(‘#employeeListPage’).bind(‘pageinit’, function(event) {
getEmployeeList();
});

function getEmployeeList() {
$.getJSON(serviceURL + ‘data/collections/name/People?count=1000’, function(data) {
$(‘#employeeList li’).remove();
employees = data;
$.each(employees, function(index, employee) {
var tmp = “”;
tmp+='<li>’;
tmp+='<a href=”person.xsp?id=’ + employee[“@unid”] + ‘”>’ +
‘<img src=”http://greenhouse.lotus.com/plugins/plugincatalog.nsf/NoPhotoPerson.gif”/>&#8217;;
tmp+='<h4>’ + employee.$17 + ‘</h4>’;
tmp+='<p>’ + employee.CompanyName + ‘</p>’;
if (employee.$12!=””) {
tmp+='<span class=”ui-li-count”>’ + employee.$12 + ‘</span>’;
}
tmp+='</a></li>’;
$(‘#employeeList’).append( tmp );
});
$(‘#employeeList’).listview(‘refresh’);
});
}

Walkthrough

At the page initiation the function getEmployeeList is called. This makes an Ajax request to the REST API of Domino Data Service and a document collection with the first 1000 entries (if available) in the view named People is being called.

I have not taken a look yet how to implement lazy loading or appending additional document collection(s) when scrolling. If you happen to know how to implement such function please drop me a line here or send me an email.

Then list items in the employee ID element on the Xpage people.xsp are removed (when available) and we loop through the results returned by Domino. We create a new list and establish a hyperlink to people.xsp and add the UNID of the document as parameter.

At last the result is appended to the (emptied) employee ID element and the listview is being refreshed. Wow! You got now a nice list of persons in the directory.

employeedetails.js

$(‘#detailsPage’).live(‘pageshow’, function(event) {
var id = getUrlVars()[“id”];
$.getJSON(serviceURL + ‘data/documents/unid/’+id, displayEmployee);
});

function displayEmployee(data) {
var employee = data;
console.log(employee);
$(‘#employeePic’).attr(‘src’, ‘http://greenhouse.lotus.com/plugins/plugincatalog.nsf/NoPhotoPerson.gif&#8217;);
$(‘#fullName’).text(employee.FirstName + ‘ ‘ + employee.LastName);
$(‘#employeeTitle’).text(employee.Title);
$(‘#city’).text(employee.OfficeCity);
console.log(employee.officePhone);
if (employee.Manager) {
$(‘#actionList’).append(‘<li><h3>Manager</h3><p>’ + employee.Manager + ‘</p></li>’);
}
if (employee.InternetAddress) {
$(‘#actionList’).append(‘<li><a href=”mailto:’ + employee.InternetAddress + ‘”><h3>Email</h3>’ +
‘<p>’ + employee.InternetAddress + ‘</p></a></li>’);
}
if (employee.OfficePhoneNumber) {
$(‘#actionList’).append(‘<li><a href=”tel:’ + employee.OfficePhoneNumber + ‘”><h3>Call Office</h3>’ +
‘<p>’ + employee.OfficePhoneNumber + ‘</p></a></li>’);
}
if (employee.CellPhoneNumber) {
$(‘#actionList’).append(‘<li><a href=”tel:’ + employee.CellPhoneNumber + ‘”><h3>Call Cell</h3>’ +
‘<p>’ + employee.CellPhoneNumber + ‘</p></a></li>’);
$(‘#actionList’).append(‘<li><a href=”sms:’ + employee.CellPhoneNumber + ‘”><h3>SMS</h3>’ +
‘<p>’ + employee.CellPhoneNumber + ‘</p></a></li>’);
}
$(‘#actionList’).listview(‘refresh’);
}

function getUrlVars() {
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf(‘?’) + 1).split(‘&’);
for(var i = 0; i < hashes.length; i++){
hash = hashes[i].split(‘=’);
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}

Walkthrough

The idea behind the details page is similar to the people’s list, except we have now a single object in the JSON.

First the ID provided by the parameter is abstracted from the URL and used in the call to a document source via the Domino Data Service.

From the result a set of details is abstracted and presented to the user. Some HTML5 attributes are used to deliver a more native behavior when clicking anchor links (telephone, mail, SMS).

Some thoughts

jQuery Mobile and Domino Access Service provide good foundations for creating mobile Web apps on Domino. However I am a bit thoughtful on the speed of Domino Access Service. If you happen to know how to speed up performance for this please drop a line here.

In a next post I will write the implementation of CRUD actions with jQuery and DDS.

 

Introduction Dojo Toolkit & IBM Lotus Domino

Currently I am collecting all sorts of information about implementing the DOJO Toolkit in Lotus Domino applications. Basically for preparing myself for a upcoming project to ‘pimp’ an existing web-application using this JS Framework.

Untill now I have been only working with Prototype and Scriptaculous (which I like a lot) but since IBM’s horizon also shines more and more Dojo I am really curious in DOJO’s capabilities. Especially since documentation seems to be getting better and better.

I have found a good presentation about implementing the Dojo Toolkit in Lotus Domino applications on Slideshare. You can find the presentation here.

Introduction Dojo Toolkit & IBM Lotus Domino - presentation

It seems that more and more Notes related people find the time and effort in uploading their presentations on this site, which is ofcourse a very good thing! (HINT)

Navigation-menu from view (XML Transformation)

In a previous writing I explained how you can create a navigation-menu that collects it’s information straight from a Notes View.

example navigation 

Using the ?ReadViewEntries URL command Notes outputs the view data in XML form which can be the source of a transformation to HTML using the XSLTProcessor in your browser.

When the project became actual again I found some time to improve it’s functionality, since it was not working 100% in Firefox. So here is an example available for downloading.

Here is short summary of the example’s features:

  • the navigator collects it’s source data from a Notes view using the ReadViewEntries command
  • when navigating through the menu for each subcategory (via the + and – icons) a new AJAX request is done to collect the information withing that (sub)category (so the amount of data is being divided into smaller parts)
  • the information is being transformed into HTML via the build XSLTransformator of the browser
  • when clicking on (sub)category a collection of responding documents is collected and presented in another frame
  • the navigator also contains document links which will load the document info in the right frame when clicked
  • documents can be grouped under whatever structure in the View

Very nice, I did not manage to solve 1 thing yet: if a (sub)category contains subcategories AND documents, the documents are being displayed FIRST. I rather would display the subcategories first and then the documents. Maybe you can help me with that one?

example of results

Scriptaculous autocompleter in Domino form

This post describes briefly an implementation of Scriptaculous’ Ajax.Autocompleter class.

It uses an AJAX request using a Page element for an @DBLookup I believe first mentioned at Lotusphere 2007 by Jack Rattcliff and later (?) by Jake Howlett in his http://www.codestore.net/store.nsf/unid/BLOG-20060221 and perfectionized (?) by my collegue Tomas.

Scriptaculous autocompleter needs an unordered list in return. For instance this list might be returned after the user typed the letter “y”:

<ul>
    <li>your mom</li>
    <li>yodel</li>
</ul>

Some examples use an agent to return the unordered list, others describe using a page.

The demo allows you to fill in multiple fields after clicking on one of the presented suggestions by splitting the responseText:

autocompleter form

In demo-mode it would look something like this:

demo autocompletion

For downloading a working example click here.

Download Pagination example

I received a couple of mails saying that the link to Bob Obringers article on his ‘ultimate view navigator’ is dead and if I could send a copy of the example.

I have uploaded a working example > here < including an example for a flat view and an example for a categorized view. In the flat view I have included Prototype’s AJAX request handler, the categorized view still uses Bob’s approach. Good luck!

page navigation example

Updated: the application had local encryption enabled…

Printing documents from a view (web)

I almost have my summer-break so in order not to forget what I have been doing lately here a summary of my activities on development: 

Recently I have been working on some projects which all had one thing in common: ‘printing documents from a web browser’.  In this article I describe you my solution how provide a funtion to print (multiple) documents from a web view, with maintaining the often used previous-next navigation in Domino views.

The function is based upon the idea of a cookie. In the cookie you write the document unique ID of the document you want to add to your print selection, and at the end, the point that the user want to actually print the selection, all documents will be collected via an AJAX request and added on a new window.

I have no idea what the performance impact are and have not tested the application well in IE, I assume there might be some considerations using different browser types and versions.

print expl 01

In the view for each document the user has the option to ‘add’ this document to his ‘selection’. The selected document will be presented in a DIV below the view. Also the icon will change from blue to a more inactive ‘purple’:

print expl 02

In the example above the user has selected 2 documents, which can be deleted in 2 ways from the selection:

– document one by one

– all documents at once

print expl 3

Att the bottom the print icon uses a JavaScript function that makes an AJAX request for each document UNID in the cookie and writes the result (HTML) to a new window:

print expl 4

Since the whole idea is based upon a normal default Domino view and it support the option to use the previous-next navigation I think this approach should be easy to implement in most applications.

A sample of the application can be found here. Since this was only one of my approaches to enable printing Notes documents from a webbrowser there is more to come soon.

Now I can really start backpacking at ease🙂

Bob’s ultimate view navigator – categorized view

I love Bob Obringer’s solution for a view navigator. Especially you can easily improve the user’s navigation experience in existing applications.

For example I received a few days ago a request to investigate the options to improve performance on a view. After a quick investigation default a view was opened with the URL parameter &Count=1000. Happy waiting! Further the application enables you to navigate through the documents by opening a categorized view with &RestrictToCategory= parameter, again followed by the &Count=1000 parameter.

Implementing Bob’s solution for the flat was was piece of cake, just copy & paste.

Unfortunately Bob’s approach only works on a flat view and some of the functionality it is is not suitable for a categorized view:

  • the cookie function
  • the function to enter a page number directly in an input field

 Therefor I rewrote the script a bit, preserving 95% of Bob’s code. Thanks Bob!

viewnav02.jpg

URL opened:

http://server.domain.com/path/database.nsf/web_internetRegionCat?OpenView&RestrictToCategory=USA&Count=15

// initViewNav function, loaded at onLoad event of form
/************************************************/
function initViewNav(range, cache, div1, div2) {
 navRange = range;
 navCache = cache;
 navDiv1 = div1;
 navDiv2 = div2;
 startDoc = queryString(‘start’);
 startDoc = (startDoc == “”) ? 1 : parseInt(startDoc);
 docsPerPage = queryString(‘count’);
 docsPerPage = (docsPerPage == “”) ? 20 : parseInt(docsPerPage);
 waitForDocCount()
}

// QueryString function
/****************************************************/
function queryString(key){
 var page = new PageQuery(window.location.search);
 return unescape(page.getValue(key.toLowerCase()));
}

function PageQuery(q) {
 if(q.length > 1) this.q = q.substring(1, q.length);
  else this.q = null;
 this.keyValuePairs = new Array();
 if(q) {
  for(var i=0; i < this.q.split(“&”).length; i++) {
   this.keyValuePairs[i] = this.q.split(“&”)[i].toLowerCase();
  }
 }
 this.getKeyValuePairs = function() { return this.keyValuePairs; }
 this.getValue = function(s) {
  for(var j=0; j < this.keyValuePairs.length; j++) {
   if(this.keyValuePairs[j].split(“=”)[0] == s)
   return this.keyValuePairs[j].split(“=”)[1];
  }
  return “”;
 }
 this.getParameters = function() {
  var a = new Array(this.getLength());
  for(var j=0; j < this.keyValuePairs.length; j++) {
   a[j] = this.keyValuePairs[j].split(“=”)[0];
  }
  return a;
 }
 this.getLength = function() { return this.keyValuePairs.length; }
}
/****************************************************/

// Cookie functions
/****************************************************/
function getExpiryDate(minutes){
 var UTCstring;
 Today = new Date();
 nomilli = Date.parse(Today);
 // Today.setTime(nomilli + (minutes * 60000));
 // Today.setTime(nomilli + (minutes * 0)); 
 // Means Cookies disabled!
 // This done because we work with a &RestrictToCategory command in a categorized view and we switch all the time from categories…
 Today.setTime(nomilli + (minutes * 0));
 UTCstring = Today.toUTCString();
 return UTCstring;
}

function setCookie(name, value, duration){
 cookiestring = name + “=” + escape(value) + “; EXPIRES=” + getExpiryDate(duration);
 document.cookie = cookiestring;
}

function getCookie(cookiename) {
 var cookiestring = “” + document.cookie;
 var index1 = cookiestring.indexOf(cookiename);
 if (index1 == -1 || cookiename == “”) return “”;
  var index2 = cookiestring.indexOf(‘;’, index1);
 if (index2 == -1) index2 = cookiestring.length;
  return unescape(cookiestring.substring(index1 + cookiename.length + 1, index2));
}
/*****************************************************/

// Get View Navigator HTML
/*****************************************************/
var startDoc = 0;
var docsPerPage = 0;
var totalPages = 0;
var navRange = 0;
var navCache = 0;
var navDiv1 = “”;
var navDiv2 = “”;
// var checkCookie = true;
// cookies has not use with a &RestrictToCategory command in a categorized view
var checkCookie = false;
var totalDocs = “”;
function waitForDocCount() {
 if (checkCookie) {
  totalDocs = getCookie(‘totaldocs’);
  checkCookie = false;
 }
 if (totalDocs == “”) {
  getDocCount();
  setTimeout(“waitForDocCount()”, 100);
 } else {
  drawViewNav();
 }
}
/*******************************************************/

// Function to get document count using XMLHTTP
/*******************************************************/
function getDocCount () {
 var xmlHttp = getXMLHTTP();
 xmlHttp.open(“GET”, dbPath + viewAlias + “?ReadViewEntries&RestrictToCategory=”+ Categories );
 xmlHttp.onreadystatechange = function() {
  if (xmlHttp.readyState == 4 && xmlHttp.responseText) {
   var resp = xmlHttp.responseText;
   var countTag = resp.toLowerCase().indexOf(‘toplevelentries’);
   if (countTag > 0) {
    resp = resp.substr(resp.indexOf(‘”‘, countTag) + 1);
    totalDocs = resp.substring(0, resp.indexOf(‘”‘));
    setCookie(‘totaldocs’, totalDocs, navCache)
   }
  }
 };
 xmlHttp.send(null);
}
/**************************************************/

// Wrapper function to get a cross browser XMLHTTP object
/**************************************************/
function getXMLHTTP() {
 // function to create an XmlHttp object
 var xmlHttp = null
 try {
  xmlHttp = new ActiveXObject(“Msxml2.XMLHTTP”)
 } catch(e) {
  try {
   xmlHttp = new ActiveXObject(“Microsoft.XMLHTTP”)
  } catch(oc) {
   xmlHttp = null
  }
 }
 if(!xmlHttp && typeof XMLHttpRequest != “undefined”) {
  xmlHttp = new XMLHttpRequest()
 }
 return xmlHttp
}
/**************************************************/
// viewnav.js file
/**************************************************/
function drawViewNav() {
 // Do all our calculations to find out where we are in the view
 partialPages = totalDocs / docsPerPage;
 extraPage = (partialPages == Math.floor(partialPages)) ? 0 : 1;
 totalPages = Math.floor(partialPages) + extraPage;
 curPage = Math.floor(startDoc / docsPerPage) + 1
 // Figure out the number of the first and last pages to display on the navigator
 startLink = (curPage < (navRange + 1)) ? 1 : curPage – navRange;
 endLink = ((curPage + navRange) > totalPages) ? totalPages : curPage + navRange;
 // Start writing our menu 
 navHTML = “<div class=’nav’ align=’center’><table class=’navtable’ cellpadding=’0′ cellspacing=’0′><tr>”;
 // Write out the “First”, “Jump”, and “Previous” links when applicable
 if (startLink > 1) {
  navHTML = navHTML + buildLink(1, “First”);
  navHTML = navHTML + buildLink(curPage – (navRange + 1), “<<“);
 } 
 if (curPage > 1) navHTML = navHTML + buildLink(curPage – 1, “<“);
 // Generate all the page # links we want to display
 for (i = startLink; i <= endLink; i++) {
  if (i == curPage) {
   navHTML = navHTML + “<td class=’navtablecur’>” + i + “</td>”
  } else {
   navHTML = navHTML + buildLink(i, i);
  }
 } 
 // Write out the “End”, “Next”, and “Jump” links when applicable
 if (curPage < totalPages) navHTML = navHTML + buildLink(curPage + 1, “>”);
 if (endLink < totalPages) {
  navHTML = navHTML + buildLink(curPage + (navRange + 1), “>>”);
  navHTML = navHTML + buildLink(totalPages, “Last”);
 }
 // Close the list of links
 navHTML = navHTML + “</td>” 
 // Write out the “Page x of y” text and create input box
 //navHTML = navHTML + “<td class=’navpages’>Page “;
 navHTML = navHTML + “<td class=’navtablelink’>Page “;
 // input field for page number is disabled because this does not work for a categorized view
 //navHTML = navHTML + “<input title=\”Click to enter a new page number here\” onKeyUp=\”void(getPage(event, this))\” “;
 //navHTML = navHTML + “onClick=’this.select()’ onFocus=’this.select()’ type=’text’ value='” + curPage + “‘ />”;
 navHTML = navHTML + curPage ;
 navHTML = navHTML + ” of ” + totalPages + “</td>”;  
 // Close out the menu
 navHTML = navHTML + “</tr></table></div>”;
 // Write out the navigator
 document.getElementById(navDiv1).innerHTML = navHTML;
 if (typeof navDiv2 != “undefined”) document.getElementById(navDiv2).innerHTML = navHTML;
  document.getElementById(‘view’).style.display = ‘block’;
}

function buildLink(pageNum, text) {
 startLinkDoc = (((pageNum – 1) * docsPerPage) + 1);
 endDoc = (pageNum == totalPages) ? totalDocs : startLinkDoc + docsPerPage  // Check for last page when creating tooltip range
 linkHTML = “<td class=’navtablelink’ onmouseover=\”this.className=’navtablelink_on’\” onmouseout=\”this.className=’navtablelink’\” ”
 linkHTML = linkHTML + “title=’Page ” + pageNum + ” : Documents ” + startLinkDoc + ” through ” + endDoc + “‘ “;
 linkHTML = linkHTML + “onclick=document.location.href='” + viewAlias + “?OpenView&RestrictToCategory=”  + Categories + “&Start=” + startLinkDoc + “&Count=” + docsPerPage + “‘>” + text + “</td>”;
 return linkHTML;
}

function getPage(event, field) {
 var keyCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
 if (keyCode == 13) {
  newPage = field.value;
  if (newPage > totalPages || newPage < 1) {
   field.select();
   return false;
  }
   document.location.href = dbPath + viewAlias + “?OpenView&RestrictToCategory=”  + Categories + “&Start=” + (((newPage – 1) * docsPerPage) + 1) + “&count=” + docsPerPage
 }
}
/************************************************************/

The variable Categories is a value I receive from one of the dialoglists on top of the screen. The screen is a frameset with 2 frames.

In the dialoglist I have a JS OnChange event opening the url in the bottom frame:

varURL = “web_internetRegionCat?OpenView&RestrictToCategory=” + varSelect + “&Count=” + varNumPage}

In the bottom frame I open a View via a ‘$$ViewTemplate for viewname’ Form. In the HTML Head section of the Form I calculate the variable Categories, via:

varCategories:=@Right(Query_String_Decoded;”OpenView&RestrictToCategory=”);
varCategories:=@If(@Contains(varCategories;”&Start”);@Left(varCategories;”&Start”);@Left(varCategories;”&Count”));

Playing with XSLT – Domino view

In a recent project I was asked to find a solution to display the entries in a view in a more advanced navigational way. Currently this functionality is being built by an agent.

The general quest is to limit the amount of using agents and try to use already available views instead. Also the agent has it’s limitations.

There were some conditions this ‘navigator’ must have:

  • documents must be displayed in a familiar way, like a menu-tree structure 
  • a document may have maximum 4 categories depth
  • not each document will have 4 categories depth
  • when clicking on a twistie (plus/minus sign) the tree must be opened
  • when clicking on a category name all the corresponding documents must be presented as thumbnails and the user must be able to navigate through these thumbnails
  • when clicking on a final document entry (the product name) the document must be presented
  • the navigator may only show documents a person is entitled to see, therefor a person may only see documents for his organisation

Nice!

The view

Since I did not had any experience in transforming a view in XML format via a XSL stylesheet I use some old articles available on the IBM website here and here.

Finally my view looked like this:

notes categorized view

Transformation

I first setup my XSL file in Eclipse, just because I just got it installed. But since the view was only allowed to show the documents a person is entitled to somehere in the XSL document capture which OU1 unit a person is listed in.

By placing the XSL on a page and add some good old <computed text> it was easy to create some automated text:

@Name([OU1];@UserName) 

Validating the code I managed by making everytime an export to a flat file, import it in Eclipse again and there do the validation😦

Finally my code looked like this:

<?xml version=“1.0” encoding=“UTF-8”?>

<xsl:stylesheet xmlns:xsl=http://www.w3.org/1999/XSL/Transform&#8221; version=“1.0” xmlns:xalan=http://xml.apache.org/xslt&#8221;>

<xsl:template match=“viewentry[@children]”>

<div>

<xsl:attribute name=“ID”>

<xsl:value-of select=“@position”/>

</xsl:attribute>

 

<span style=“cursor:arrow;” onclick=“toggleSection(this)”>

<img border=“0” src=“./navigation_plus.gif”/>

</span>

 

<span style=“font-size: 8pt;font-weight:normal;cursor:hand;color:#000000;” onclick=“collectCategory(this)”>

<xsl:attribute name=“NextEntrySrc”><Computed Value>/<Computed Value><Computed Value>&Start=

<xsl:value-of select=“@position”/>.1&Count=

<xsl:value-of select=“@children”/>&Collapse= <xsl:value-of select=“@position”/>1.1

</xsl:attribute>

<xsl:attribute name=“CatPos”>

<xsl:value-of select=“@position”/>

</xsl:attribute>

<span><xsl:value-of select=“entrydata”/></span>

</span>

 

<div style=“margin-left:15px;display:none;”/></div>

</xsl:template>

<xsl:template match=“viewentry”>

 

<table border=“0” width=“120”>

<tr bgcolor=“#FFFFFF”>

<xsl:if test=“position() mod 2 != 0”>

<xsl:attribute name=“bgcolor”>#FFFFFF</xsl:attribute>

</xsl:if>

<td width=“1%” valign=“top”>

<xsl:variable name=“unid” select=“@unid”/>

 

<span style=“font-weight:normal;color:#000000;cursor:hand;”><a href=<Computed Value>/0/{$unid}?opendocument” target=”main”>

<img border=“0” src=/<Computed Value>/dot.gif”/></a>

</span>

</td>

<span><a href=<Computed Value>/0/{$unid}?opendocument” target=”main”><xsl:apply-templates select=“entrydata”/></a></span>

</tr>

</table>

 

</xsl:template>

<xsl:template match=“entrydata[@columnnumber=1]”>

<td width=“39%”>

<span style=“font-weight:normal;color:#0060C0;cursor:hand;”><xsl:value-of select=“text”/></span>

</td>

</xsl:template></xsl:stylesheet>What this code does:

  • basically it goes through the view, looks if the entry has got children (which means it’s a category) if so it will display a + sign and the entrydata (category name).
  • the + sign got a toggle() function assigned which is described in the related IBM article
  • the entry data (the category) will get a collectCategory() function assigned which will make an AJAX call which collects all documents under the selected category

The result looks finally with some little help of a CSS file like this:

view in web

Collecting the right documents

Because I wanted to display the exact subset of documents under a certain category structure I add the value ‘this’ in the collectCategory() function:

onclick=“collectCategory(this)”What is i get in return is the object (the viewentry) I click on. Via obj.CatPosI get the position (for example 1.2.2) which also tells me how many levels / categories I am in (3) .In order to get the right subset I needed an categorized view that has a flat construction of the categories at one level, for example:organisation||Cruiser||Shadow||2006||Gray  ororganisation||Touring||Goldwing||2007||BlackFinally I could just show that view to the browser via an ?OpenView command with &RestrictToCategory parameterfunction collectCategory(obj){

varPosSelect = obj.CatPos

varPosSelectSplit = varPosSelect.split(“.”)

var varPosLevels = new Array()

var varPosCat = new Array()

for(i=0; i<varPosSelectSplit.length ; i++){

varPosLevels[i]=varPosSelectSplit[i]

}

n=0

varCat=“”

for(i=0; i<varPosLevels.length ; i++){

// for each level we are going to perform a AJAXRequest

switch (i+1) {

// check which position we want to search in te view

case 1: searchPos=eval(varPosLevels[0]); break

case 2: searchPos=eval(varPosLevels[0]) + “.” + eval(varPosLevels[1]); break

case 3: searchPos=eval(varPosLevels[0]) + “.” + eval(varPosLevels[1]) + “.” + eval(varPosLevels[2]); break

case 4: searchPos=eval(varPosLevels[0]) + “.” + eval(varPosLevels[1]) + “.” + eval(varPosLevels[2]) + “.” + eval(varPosLevels[3]); break

default: result = ‘unknown’

}

varURL= ExtDBName + “/” + XMLCategories + UserOrg + “&start=” + searchPos;createAJAXRequest (varURL,“processReturnValue”)

varPosCat[n] = varCat

n++

 

}

varPosCatString = varPosCat.join(“||”)

parent.main.location.href = ExtDBName + “/OrgProdSingleCat?OpenView&RestrictToCategory=” + UserOrg + “||” + varPosCatString + “&Count=12”

}

The parameter &Count=12 indicates that I only want to display 12 documents at a time (to avoid long loading).

In the view the documents are being displayed in <div> tags, and via CSS I display them as a pure CSS picture alike gallery:

/* settings for view display product*/
.thumbnail { float: left;
width: 250px;
height: 400px;
border: 1px solid #E1E1E1;
margin: 0 15px 15px 0;
background-color:#FFF;
}
.clearboth {
clear: both;
}

Wrapping up

Transforming normal Domino views can be very interesting and add user-experience value to your applications.If you want to go deeper into, I believe more experience in XSL is necesarry. I only have seen a top of the iceberg, but in combination with some AJAX the results can be quite amazing!

My first AJAX ‘type-ahead suggestion’ function

In a recent project I had to include a search field that would perform a full-text search on a catalog (.nsf). In order to provide a better assistance for the users to find the product they are actually looking for I had in mind to build a ‘type-ahead’ function.

If I am searching on the internet I do not find myself stupid so if I am looking for the latest CD of an artist I already know them by name, I just don’t know the name of the record. And I am not really interested in who produced the album or what record-company released the album. All I know is the artist’s name.

I pressumed that my users already know 70% sure what they are looking for.

In this example I will be using motorcycles as products , a topic I adore🙂

Maybe it is good how the products are indexed:

search box empty

A product may be indexed under maximum 4 categories, but this is not necessary. For example the Repsol model is delivered in only colorsetting.

After the user has typed in the first three characters a box will appear with the products that have the searchquery somewhere in their articlename:

suggestion box

In this example I search for ‘0RR’ and get all documents with that query in their articlename field.

Easy to implement?

First in the OnKeyPress event of the field I need to invoke the AJAX call:

var charNum = this.value.length

if (charNum>=3){

// 1 visible, 0 hidden

SearchBox(‘searchSuggest’,1)

createAJAXRequest (ExtDBName + “/” + SearchTAView + “?readviewentries&RestrictToCategory=” + UserOrg + “&Count=-1&dummy=’+ new Date().getTime();”,“processSearchValue”);}

  • SearchBox: the suggestion box that pops up with documents (as HREF links) matching the query 
  • readviewentries&RestrictToCategory: in the database example there is a restriction who from which organisation (UserOrg) is entitled to see what document…

Basically this request gets me all the documents in return the user is entitled to see.

The SearchBox function

The SearchBox function shows / hides a certain layer that will be placed just below the search field so the user sees the suggestions change along when he types a query in the search field.

function SearchBox(szDivID, iState) {

// 1 = visible, 0 = hidden

var obj = document.layers ? document.layers[szDivID] :document.getElementById ? document.getElementById(szDivID).style :document.all[szDivID].style;obj.visibility = document.layers ? (iState ? “show” : “hide”) : (iState ? “visible” : “hidden”);}

AJAX request

The AJAX is nothing unusual. Often I use the approach described on SitePoint how to make AJAX call.

The final function that generated the list in the ‘SearchBox’ layer looks like this:

function getProductName(xmlViewData){

// ** handle all the rows in the view

var viewRows = xmlViewData.getElementsByTagName (“viewentry”)

var prodList=“<div id=’ProdList’>”

for (var i=0; i < viewRows.length; i++) {

var prodName = getViewRowValues(viewRows[i])[0] numbQueryValue =

document.forms[0].Tx_SearchField.value

numbQuery = numbQueryValue.length

var searchstring=prodName.toLowerCase()

var searchfor = document.forms[0].Tx_SearchField.value.toLowerCase()

// indexOf -1 means not found

if (searchstring.lastIndexOf(searchfor)!=-1) {

prodList += “<div style=\”margin-top: 2px;\”>” + getViewRowValues(viewRows[i])[0]+ “</div>”

}

}

prodList +=“</div>”

document.getElementById(“XMLSearchArea”).innerHTML = prodList}

 What is does:

  • it checks if the search query is some where found (searchstring.lastIndexOf(searchfor)!=-1) in the first column in the XML object, this is a complete html code containing the <a href>product name</a> code
  • if so, the product is added to the product list
  • and finally the productlist is placed in the innerHTML part of the <div> with ID: XMLSearchArea.

If the user clicks on one of the product results the corresponding document is presented directly on the right, the SearchBox layer is being hidden and the search query remains in the search field.

Via an onFocus event in the searchfield I can directly re-produce the list for the user.

So that was my first setup. Any suggestions or setups are always welcome🙂