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!

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”));





