Wednesday, October 8, 2008

Objectizing Ajax (XML) results





Definition: Objectize - To make an object of; to regard as an object; to place in the position of an object.


Objectizing XML documents is a very common concept incorporated into dot.net server side framework. Whether you’re creating a web service proxy or desterilizing objects what you’re doing is objectizing xml. Wouldn’t it be the coolest thing if you could do the same on the client side, regard xml results coming back from Ajax requests as objects and manipulating then with ease. So instead on dealing with Xml DOM and writing code that looks like this:


Root.childNodes[0].childNodes[0].firstChild.attributes[“name”]


You can actually write:


Root.resultset.result.businessunitid.name


While creating my own objectizer I came across Yusuke Kawasaki ObjTree.js free class library and decided to check it out.
I modified it slightly to support ms resulting xml schema and made it a bit leaner to so only the objectizing part is included.


The OnCrmPageLoad includes a very simple example of how to create the ObjTree object, passing it an xml document and receiving an object representation of that document.The post uses the “Ajax using Fetch Message” to demonstrate the usage.

Part of Yusuke Kawasaki ObjTree.js Library


if ( typeof(XML) == 'undefined' ) XML = function() {};

// constructor

XML.ObjTree = function () {
return this;
};

// object prototype
XML.ObjTree.prototype.xmlDecl = '<?xml version="1.0" encoding="UTF-8" ?>\n';
XML.ObjTree.prototype.attr_prefix = '';

// method: parseXML( xmlsource )
XML.ObjTree.prototype.parseXML = function ( xml ) {
var root;
if ( window.ActiveXObject ) {
xmldom = new ActiveXObject('Microsoft.XMLDOM');
xmldom.async = false;
xmldom.loadXML( xml );
root = xmldom.documentElement;
}
if ( ! root ) return;
return this.parseDOM( root );
};

// method: parseDOM( documentroot )
XML.ObjTree.prototype.parseDOM = function ( root ) {
if ( ! root ) return;

this.__force_array = {};
if ( this.force_array ) {
for( var i=0; i<this.force_array.length; i++ ) {
this.__force_array[this.force_array[i]] = 1;
}
}

var json = this.parseElement( root ); // parse root node
if ( this.__force_array[root.nodeName] ) {
json = [ json ];
}
if ( root.nodeType != 11 ) { // DOCUMENT_FRAGMENT_NODE
var tmp = {};
tmp[root.nodeName] = json; // root nodeName
json = tmp;
}
return json;
};

// method: parseElement( element )
XML.ObjTree.prototype.parseElement = function ( elem ) {
// COMMENT_NODE
if ( elem.nodeType == 7 ) {
return;
}

// TEXT_NODE CDATA_SECTION_NODE
if ( elem.nodeType == 3 || elem.nodeType == 4 ) {
var bool = elem.nodeValue.match( /[^\x00-\x20]/ );
if ( bool == null ) return; // ignore white spaces
return elem.nodeValue;
}

var retval;
var cnt = {};

// parse attributes
if ( elem.attributes && elem.attributes.length ) {
retval = {};
for ( var i=0; i<elem.attributes.length; i++ ) {
var key = elem.attributes[i].nodeName;
if ( typeof(key) != "string" ) continue;
var val = elem.attributes[i].nodeValue;
if ( ! val ) continue;
key = this.attr_prefix + key;
if ( typeof(cnt[key]) == "undefined" ) cnt[key] = 0;
cnt[key] ++;
this.addNode( retval, key, cnt[key], val );
}
}

// parse child nodes (recursive)
if ( elem.childNodes && elem.childNodes.length ) {
var textonly = true;
if ( retval ) textonly = false; // some attributes exists
for ( var i=0; i<elem.childNodes.length && textonly; i++ ) {
var ntype = elem.childNodes[i].nodeType;
if ( ntype == 3 || ntype == 4 ) continue;
textonly = false;
}
if ( textonly ) {
if ( ! retval ) retval = "";
for ( var i=0; i<elem.childNodes.length; i++ ) {
retval += elem.childNodes[i].nodeValue;
}
} else {
if ( ! retval ) retval = {};
for ( var i=0; i<elem.childNodes.length; i++ ) {
var key = elem.childNodes[i].nodeName.replace("#","");
if ( typeof(key) != "string" ) continue;
var val = this.parseElement( elem.childNodes[i] );
if ( ! val ) continue;
if ( typeof(cnt[key]) == "undefined" ) cnt[key] = 0;
cnt[key] ++;
this.addNode( retval, key, cnt[key], val );
}
}
}
return retval;
};

