September 2008 - Posts

The web site I've been working on, Vloggerheads, will be opened to the public tomorrow. You can view the videos and blogs but you won't be able to comment or post videos unless you request membership and are approved. You might want to check out the video of the shrine that was built to me or the many videos thanking me for adding much needed features to the site. LOL

Vloggers make so many videos that it is not unusual for them to make a video about the site itself. I get a lot more feedback and appreciation for my work on this web site than I get on any other project. This alone makes it very worthwhile. If you are looking for recognition from users than you should definitely consider a video sharing site project.

I've added several features to this Ning site which everyone was used to having on YouTube or LiveVideo. Leaving comments on videos is an important aspect of the communication in the community so I added comment threading and comment replies. I also implemented email notification for comment replies. Working with the Ning platform is painfully difficult. Just sending an email proved to be a major hassle because Ning does not give me direct access to a member's email address. I have to use their built-in classes to send emails. And I cannot query for more than 100 content items at a time so I need to write a loop to get all the comments. Ning fights me every step of the way and frustrates me with its caching system and poor error reporting tools.

Ning is dropping the old version of the Dojo JavaScript library they were using and replacing it with jQuery. So I'm glad that ASP.NET will officially support jQuery as I'll be using it on all my projects. I've already included jQuery in my two remaining ASP.NET projects and I imported it into my Ning sites. I even use jQuery in my help files that I use for documentation.

I wrote some very complicated JavaScript to handle the comment pagination so I've been using every tool available to me to troubleshoot my JavaScript. Since the Ning platform caches JavaScript on the server side I've found it useful to load my recently changed scripts in Firebug to double check the version downloaded by Firefox. I've even resorted to using some console.log("string") lines in my code to avoid annoying alerts. My JavaScript is loading and manipulating XML so I've had to view my JavaScript objects in the DOM pane to carefully review available properties and methods. However, I haven't neglected the new debugging tools in Internet Explorer 8.0 Beta. I even have to use Opera to get an error message for a Dojo library error that won't show up in Firefox or Internet Explorer. I was uncertain as to what values the Ning platform was expecting for a POST request so I learned how to use Firebug to view what was being sent in a typical POST request.

During a recent Skype conference I was surprised to learn that both YouTube and LiveVideo were more disinterested in the vlogging community than I suspected. This surprises me because I think the social networking potential of vlogging is obvious. Vlogging is clearly more effective than blogging and other forms of text in fostering a genuine sense of online community. You can form real friendships through vlogging that would never take place among bloggers. Unfortunately, too many Web 2.0 entrepreneurs focus on valuations and numbers and neglect to consider the most important factor of business value, providing a service that meets real needs. You can never go wrong betting on a business that meets the needs of the general public. Vlogging clearly meets many crucial needs. It provides social interaction for the socially isolated. It provides recognition for creative people. It provides entertainment and communication that is more interactive than the mass media can offer. To dismiss vlogging as something that will never catch on with the general public is as stupid as it was two years ago to dismiss blogging as a fad.

Recently I was working on some complicated JavaScript to nest unordered lists and list items to match an Atom feed's XML structure. I had to spend an entire day researching JavaScript's XML parsing capabilities because there is no place on the Internet where this information is gathered to my satisfaction. I created a JavaScript cheat sheet on XML for my notes. I thought I would share it with you in order to get some feedback for improving my notes. Let me know if you know of a JavaScript library that eases the pain of working with XML in JavaScript. jQuery is somewhat useful for this if you use its DOM selectors on an XML document instead of the web page document. var entryNode = $('entry',xmlDoc).eq(5);

Load XML

You can load XML from a file on the web server or from a remote source via XMLHttpRequest. The code below shows the number of child nodes below the root element.

   1: (document).ready(function(){ 
   2:     // Load XML from a file on the web server
   3:     if (window.ActiveXObject) {
   4:         var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
   5:         xmlDoc.async = false;
   6:         xmlDoc.load("xml/video-comments.xml");
   7:     }
   8:     else {
   9:         var mXHR = new XMLHttpRequest();
  10:         mXHR.open("GET", "xml/video-comments.xml", false);
  11:         mXHR.send(null);
  12:         xmlDoc = mXHR.responseXML;
  13:     }
  14:     var root = xmlDoc.documentElement;
  15:     $("#response").append(root.childNodes.length);
  16: }); 

NOTE: Firefox, Opera, and Safari counts any whitespace as text nodes so their node counts may be higher.

