ShowTable of Contents
I created a javascript library you can use to perform some
common notes functions on the web using asynchronous AJAX lookups and dynamically
updating the form, rather than reloading the page from the server.
Verified working in IE7, Firefox 2, Safari 3
What it does:
In notes, it's easy to have one field or fields (the targets) combo-box
choices change when some other field (the source) changes, generally using
a dblookup.
This does something like a dblookup to replace "refresh choices on
document refresh" and also makes it simple to, for instance, hide
a field until a lookup is done.
If you want to use a multi-valued source field, it will do multiple lookups,
one for each value.
To set it up:
In "HTML Head Content", include the libarary (angle brackets
removed):
"script type=\"text/javascript\" src=\"/js/dominoFunctions.js\""
In the web version of your form, UNCHECK "refresh choices on document
refresh" on your target fields.
In your source fields, UNCHECK "refresh fields on keyword change"
You will use this function:
refreshKeywords(
source,
view,
column,
targetList,[multiRow,maxCount])
source: source field object
view: url of lookup view
column: column number to return (if this column contains vertical bars
(|), they will be treated as keyword synonym delimiters)
targetList: semi-colon separated list of target field names
multiRow: optional value default false, true/false indicates if multiple
rows should be returned from the view. If you use this, you must also include
maxCount: optional value--used with multiRow--indicates maximum
number of rows to return
In your form, you will set up a onPostKeywordRefresh function so you can
customize what happens for each lookup (it's a response from the server).
An example of this function is below.
Example:
In your souce fields, do this:
in OnClick event:
refreshKeywords(this,"/lawitc/lawtask.nsf/groups",2,"Owner;Viewers")
Optionally, in your target fields, you could set them to be hidden by default
(before a lookup occurs). You do this by putting this in the "HTML
Attributes" of the target field.
The following will set the field to be initallly hidden IF Subproject is
blank:
@If(SubProjects="";"style=\"visibility:hidden;\"";"")
in your JS Header, you need to put a javascript function called onPostKeywordRefresh.
This will fire after each refresh is complete and allows you to add special
customizations to your various refreshes if you want to.
If you don't want to do any special things after the refreshes, you can
just make it an empty do-nothing function (eg
function onPostKeywordRefresh()
{} ), but it needs to exist:
function onPostKeywordRefresh()
{
custom things that happen depending on which source field was changed.
You might want to show/hide fields, e.g.
switch(sourceFieldGlobal.name)
{
case 'Projects':
document.getElementsByName("SubProjects")[0].style.visibility="visible";
break;
case 'Department':
document.getElementsByName("Owner")[0].style.visibility="visible";
document.getElementsByName("Viewers")[0].style.visibility="visible";
break;
}
}
Notes on extensibility:
This only does one AJAX function call now--for refreshKeywords. It's set
up so it could potentially handle others.
It has some other convenience functions in it too--markAllSele cted,copyOptionsToArray,
removeDuplicates prototype.
Library (I call it dominoFunctions.js above and saved it to my server's
ht ml/js folder):
/
Library to perform flexible ajax keyword refreshes with Domino.
If you improve on this library, please let me know.
--George Payne 2/10/08 gpayne@virginia.edu
*/
var columnNumGlobal="";
var refreshTargetsGlobal="";
var currentFunctionGlobal="";
var sourceFieldGlobal="";
var lookupURL;
var keyGlobal="";
var keyIndexGlobal=0;
var keyLenGlobal=0;
var viewURLGlobal="";
var multiValGlobal=false;
var lastVal = "";
var val = ""
var searching=false;
var xmlHttpGlobal=null;
var retryNum=0;
var globalDom;
function incrementString(val)
{
 return string one alpha character beyond current
var alphaChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_";
var lastChar=val.charAt(val.length-1).toUpperCase();
var alphaIndex=alphaChars.indexOf(lastChar);
alphaIndex=(alphaIndex == -1)?25:alphaIndex;
alert("index: " + alphaIndex + " val: "
+ val.substring(0,val.length-1) + alphaChars.charAt(alphaIndex+1));
return val.substring(0,val.length-1) + alphaChars.charAt(alphaIndex+1);
}
Array.prototype.removeDuplicates = function ()
{
var temp=new Array();
this.sort();
for(i=0;i
{
if(this[i]==this[i+1]) {continue}
temp[temp.length]=this[i];
}
return temp;
}
function refreshKeywords(source, viewURL,columnNumber,targetList)
{
refreshKeywords(source, viewURL,columnNumber,targetList,
false,1)
}
function refreshKeywords(source, viewURL,columnNumber,targetList, multiVal,
maxCount)
{
 this function is called from field onChange event
targetList can be multivalued, use ";" to separate
target vals
currentFunctionGlobal="refreshKeyword";
columnNumGlobal=columnNumber;
refreshTargetsGlobal=targetList;
sourceFieldGlobal=source;
viewURLGlobal=viewURL
multiValGlobal=multiVal;
if (source.type 'select-one' || source.type
'text')
{
keyGlobal=new Array(1);
keyGlobal[0]=source.value;
}
else
{
keyGlobal=new Array(0);
for (var i = 0; i < source.options.length;
i++)
{
  alert(((source.options[i].getAttribute('value'))?
"Found" : "Not found"))
alert(((source.options[i].getAttribute('value'))?
source.options[i].value : source.options[i].text));
if (source.options[i].selected)
keyGlobal.push(((source.options[i].getAttribute('value'))? source.options[i].value
: source.options[i].text));
}
}
keyLenGlobal=keyGlobal.length;
keyIndexGlobal=0;
  alert("looking up: " + keyGlobal[0] + "
len: " + keyGlobal.length);
if (multiVal)
{
doRemoteQuery(viewURLGlobal + "?readviewentries&login=1&count="+maxCount+
"&preformat&startkey="+keyGlobal[0]+"&
untilkey=" + incrementString(keyGlobal[0]));
}
else
{
doRemoteQuery(viewURLGlobal + "?readviewentries&login=1&count=1&preformat&startkey="+keyGlobal[0]);
}
doRemoteQuery(viewURLGlobal + "?readviewentries&login=1&preformat&restrictToCategory="+keyGlobal[0]);
  use commented doRemoteQuery for categorized views to grab
multiple rows with the same key
}
function getFoundArray(column)
{ parses rows, cols from domino xml DOM
tempArr=new Array(0)
if (globalDom.documentElement == undefined)
{
return tempArr;
}
  xml nesting is viewentries/viewentry/entrydata (columns)
entrynodes = globalDom.documentElement.getElementsByTagName('viewentry');
for (var i = 0; i < entrynodes.length; i++)
{
cols=entrynodes[i].getElementsByTagName('entrydata');
temp = cols[column].getElementsByTagName('text')[0].firstChild.nodeValue;
if (i==0)
{
tempArr= temp.split(";");
}
else
{
tempArr=tempArr.concat(temp.split(";"))
}
}
return tempArr;
}
function updateCategories(targetList, newArray, append)
on fields specified by targetList, update options to newArray
newArray may contain synonyms separated by |
{
var targetArr=targetList.split(";");
var targetField;
for (var tnum=0; tnum < targetArr.length; tnum++)
{
targetField=document.getElementsByName(targetArr[tnum])[0];
alert("target field found:"+targetField.name);
var newValueArr;
if (!append){
targetField.options.length=0;
Remove current options
targetField.options.length=newArray.length;
}
Loop through the array and add all the new
values...
for(var i=0; i< newArray.length; i++)
{
newValueArr=newArray[i].split("|")
newOpt=document.createElement('option')
if (append)
{
try{
targetField.add(newOpt, null);
}
catch(ex
) {
targetField.add(newOpt);IE only
}
targetField.options[(targetField.options.length
- 1)].text=newValueArr[0];
if (newValueArr.length
> 1) targetField.options[(targetField.options.length - 1)].value=newValueArr[1]
}
else
{
targetField.options[i].text=newValueArr[0];
if (newValueArr.length
> 1) targetField.options[i].value=newValueArr[1]
}
}
if (newArray.length > 0)targetField.options[0].selected=true;
}
}
/
This sets up the XMLHTTP object we're using for the dynamic lookups.
function getXMLHTTP(){
var A = null;
try{
A = new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){
try{
A = new ActiveXObject("Microsoft.XMLHTTP");
} catch(oc){
A = null;
}
}
if(!A && typeof XMLHttpRequest != "undefined")
{
A = new XMLHttpRequest();
}
return A;
}
function doRemoteQuery(url){
searching = true;
retryNum=5;  allow IE to retry 5 times
if(xmlHttpGlobal && xmlHttpGlobal.readyState != 0)
{
xmlHttpGlobal.abort()
}
if(!xmlHttpGlobal) xmlHttpGlobal=getXMLHTTP();
if(xmlHttpGlobal){
xmlHttpGlobal.open("GET", url, true);
What do we do when the response comes back?
xmlHttpGlobal.onreadystatechange = function()
{
if (xmlHttpGlobal.readyState
== 4 && xmlHttpGlobal.responseText && searching) {
searching = false;
globalDom = xmlHttpGlobal.responseXML;
handleResponse();
}
} ;
xmlHttpGlobal.send(null);
}
}
function copyOptionsToArray(source)
{
var newArr=new Array(0);
for (var i = 0; i < source.options.length; i++)
{
; if (source.options[i].selected) newArr.push((source.options[i].getAttribute('value'))?
(source.options[i].text+"|"+source.options[i].value) : source.options[i].text);
}
return newArr;
}
function markAllSelected(sourceName)
{
var source=document.getElementsByName(sourceName)[0]
for (var i = 0; i < source.options.length; i++)
{
source.options[i].selected=true;
}
}
function handleResponse()
triggered by async server response to AJAX call
{
if (currentFunctionGlobal=="refreshKeyword")
{
if (keyIndexGlobal==0)
{
updateCategories(refreshTargetsGlobal,getFoundArray(columnNumGlobal),false);
start new
onPostKeywordRefresh()
}
else
{
updateCategories(refreshTargetsGlobal,getFoundArray(columnNumGlobal),true);
append
}
keyIndexGlobal++;
if (keyLenGlobal > keyIndexGlobal) if
key was a multivalue, do more lookups
{
doRemoteQuery(viewURLGlobal +
"?readviewentries&login=1&count=1&preformat&startkey="+keyGlobal[keyIndexGlobal]);
  doRemoteQuery(viewURLGlobal
+ "?readviewentries&login=1&preformat&restrictToCategory="+keyGlobal[keyIndexGlobal]);
use commented doRemoteQuery
for categorized views to grab multiple rows with the same key
}
}
}