Mar 022007
 

As several bloggers have posted, there is a new way of getting data out of Domino views using JSON. This feature was planned for Domino 8 but “slipped” into the 7.0.2 release of Domino.

I wanted to know if it would be faster to parse the data with JSON out of a huge view, with over a 1000 documents, and print the result back to the browser. So I needed a way of telling how fast it really was. Thanks to a great plug-in for Firefox called Firebug I could do just that. Get Firebug now. Serious web developers can’t live without it.

So I created a domino database with a view called “Docs”. I then created a simple form with several different types of fields. (See Fig. 1) I created different types of fields so I could verify that I could parse all of them.

Fig. 1

I created 2 documents based on that form and copied and pasted those so I had more than 1000 documents in my “Docs” view.

Then I created two different Domino Pages with my XML code in one, and the JSON code in the other. I called them TestXML and TestJSON. I selected HTML as the Content type for Web Access.
The code in these two pages make GET calls to the view, via Ajax, to get the XML or JSON back. The JavaScript then parses that to print HTML back to the browser. I post all the code for both pages below for you to take a look at.

The two URL’s that we get are:
var sURL = './Docs?ReadViewEntries&count=1000';
var sURL = './Docs?ReadViewEntries&count=1000&outputformat=json';

As you can see the only difference between the two calls are that for JSON you add the “&outputformat=json”. To show you example of what the code looks like I post the first entry in both formats.

XML

<?xml version="1.0" encoding="UTF-8"?>
<viewentries toplevelentries="1019">
<viewentry position="1" unid="7ABFA0DB15FEC6B186257283006771FE" noteid="8FA" siblings="1019">
<entrydata columnnumber="0" name="TextField">
<text>This is text</text>
</entrydata>
<entrydata columnnumber="1" name="NumberField">
<number>12</number>
</entrydata>
<entrydata columnnumber="2" name="DateTimeField">
<datetime>20070216T100458,29-06</datetime>
</entrydata>
<entrydata columnnumber="3" name="ListField">
<textlist>
<text>Viktor</text>
<text>Troy</text>
<text>Rob</text>
</textlist>
</entrydata>
<entrydata columnnumber="4" name="ListNumberField">
<numberlist>
<number>1</number>
<number>2</number>
<number>3</number>
</numberlist>
</entrydata>
<entrydata columnnumber="5" name="ListDateField">
<datetimelist>
<datetime>20070216</datetime>
<datetime>20070217</datetime>
<datetime>20070316</datetime>
</datetimelist>
</entrydata>
</viewentry>
</viewentries>

JSON

{
"@toplevelentries": 1019,
viewentry: [
{
"@position": '1',
"@unid": '7ABFA0DB15FEC6B186257283006771FE',
"@noteid": '8FA',
"@siblings": 1019,
entrydata: [
{
"@columnnumber": 0,
"@name": 'TextField',
text: {
0: 'This is text'
}
},
{
"@columnnumber": 1,
"@name": 'NumberField',
number: {
0: 12
}
},
{
"@columnnumber": 2,
"@name": 'DateTimeField',
datetime: {
0: '20070216T100458,29-06'
}
},
{
"@columnnumber": 3,
"@name": 'ListField',
textlist: {
text: [
{
0: 'Viktor'
},
{
0: 'Troy'
},
{
0: 'Rob'
}
]
}
},
{
"@columnnumber": 4,
"@name": 'ListNumberField',
numberlist: {
number: [
{
0: 1
},
{
0: 2
},
{
0: 3
}
]
}
},
{
"@columnnumber": 5,
"@name": 'ListDateField',
datetimelist: {
datetime: [
{
0: '20070216'
},
{
0: '20070217'
},
{
0: '20070316'
}
]
}
}
]
}
]
}

TestXML

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Test XML</title>
<script>
var g_XMLHttpRequest_ActiveX;
var g_XMLDomDocument_ActiveX;

function initXMLHttpRequest(){
var objHTTP = null;
if(window.ActiveXObject && !window.XMLHttpRequest){ //IE
if(g_XMLHttpRequest_ActiveX){
objHTTP = new ActiveXObject(g_XMLHttpRequest_ActiveX);
}else{
var xmlhttp = new Array('Msxml2.XMLHTTP.7.0','Msxml2.XMLHTTP.6.0',
'Msxml2.XMLHTTP.5.0','Msxml2.XMLHTTP.4.0','MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP','Microsoft.XMLHTTP');
for(var i = 0; i < xmlhttp.length; i++){
try{
objHTTP = new ActiveXObject(xmlhttp[i]);
if(objHTTP != null){
g_XMLHttpRequest_ActiveX = xmlhttp[i];
break;
}
}catch(e){}
}
}
}else{ //Mozilla
try{
objHTTP = new XMLHttpRequest();
}catch(e){}
}
return objHTTP;
}