getElementsByTagName

You can get an array of elements by tag name which returns all occurrences even when the elements are nested within each other.

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: $("#response").append("Entry Nodes: " + nodelist.length);

You can also use a wildcard character to get all elements with a tag name:

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('*');
   3: $("#response").append("<b>All tags count: </b>" + nodelist.length);

Find A Child Node By Tag Name

Internet Explorer has a text property for a node while the other browsers use textContent:

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: var strID = "";
   4: // loop through the sixth node's children
   5: for (var i = 0; i < nodelist[5].childNodes.length; i++) {
   6:     var child = nodelist[5].childNodes[i];
   7:     // find the child node by tag name
   8:     if (child.tagName == "id") {
   9:         // IE supports the text property
  10:         if (window.ActiveXObject) {
  11:             strID = child.text
  12:         }
  13:         // Other browsers use textContent
  14:         else {
  15:             strID = child.textContent;
  16:         }
  17:     }
  18: }

Internet Explorer's Xml Property

Internet Explorer has an xml property for a node but the other browsers do not.

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: var strID = "";
   4: // loop through the sixth node's children
   5: for (var i = 0; i < nodelist[5].childNodes.length; i++) {
   6:     var child = nodelist[5].childNodes[i];
   7:     // find the child node by tag name
   8:     if (child.tagName == "id") {
   9:         // IE supports the xml property
  10:         if (window.ActiveXObject) {
  11:             strID = child.xml
  12:         }
  13:     }
  14: }
  15: $("#response").append("<b>5th Entry Element: </b>" + htmlentities(strID));

NOTE: The xml namespace will be added to the xml tag.

Get The First Child Element

Remember that the other browsers may count whitespace as a child node:

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: var strText = "";
   4: // Get the first child of a node in IE 
   5: if (window.ActiveXObject) {
   6:     child = nodelist[5].firstChild;
   7:     strText = child.text;
   8: }
   9: // In other browsers the first child would be whitespace so get the next sibling
  10: else {
  11:     child = nodelist[5].childNodes[0].nextSibling;
  12:     strText = child.textContent;
  13: }
  14: $("#response").append("<b>6th Node's First Child: </b>" + strText);

Get The Last Child Element

Remember that the other browsers may count whitespace as a child node:

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: var strText = "";
   4: // Get the last child of a node in IE 
   5: if (window.ActiveXObject) {
   6:     child = nodelist[5].lastChild;
   7:     strText = child.text;
   8: }
   9: // In other browsers the last child would be whitespace so get the previous sibling
  10: else {
  11:     child = nodelist[5].lastChild.previousSibling;
  12:     strText = child.textContent;
  13: }
  14: $("#response").append("<b>6th Node's Last Child: </b>" + strText);

Get The Parent Node

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: var strText = "";
   4: child = nodelist[5].parentNode;
   5: strText = child.tagName;
   6: $("#response").append("<b>6th Node's Parent Tag Name: </b>" + strText);

Node Types

nodeType returns the type of the node; 1 is an element node, 2 is an attribute and 3 is text.

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: // node type 1 = element
   4: var type = nodelist[7].nodeType;
   5: $("#response").append("<b>Node Type: </b>" + type + "<br>");
   6: // node type 2 = attribute
   7: var type = nodelist[7].childNodes[5].attributes[0].nodeType;
   8: $("#response").append("<b>Node Type: </b>" + type + "<br>");
   9: // node type 3 = text
  10: var type = nodelist[7].childNodes[0].nodeType;
  11: $("#response").append("<b>Node Type: </b>" + type + "<br>")

Get An Attribute Of An Element

   1: var root = xmlDoc.documentElement;
   2: var nodelist = root.getElementsByTagName('entry');
   3: // get an attribute
   4: if (window.ActiveXObject) {
   5:     var attr = nodelist[7].childNodes[3].getAttribute("type");
   6: }
   7: // In other browsers whitespace counts as child nodes
   8: else {
   9:     var attr = nodelist[7].childNodes[5].getAttribute("type");
  10: }
  11: $("#response").append("<b>Attribute: </b>" + attr);

XPath

The main interface to using XPath is the evaluate function of the document object.

The evaluate function takes a total of five parameters:

  • xpathExpression: A string containing the XPath expression to be evaluated.
  • contextNode: A node in the document against which the xpathExpression should be evaluated, including any and all of its child nodes. The document node is the most commonly used.
  • namespaceResolver: A function that will be passed any namespace prefixes contained within xpathExpression which returns a string representing the namespace URI associated with that prefix. This enables conversion between the prefixes used in the XPath expressions and the possibly different prefixes used in the document. The function can be either:
    Created by using the createNSResolver method of a XPathEvaluator object. You should use this virtually all of the time.
    null, which can be used for HTML documents or when no namespace prefixes are used. Note that, if the xpathExpression contains a namespace prefix, this will result in a DOMException being thrown with the code NAMESPACE_ERR.
    A custom user-defined function.
  • resultType: A constant that specifies the desired result type to be returned as a result of the evaluation. The most commonly passed constant is XPathResult.ANY_TYPE which will return the results of the XPath expression as the most natural type.
  • result: If an existing XPathResult object is specified, it will be reused to return the results. Specifying null will create a new XPathResult object.
   1: var root = xmlDoc.documentElement;
   2: var nsResolver = xmlDoc.createNSResolver(xmlDoc.ownerDocument == null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement);
   3: var xpathResult = xmlDoc.evaluate('count(//*)', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
   4: $("#response").append("<b>Number of Nodes: </b>" + xpathResult.numberValue + "<br>");
   5: // @ Selects attributes
   6: var xpathResult = xmlDoc.evaluate('count(//*/@*)', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
   7: $("#response").append("<b>Number of Nodes: </b>" + xpathResult.numberValue + "<br>");
   8: // . Selects the current node
   9: var xpathResult = xmlDoc.evaluate('count(.)', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
  10: $("#response").append("<b>Number of Nodes: </b>" + xpathResult.numberValue + "<br>");
  11: // / Selects from the root node
  12: var xpathResult = xmlDoc.evaluate('count(/)', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
  13: $("#response").append("<b>Number of Nodes: </b>" + xpathResult.numberValue + "<br>");
  14: // Predicates are always embedded in square brackets.
  15: var xpathResult = xmlDoc.evaluate('count(//*[1])', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
  16: $("#response").append("<b>Number of Nodes: </b>" + xpathResult.numberValue + "<br>");
  17: // node() Matches any node of any kind 
  18: var xpathResult = xmlDoc.evaluate('count(//node())', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
  19: $("#response").append("<b>Number of Nodes: </b>" + xpathResult.numberValue + "<br>");
  20: // NUMBER_TYPE = 1
  21: // A result containing a single number. This is useful for example, in an XPath expression using the count() function.
  22: $("#response").append("<b>Result Type: </b>" + xpathResult.resultType + "<br>");
  23: // //*[2] Selects every second child node of all the nodes
  24: var xpathResult = xmlDoc.evaluate('//*[2]', xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
  25: // UNORDERED_NODE_ITERATOR_TYPE = 4
  26: // A result node-set containing all the nodes matching the expression. 
  27: // The nodes may not necessarily be in the same order that they appear in the document.
  28: $("#response").append("<b>Result Type: </b>" + xpathResult.resultType + "<br>");
  29: var result = xpathResult.iterateNext();
  30: while (result) {
  31:     $("#response").append("<b>Child Node: </b>" + result.tagName + "<br>");
  32:     result = xpathResult.iterateNext();
  33: }

NOTE: This is not supported in Internet Explorer. There isn't much sample code to be found using this method and it gave me a lot of trouble. These are the only XPaths that worked for me.

Select Nodes

You can only use the functions selectNodes or selectSingleNode in Internet Explorer:

   1: var root = xmlDoc.documentElement;
   2: $("#response").append("<b>Feed Title: </b>" + xmlDoc.selectSingleNode("/feed/title").text + "<br>");
   3: // Items
   4: var entries = xmlDoc.selectNodes("/feed/entry");
   5: for (var i = 0; i < entries.length; i++) {
   6:     $("#response").append("<b>Entry ID: </b>" + entries[i].selectSingleNode("./id").text + "<br>");
   7: }

Client Side XSL Transformations

   1: <script type="text/javascript">
   2:     // Load XML 
   3:     var xml = new ActiveXObject("Microsoft.XMLDOM")
   4:     xml.async = false
   5:     xml.load("file.xml")
   6:     // Load XSL
   7:     var xsl = new ActiveXObject("Microsoft.XMLDOM")
   8:     xsl.async = false
   9:     xsl.load("file.xsl")
  10:     // Transform
  11:     document.write(xml.transformNode(xsl))
  12: </script>
More Posts