« Vertigo 42 | Main| House Move Off »

How the Name Picker Works

Category
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

The other day I described how to use my name picker page. Today I'll go into a little detail about how it actually works.

The page is mainly javascript, when it loads up, at the simplest level these are the steps it goes through.
Check if there are URL Parameters
If there are then use them. This just uses my getURLParam function (you can get more information about it here).

Ultimately we are looking to have the ability to build a URL which will be something like:
http://www.11tmr.com/apps/50wordreview.nsf/vwReviewsByName?readviewentries&count=10

Load the first set of view entries
The HTML page itself doesn't contain any data, what it does is use the wonderful XMLHttpRequest (or Microsoft.XMLHTTP) object to send a request to the Domino server and load an XML page. In this case we are making use of the "readviewentries" command on the Domino view which has been available for years now. I have wrapped the actual task of getting the XML into this function:
function doXMLHTTPGet(vurl)
{
  var returnData = null;

  if (window.XMLHttpRequest)
  {
    //We are in a non-IE browser
    httpObj=new XMLHttpRequest()
  }
  else if (window.ActiveXObject)
  {
    //We are in IE
    httpObj=new ActiveXObject("Microsoft.XMLHTTP")
  }

  //Return the XML document when it has finished loading
  httpObj.onreadystatechange= function()
    {
      if (httpObj.readyState==4)
      {
        if (httpObj.status==200)
        {
          displayData(httpObj.responseXML);
        }
        else
        {
          alert("Problem retrieving XML data:" + httpObj.statusText)
        }
      }
    }

  //Request the XML document
  httpObj.open("GET",vurl,true)
  httpObj.send(null)
}
Depending on the browser we get the correct type of XMLHttpRequest object and give it a URL to open. The function runs through to completion, but first it sets the "onReadyStateChange" method of the request itself so that when the page is fully loaded the XML within the page is sent to a function called displayData.
Parse the XML
In the displayData function which is called when the XML is loaded, all we need to do is move through the XML data, picking out the values required and then formatting them into HTML. It would be possible to do this with XSLT but personally I find it much more difficult to use and the volume of data we are processing here hardly registers so there are no real performance issues to worry about. Some of the key things to note about the XML returned are the old favourite "toplevelentries" property. This tells us how many documents there are in the view which enables us to move to the last page and not beyond and to show the user where in the view they are. Getting the data out of the XML couldn't be easier, all we are doing is drilling down through the levels using the getElementsByTagName method. For example, to get each view entry we just say
var results = xmlDoc.documentElement.getElementsByTagName("viewentry");
This will return an array which we can loop through. For each viewentry we can then get the column data which we need and format it into HTML:
var strHTML = "<table width=\"100%\">";
for (i=0; i<results.length; i++)
{
  //Get the value which the row will return if clicked
  var returnText = getInnerText(results[i].getElementsByTagName("entrydata")[returnColumn]);
  //Write the tr tag
  strHTML += "<tr class=\"normal\" onMouseOver=\"this.className='highlight'\" onMouseOut=\"this.className='normal'\" onClick=\"storeValue('" + escape(returnText) + "');\">";
  for (iCol=0; iCol < columns.length; iCol++)
  {
    //Write each cell of the current row
    var text;
    try
    {
        text = getInnerText(results[i].getElementsByTagName("entrydata")[parseInt(columns[iCol], 10)]);
    }
    catch (e)
    {
        text =+ e. message + " (" + e.name + ")";
    }
    strHTML += "<td>gt;" + text + "</td>";
  }
  strHTML += "</tr>";
}
strHTML += "</table>";
So we can see that for each viewentry there is an entrydata element per view column which contains the text which we want to extract using the getInnerText function.
/*
  getInnerText function courtesy of Martin Honnen
  http://www.thescripts.com/forum/threadedpost582930.html
*/
function getInnerText (node) {
  if (typeof node.textContent != 'undefined')
  {
    return node.textContent;
  }
  else if (typeof node.innerText != 'undefined')
  {
    return node.innerText;
  }
  else if (typeof node.text != 'undefined')
  {
    return node.text;
  }
  else
  {
    switch (node.nodeType)
    {
      case 3:
      case 4:
        return node.nodeValue;
        break;
      case 1:
      case 11:
        var innerText = '';
        for (var i = 0; i < node.childNodes.length; i++)
        {
          innerText += getInnerText(node.childNodes[i]);
        }
        return innerText;
        break;
      default:
        return '';
    }
  }
}

Scroll through the pages
Getting the next page is as easy as just adding a new parameter to the URL. start allows us to get n documents from any starting position in the view. So all we need to know is where we currently are. This can be extracted from the "position" attribute of any viewentry.
Search for a value
Searching is equally easy, rather than using the start URL parameter to open the view, we just use the startkey parameter and give it the value we are searching for.
Select a value
Now depending on how the picker has been opened there are two different ways that the selected value is returned to the opening window. If we are using the IE only modal dialog then we have only one option: to set the "returnValue" property of the window and then close it. What this means is that the opening window is returned the value and it has to go and store the returnValue into the relevant field.
The other option is if the picker has been opened as a popup window. This means that it can talk to the opening window using "window.opener..." so we can directly set the field which needs to store the value.

Either way is pretty easy:
function storeValue(strValue)
{
  try
  {
    //Decode the return value
    strValue = unescape(strValue);

    //Check to see if we can talk to parent window
    if (window.opener && returnFieldName != null)
    {
      //We can talk to parent so set returnFieldName directly
      window.opener.document.getElementById(returnFieldName).value = strValue;
    }
    else
    {
      //Set the returnValue property of the window as we are in a modal dialog
      //NB. We cannot set the returnFieldName directly so calling function must do this.
      window.returnValue = strValue;
    }
  }
  catch (e)
  {
    alert("There has been an error returning the selected value:\n\n" + e.message);
  }
  window.close();
}


Overall you can see that this whole tool is pretty simple. The things you can do with AJAX are pretty much endless and I have to say that the users love it. We are able to re-invigorate our applications for very little cost, everyone will be happy with that!

Note: I am aware of one bug with this tool in Safari (thanks Bruce). I have one idea about how to solve it and will post the fixed code when I get a minute (and I don't have a hangover!).

Comments

Gravatar Image1 - Great job on this! I have done something very similar to this in my Domino Web Tools project on OpenNTF.org.

Also, using Ajax techniques, I just added the ability to expand multiple categories in a single category view. You can take a look here to see it in action: http://jackratcliff.com/jratcliff/dwt/dwt-demo.nsf/sc1?OpenView

Jack

UKLUG

NO2ID - Stop ID cards and the database state

Email / MSN / Google Talk matthew.white@fclonline.com
AIM: whitemx
Skype: whitemrx

Hits since 03-Feb-2006