function postCommandAsync(sURL, sParms, sResponseHandler){
var sPassedParms = "";
var iArgCount = postCommandAsync.arguments.length;
var aArgs;
if (iArgCount > 3){
aArgs = postCommandAsync.arguments
for (var i = 3; i < iArgCount; i++){
sPassedParms += ", aArgs[" + i + "]";
}
}
var objHTTP = initXMLHttpRequest();
try{
objHTTP.onreadystatechange = function() {
if(objHTTP.readyState == 4) {
if(typeof objHTTP.status == 'undefined' || objHTTP.status == 200 || objHTTP.status == 304){
eval(sResponseHandler + "(objHTTP.responseXML" + sPassedParms + ")");
}
}
};
objHTTP.open("GET", sURL, true);
objHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
objHTTP.send(sParms);
}catch(e){}
}

function returnEntryValue(node){
node = node.firstChild;
var oChild = oGChild = null;
var aReturn = [];
while(node != null){
if((node.nodeType == 3 || node.nodeType == 4) && node.nodeValue != 'n'){
aReturn[aReturn.length] = node.nodeValue;
}else if(node.nodeType == 1){
oChild = node.firstChild;
while(oChild != null){
if((oChild.nodeType == 3 || oChild.nodeType == 4) && oChild.nodeValue != 'n'){
aReturn[aReturn.length] = oChild.nodeValue;
}else if(oChild.nodeType == 1){
oGChild = oChild.firstChild;
while(oGChild != null){
if((oGChild.nodeType == 3 || oGChild.nodeType == 4) && oGChild.nodeValue != 'n'){
aReturn[aReturn.length] = oGChild.nodeValue;
}
oGChild = oGChild.nextSibling;
}
}
oChild = oChild.nextSibling;
}
}
node = node.nextSibling;
}
return aReturn;
}

function getInnerXML(node){
if(node.innerXML){
return node.innerXML; // string
}else if (node.xml){
return node.xml; // string
}else if(typeof XMLSerializer != "undefined"){
return (new XMLSerializer()).serializeToString(node); // string
}
}

function gotDocs(xmlDoc){
var viewentries = xmlDoc.getElementsByTagName("viewentry");
var n_viewentries = viewentries.length;
var sHTML = '<table border="1">';
for (var i = 0; i < n_viewentries; i++){
var unidAttr = viewentries[i].getAttribute("unid");
var entrydata = viewentries[i].getElementsByTagName("entrydata");
var sTextField = returnEntryValue(entrydata[0])[0];
var sNumberField = returnEntryValue(entrydata[1])[0];
var sDateTimeField = returnEntryValue(entrydata[2])[0];
var aListField = returnEntryValue(entrydata[3]);

sHTML += '<tr>';
sHTML += '<td>' + sTextField + '</td>';
sHTML += '<td>' + sNumberField + '</td>';
sHTML += '<td>' + sDateTimeField + '</td>';
sHTML += '<td>'
for (var j = 0; j < aListField.length; j++){
sHTML += aListField[j] + '<br />';
}
sHTML += '</td>'
sHTML += '</tr>';
}
sHTML += '</table>';
document.getElementById('docsDiv').innerHTML = sHTML;
}

function init(){
var sURL = './Docs?ReadViewEntries&count=1000';
postCommandAsync(sURL, "", "gotDocs");
}
</script>
</head>
<body onLoad="init()">
<div id="docsDiv"></div>
</body>
</html>

TestJSON

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Test JSON</title>
<script>
var g_XMLHttpRequest_ActiveX;
var g_XMLDomDocument_ActiveX;


function initXMLHttpRequest(){
var objHTTP = null;
if(window.ActiveXObject && !window.XMLHttpRequest){ //IE
if(g_XMLHttpRequest_ActiveX){
objHTTP = new ActiveXObject(g_XMLHttpRequest_ActiveX);
}else{
var xmlhttp = new Array('Msxml2.XMLHTTP.7.0',
'Msxml2.XMLHTTP.6.0','Msxml2.XMLHTTP.5.0','Msxml2.XMLHTTP.4.0',
'MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP','Microsoft.XMLHTTP');
for(var i = 0; i < xmlhttp.length; i++){
try{
objHTTP = new ActiveXObject(xmlhttp[i]);
if(objHTTP != null){
g_XMLHttpRequest_ActiveX = xmlhttp[i];
break;
}
}catch(e){}
}
}
}else{ //Mozilla
try{
objHTTP = new XMLHttpRequest();
}catch(e){}
}
return objHTTP;
}