// method: addNode( hash, key, count, value )
XML.ObjTree.prototype.addNode = function ( hash, key, cnts, val ) {
key = this.key_qualify(key);
if ( this.__force_array[key] ) {
if ( cnts == 1 ) hash[key] = [];
hash[key][hash[key].length] = val; // push
} else if ( cnts == 1 ) { // 1st sibling
hash[key] = val;
} else if ( cnts == 2 ) { // 2nd sibling
hash[key] = [ hash[key], val ];
} else { // 3rd sibling and more
hash[key][hash[key].length] = val;
}
};

XML.ObjTree.prototype.key_qualify = function( key ){
return key.replace(/\W/gi,"");
}
// method: xml_escape( text )
XML.ObjTree.prototype.xml_escape = function ( text ) {
return String(text).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
};

/*
Copyright (c) 2005-2006 Yusuke Kawasaki. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the Artistic license. Or whatever license I choose,
which I will do instead of keeping this documentation like it is.
*/



The original xml result looks like this


<resultset morerecords=\"0\" paging-cookie=\"&lt;cookie page=&quot;1&quot;&gt;&lt;fullname last=&quot;SYSTEM&quot; first=&quot;INTEGRATION&quot; /&gt;&lt;systemuserid last=&quot;{D874E288-2C8C-43D5-AEBA-5404888BC185}&quot; first=&quot;{B2C53269-CFF5-4F26-A4E5-669284EA6E96}&quot; /&gt;&lt;/cookie&gt;\">
<result>
<fullname>INTEGRATION</fullname>
<businessunitid dsc=\"0\" name=\"MicrosoftCRM\">
{80E4E0DF-65C1-DC11-B67A-0003FFBB057D}
</businessunitid>
<systemuserid>{B2C53269-CFF5-4F26-A4E5-669284EA6E96}</systemuserid>
</result>
<result>
<fullname>LitwareInc Administrator</fullname>
<businessunitid dsc=\"0\" name=\"MicrosoftCRM\">
{80E4E0DF-65C1-DC11-B67A-0003FFBB057D}
</businessunitid>
<systemuserid>{9BF5E0DF-65C1-DC11-B67A-0003FFBB057D}</systemuserid>
</result>
</resultset>



Usage Example:



function OnCrmPageLoad()
{
/* Get All Users */
var fetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">';
fetchXml += '<entity name="systemuser">';
fetchXml += '<attribute name="fullname"/>';
fetchXml += '<attribute name="businessunitid"/>';
fetchXml += '<attribute name="title"/>';
fetchXml += '<attribute name="address1_telephone1"/>';
fetchXml += '<attribute name="systemuserid"/>';
fetchXml += '<order attribute="fullname" descending="false"/>';
fetchXml += '</entity>';
fetchXml += '</fetch>';

/* Make the fetch and retrieve xml result */
var resxml = Fetch(fetchXml);
/* Create an ObjTree Object */
var xotree = new XML.ObjTree();
/* Objectize xml result */
var tree = xotree.parseXML( resxml );

/*
if the original node contains only data then the property is treated as string
*/
alert( tree.resultset.result[0].fullname )
/*
if the original node contains attributes or children then you should treat it as
object and reference its properties e.g. object.text and object.propertyName
*/
alert( tree.resultset.result[0].businessunitid.text ); //GUID
alert( tree.resultset.result[0].businessunitid.name ); //OrgName
}

function Fetch( xml )
{
var Xml = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
Xml += GenerateAuthenticationHeader()
Xml += "<soap:Body>";
Xml += "<Fetch xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">";
Xml += "<fetchXml>";
Xml += _HtmlEncode(xml); // Microsoft _HtmlEncode function
Xml += "</fetchXml>";
Xml += "</Fetch>";
Xml += "</soap:Body>";
Xml += "</soap:Envelope>";

var XmlHttp = CreateXmlHttp(); // Microsot CreateXmlHttp function
XmlHttp.open("POST", "/mscrmservices/2007/crmservice.asmx", false ); //Sync Request
XmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
XmlHttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Fetch");
XmlHttp.send(Xml);

return XmlHttp.responseXML.text
}

OnCrmPageLoad();

No comments: