<CharlieDigital/> Programming, Politics, and uhh…pineapples

5Apr/10Off

jsTree and Nested XML Data Stores

I happened upon jsTree a few months back while searching for a solid jQuery based tree.

Without a doubt, it is one of the most well implemented and functional Javascript trees I've used with perhaps the most powerful feature being the built-in support for client-side XML representations of the tree and the ability to add arbitrary metadata to the tree using the Sarissa library.

While the tree itself is extremely powerful, some of the documentation is actually out of date and made my implementation of the tree a bit more tasking than it should have been.

For example, the option for initializing the tree with a static XML string (as demonstrated here) actually requires using staticData instead of static in the latest version.  Adding metadata to the tree is also made far more complicated by the documentation and examples I found online.

In reality, the datastore implementation for the nested XML support is powerful enough to extract arbitrary DOM attributes (online posts seem to indicate that you need to use the custom metadata plugin and/or the jQuery metadata plugin).  You can see in the sample below, that I set the attribute "md" to an encoded JSON string (you'll want to do this for when you reload the tree as a Javascript string) and then retrieve the value as a part of the XML by specifying the attributes to collect:

/*--- test adding data ---*/
$("#test").click(function() {
    var t = $.tree.focused();

    if (!t.selected) {
        return;
    }

    /*--- sets a person on the node ---*/
    var person = {
        "FirstName": $("#firstName").val(),
        "LastName": $("#lastName").val(),
        "Age": $("#age").val() 
    };

    var serialized = JSON.stringify(person);

    /*--- sample of adding an arbitrary attribute at the DOM level ---*/
    t.selected.attr("md", encodeURI(serialized));

    /*--- ...and how to retrieve it in XML ---*/
    var opts = {};
    opts.outer_attrib = ["id", "rel", "class", "md"];

    var xml = t.get(null, "xml_nested", opts)

    $("#xml-d").text(xml);
    $("#treeXml").val(encodeURI(xml));              
});

In real usage, you'd assign some more meaningful values to the metadata and save it.  I find that with a library like this, it's probably easier to just save the whole XML string and that's simple enough by just pushing the XML to a hidden input before submitting the form (as I've done in the last line).

Mahr Mohyuddin has a much more complex and more generic implementation of ASP.NET integration here, but I think that might be more complexity than is needed.  In practice, it makes more sense to use full JSON objects on the client side (as I've used above) and embed them into the attribute and then, using the JavaScriptSerializer class, extract the objects into domain objects on the server side.  Here's an example:

string xmlString = Uri.UnescapeDataString(treeXml.Value);

XDocument xml = XDocument.Parse(xmlString);

// Get all the <items/>.
var items = from x in xml.Descendants()
            where x.Name == "item"
            select x;

List<Person> people = new List<Person>();

JavaScriptSerializer serializer = new JavaScriptSerializer();

// Resolve the paths and Person instances.
foreach(var item in items) {
    string[] parts = item.AncestorsAndSelf()
        .Select(a => a.Descendants("name").First().Value)
        .Reverse().Skip(1).ToArray();

    string path = string.Join("/", parts);

    if(item.Attribute("md") == null) {
        continue; // Next iteration.
    }

    string serializedPerson = 
        Uri.UnescapeDataString(item.Attribute("md").Value);

    Person p = serializer.Deserialize<Person>(serializedPerson);
    p.OrgPath = path;

    people.Add(p);
}

_people.DataSource = people;
_people.DataBind();

Nothing fancy here; the only thing of note is the little LINQ query to resolve the node path (may or may not be useful).

The Person class is also very simple and barebones:

using System;

namespace JsTreeSample {
    /// <summary>
    /// Models a person.
    /// </summary>
    /// <remarks>
    /// Serializable to support deserialization from JSON.
    /// </remarks>
    [Serializable]
    public class Person {
        private string _orgPath;
        private int _age;
        private string _firstName;
        private string _lastName;

        public string FirstName {
            get { return _firstName; }
            set { _firstName = value; }
        }

        public string LastName {
            get { return _lastName; }
            set { _lastName = value; }
        }

        public int Age {
            get { return _age; }
            set { _age = value; }
        }

        public string OrgPath {
            get { return _orgPath; }
            set { _orgPath = value; }
        }
    }
}

The full project is included.  Some usage notes: select a node first and then enter a first name, last name, and age.  Then click "Set Data" to create a Person object at the node (this will also show the full XML of the tree).  Then click Submit to send the data (displays in a repeater).

JsTreeSample.7z (144.07 KB)

Definitely check out jsTree for your next project; it's amazingingly versatile and rich in functionality.

Posted by Charles Chen

Filed under: Dev, DevTools Comments Off
Comments (1) Trackbacks (0)
  1. Go for it.

    I am currently not on Twitter; my mind isn’t interesting enough (or irreverent enough) for it.


Trackbacks are disabled.