function getCommandAsync(sURL, sParms, sResponseHandler){
var sPassedParms = "";
var iArgCount = getCommandAsync.arguments.length;
var aArgs;
if (iArgCount > 3){
aArgs = getCommandAsync.arguments;
for (var i = 3; i < iArgCount; i++){
sPassedParms += ", aArgs[" + i + "]";
}
}
var objHTTP = initXMLHttpRequest();
try{
objHTTP.onreadystatechange = function() {
if(objHTTP.readyState == 4) {
if(typeof objHTTP.status == 'undefined' || objHTTP.status == 200 || objHTTP.status == 304){
eval(sResponseHandler + "(eval('(' + objHTTP.responseText + ')')" + sPassedParms + ")");
}
}
};
objHTTP.open("GET", sURL, true);
objHTTP.setRequestHeader("Content-Type", "text/javascript");
objHTTP.send(sParms);
}catch(e){}
}


function returnJSONValue(obj){
var aReturn = [];
for(var a in obj) {
switch(a){
case "text":
case "number":
case "datetime":
if(obj[a].constructor.toString().indexOf("Array") == -1){
aReturn.push(obj[a][0]);
}else{
for(var i=0; i<obj[a].length; i++){
aReturn.push(obj[a][i][0]);
}
}
break;
case "textlist":
case "numberlist":
case "datetimelist":
aReturn = returnJSONValue(obj[a]);
break;
default:
break;
}
}
return aReturn;
}

function gotDocsJSON(oObject){
var viewentries = oObject.viewentry;
var n_viewentries = viewentries.length;
var sHTML = '<table border="1">';
for (var i = 0; i < n_viewentries; i++){
var unidAttr = viewentries[i]["@unid"];
var entrydata = viewentries[i].entrydata;
var sTextField = returnJSONValue(entrydata[0])[0];
var sNumberField = returnJSONValue(entrydata[1])[0];
var sDateTimeField = returnJSONValue(entrydata[2])[0];
var aListField = returnJSONValue(entrydata[3]);


sHTML += '<tr>';
sHTML += '<td>' + sTextField + '</td>';
sHTML += '<td>' + sNumberField + '</td>';
sHTML += '<td>' + sDateTimeField + '</td>';
sHTML += '<td>'
for (var j = 0; j < aListField.length; j++){
sHTML += aListField[j] + '<br />';
}
sHTML += '</td>'
sHTML += '</tr>';
}
sHTML += '</table>';
document.getElementById('docsDiv').innerHTML = sHTML;
}

function init(){
var sURL = './Docs?ReadViewEntries&count=1000&outputformat=json';
getCommandAsync(sURL, "", "gotDocsJSON");
}
</script>
</head>
<body onLoad="init()">
<div id="docsDiv"></div>
</body>
</html>

I then ran the code in Firefox. I could see a difference right away in the speed the page was loading, but I wanted two know how much an improvement it was. Firebug to the rescue. Firebug has a feature to run code and do a “Profile” on that code to see how long each function took. You can run the code several times, and see an average time it took for each function to execute. You also get the exact number of milliseconds it took for the query to return the XML or JSON of the view.

The result

The results are quite remarkable. The JSON GET query ran an average of 275% faster then the XML GET query. And the JavaScript parse code ran an average of 366% faster for the JSON. The average total execution time for the XML on a refresh was just over 4040 ms. Over 4 seconds. For the JSON the same result was just over 1310 ms. The average total time for the query to be returned and all JavaScript to execute was on an average over 300% faster with the JSON code. You can see the result below. Click on the images to see them in full scale. (See Fig. 2 and 3)

I’m going to use JSON from now on when coding in Domino 7.0.2 or above. This is not a scientific test done in a lab with different servers on different platforms. I did this in an afternoon between projects. You might see different results. If you do please leave a response below.

I also want to mention that IBM has indicated that the the structure of the JSON might change in the final release of Domino 8. I wouldn’t mind. It’s not pretty at the moment.

Fig. 2 – XML Test

Fig. 3 – JSON Test

  6 Responses to “Faster Ajax with Domino and JSON”

  1. Hej,

    Thank you for the article.

    I am really looking for more info on using DOJO inside Domino applications, any resource is welcome.

    KR // Patrick

  2. Hi,
    thanks for this very usable article!
    Greetings from Slovenia,
    Bojan

  3. Can you publish the example application? This would be helpfull in learning and customizing to fit a novices applications.

    Steve

  4. #Steve –
    I have posted an update to this article where you can download the database including the code.

  5. Ok. So the previous comment was stripped because it contained “greater than” and “less than” signs which is treated as HTML and thus stripped from the message. Let’s try again:
    var sHTML = new Array();
    for (var i = 0; i < n_viewentries; i++){
    sHTML.push(“<tr>”);

    }
    document.getElementById(’docsDiv’).innerHTML = sHTML.join(””);

  6. really thank you. your post make me stronger 🙂
    actually i found only one good article about domino/xml/json and it’s yours.

    Thank you ! I will use you experience ! thank u for share ! reallly BIG BIG thank 🙂

Leave a Reply to Viktor Cancel reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)