Using jsTree to get a Treeview with checkboxes in ASP.NET MVC

UPDATE 17 December 2011!

After one year this is still the most popular post on my blog. However, this uses jsTree v0.9.9a2 and jsTree is now in version v1.0 which has changed completely. This post does not work with jsTree v1.0!!!

I have written a post using the latest version of jsTree here

jsTree is a jQuery plugin for creating a treeviews, and jsTree’s checkbox plugin allows you to create a treeview with tri-state checkboxes, like so:

Notice how “Origination” appears half-checked because only some of its children are checked.

Getting started

For this demo I am using ASP.NET MVC 2 and jsTree v0.9.9a2. Let’s start with a new “ASP.NET MVC 2 Web Application”, name it jsTreeDemo, and add the required jsTree files to our solution:

In the View, create a div which you want to become a treeview. I’ll name mine demoTree. Also add references to the required jQuery and jsTree scripts:

Views/Home/Index.aspx

<asp:content id="Content2" runat="server" contentplaceholderid="MainContent">
 <h2><%: ViewData["Message"] %></h2>
 <div id="demoTree">
 </div>
 <script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
 <script src="../../Scripts/jquery.tree.js" type="text/javascript"></script>
 <script src="../../Scripts/plugins/jquery.tree.checkbox.js" type="text/javascript"></script>
</asp:content>

Next, create index.js, add a reference to it in Index.aspx, and add the following code:

Scripts/index.js

/// <reference path="http://ajax.microsoft.com/ajax/jQuery/jquery-1.4.1-vsdoc.js"/>

$(function () {
  $("#demoTree").tree({
   ui: {
     theme_name: "checkbox"
   },
   data: {
     type: "json",
     opts: {
       static: [
           {
              data: "Origination",
              children: [
                { data: "New Connection" },
                { data: "Disconnection" },
                { data: "Load Change" },
                { data: "Corporate" },
              ]
            },
            {
              data: "Confirm Application"
            }
       ]
     }
   },
   plugins: {
     checkbox: {}
   }
  });
});

This should create a basic treeview with checkboxes, with static data. Not very interesting yet.

Populating the tree with an AJAX request

Let’s create an ActionMethod to return some JSON data and use that data to populate our treeview. The jsTree documentation specifies how the JSON data should look but to help clarify, here’s an example:

[{"data":"Origination","attributes":{"id":"10"}",children":[
                         {"data":"New Connection","attributes":{"id":"11"}},
                         {"data":"Disconnection","attributes":{"id":"12"}},
                         {"data":"Load Change","attributes":{"id":"13"}},
                         {"data":"Corporate","attributes":{"id":"14"}}
                         ]},
{"data":"Confirm Application","attributes":{"id":"20"}}
...]

While it might be possible to write a LINQ query with anonymous types to return this data, it quickly gets rather hairy, so to simplify things let’s create a model to help us construct the data correctly. In the Models folder, add the following classes:

Models/JsTreeModel.cs

namespace jsTreeDemo.Models
{
 public class JsTreeModel
 {
   public string data;
   public JsTreeAttribute attributes;
   public JsTreeModel[] children;
 }

 public class JsTreeAttribute
 {
   public string id;
   public bool selected;
 }
}

In Controllers/HomeController.cs add the following function:

Controllers/HomeController.cs

[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GetTreeData()
{
 var tree = new JsTreeModel[]
 {
   new JsTreeModel {
     data = "Origination",
     attributes = new JsTreeAttribute  { id="10"},
     children = new JsTreeModel[]
     {
       new JsTreeModel { data = "New Connection", attributes = new JsTreeAttribute { id="11"} },
       new JsTreeModel { data = "Disconnection", attributes = new JsTreeAttribute { id="12", selected=true } },
       new JsTreeModel { data = "Load Change", attributes = new JsTreeAttribute { id="13"} },
       new JsTreeModel { data = "Corporate", attributes = new JsTreeAttribute { id="14", selected=true} },
     }
   },
   new JsTreeModel {
     data = "Confirm Application",
     attributes = new JsTreeAttribute { id="20" }
   },
   new JsTreeModel {
     data = "Things",
     attributes = new JsTreeAttribute  { id="30", selected=true },
     children = new JsTreeModel[]
     {
       new JsTreeModel { data = "Thing 1", attributes = new JsTreeAttribute { id="31"} },
       new JsTreeModel { data = "Thing 2", attributes = new JsTreeAttribute { id="32"} },
       new JsTreeModel { data = "Thing 3", attributes = new JsTreeAttribute { id="33"} },
       new JsTreeModel { data = "Thing 4", attributes = new JsTreeAttribute { id="34"} },
     }
   },
   new JsTreeModel {
     data = "Colors",
     attributes = new JsTreeAttribute  { id="40"},
     children = new JsTreeModel[]
     {
       new JsTreeModel { data = "Red", attributes = new JsTreeAttribute { id="41"} },
       new JsTreeModel { data = "Green", attributes = new JsTreeAttribute { id="42"} },
       new JsTreeModel { data = "Blue", attributes = new JsTreeAttribute { id="43"} },
       new JsTreeModel { data = "Yellow", attributes = new JsTreeAttribute { id="44"} },
     }
   }
 };

 return Json(tree);
}

Now we need to change our javascript to tell it to use GetTreeData:

Scripts/index.js


  $("#demoTree").tree({
   ui: {
     theme_name: "checkbox"
   },
   data: {
     type: "json",
     opts: {
         method: "POST",
         url: "/Home/GetTreeData"
     }
   },
...

Now our tree should be populated with the same data as Figure 1.

Determining which items are checked when posting

Let’s put our tree inside a <form> and submit it.

Views/Home/Index.aspx

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 <h2><%: ViewData["Message"] %></h2>

 <% using (Html.BeginForm("Submit", "Home", FormMethod.Post, new { id = "frmTree" }))
 { %>
   <div id="demoTree" style="height:300px">
   </div>
   <div>
     <input type="submit" value="Submit" id="btnSubmit" />
   </div>
 <% } %>

 <script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
 <script src="../../Scripts/jquery.tree.js" type="text/javascript"></script>
 <script src="../../Scripts/plugins/jquery.tree.checkbox.js" type="text/javascript"></script>
 <script src="../../Scripts/index.js" type="text/javascript"></script>

</asp:Content>

And now let’s add the Submit method to the HomeController

Controllers/HomeController.cs

[AcceptVerbs(HttpVerbs.Post)]
 public ActionResult Submit(FormCollection form)
 {
   return View(form);
 }

and the View

Views/Home/Submit.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<FormCollection>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 Submit
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 <h2>Submitted</h2>
 You chose:
 <% foreach (var item in Model)
    { %>
      <%: item %>
 <% } %>
</asp:Content>

If you press the Submit button, nothing will happen as nothing is passed through in the FormCollection to Submit() in HomeController. Despite appearances, the jsTree doesn’t actually render any HTML <input>s for the checkboxes. So we need to write some javascript to call jsTree’s get_checked() function to figure out which nodes are checked, and then add them to the form somehow. One way to add stuff to the form is to generate a hidden field, since hidden fields within a form are posted. This code generates hidden <input>s with the same name as each of the checked items, and adds them to the form.

Scripts/index.js

function generateHiddenFieldsForTree(treeId) {
  $.tree.plugins.checkbox.get_checked($.tree.reference("#" + treeId)).each(function () {
    var checkedId = this.id;
    $("<input>").attr("type", "hidden").attr("name", checkedId).val("on").appendTo("#" + treeId);
  });
}

and bind it to the form’s submit event:

Scripts/index.js

$(function () {
  $("#frmTree").submit(function () { generateHiddenFieldsForTree("demoTree"); });
}

Now when we submit our form, we should see the ids of the values which were checked:

What about telling the tree to pre-check some items?

So how do we render the tree with some items already checked? My colleague Paul came up with this problem and the solution. Notice how we added “public bool selected” to JsTreeAttribute? This doesn’t do anything as far as the checkboxes are concerned, but it does add a custom property called “selected” to each node’s <li>. We can use that to tell the jsTree to check the given node, by using the jsTree’s callback option.

Scripts/index.js


$("#demoTree").tree({
   ui: {
     theme_name: "checkbox"
   },
   data: {
     type: "json",
     opts: {
         method: "POST",
         url: "/Home/GetTreeData"
     }
   },
   plugins: {
     checkbox: {}
   },
   callback: {
     onload: function (tree) {
       $('li[selected=true]').each(function () {
         $.tree.plugins.checkbox.check(this);
       });
     }
   }
  });

You’ll see that the nodes that are marked with selected=true in GetTreeData() are now checked when you load the page.

Here’s a link to the solution (VS 2010) with code samples.

UPDATE 17 December 2011!

After one year this is still the most popular post on my blog. However, this uses jsTree v0.9.9a2 and jsTree is now in version v1.0 which has changed completely. This post does not work with jsTree v1.0!!!

I have written a post using the latest version of jsTree here

Advertisements

23 thoughts on “Using jsTree to get a Treeview with checkboxes in ASP.NET MVC

    • Really? That sucks. I thought there was a free (i.e. non-account) option… I wish I could upload files to wordpress.com. Maybe I should try put it on Google Docs or something.

      • not a paid or i miss have mis interpreted. I signed up for a free one. I thought they were some scam so i always said no. A lot of the virtual machines available are through them. Do they host pirated software? That’s an issue for me.

        I’m looking for a recessive tree loader i wrote a few years ago using recursive sql sp and then recursive c# loader. if i find t i’ll send it up if you like.

        Thanks
        KES

  1. In my MVC site (i’m using mvc 2)
    I keep getting an error
    $(“#demoTree”).tree is not a function
    error source line: [Break on this error] onload: function (tree) {

    has anyone run into this before? It’s got to be simple. I changed the controler action to my json controler action. Actually for now to debug i’m using your hard coded tree and ViewModel.

    I’m flipping mine to create a generic based on your design, but just want to debug this.

    Tnaks
    KES

    • It is something simple. You need to add a reference to jquery.tree.js in your view, i.e. add script src=”../../Scripts/jquery-1.4.1.js” etc to the end of Index.aspx, as per line 21 of my demo.

      -Matt

    • Are you using jsTree 1.0? I had similar problems as well at first but fixed the issues by referencing .jstree and modifying a few loops here and there to access the plugin functionality. For example:

      jQuery(“#your_selector_here”).jstree(“get_checked”).each(function () { …some logic… });

  2. Hi,

    Thanks for the article.

    I try to download the source code but always failed “IP address was modified” and I try to use static data it works fine, but once I try to use json it doesn’t show me anything, I debug the controller it returning correct data and there is no javascript error. any idea?

    Thanks

  3. Hi,

    This example is good. However i would like to add/remove/copy parent/child nodes on clicking add/remove/copy buttons.

    I tried using many ways to incorporate in your example above without any success.

    Can anyone show me a way how to acheive this?

    Mohan

  4. Hi,
    I add selected=true – a custom attr as you mentioned. But my tree DOM doesn’t show any custom attr! I used latest version of the plug-in. Please give me some advice

    • @huyn As I said at the very start of the post, this code doesn’t work with the latest version of jsTree. I wrote this post a year ago and jsTree has changed a lot since then.

  5. You can do this for the latest jstree version:

        $(".jstree").jstree({
                "plugins" : [ "themes", "html_data", "checkbox", "ui" ],
                "checkbox": {
                    "real_checkboxes": true,
                    "real_checkboxes_names": function (n) {
                        return [n[0].id, 1];
                    },
                    "two_state": true
                }
         }).bind("loaded.jstree", function (event, data) {
                $('li[selected=true]').each(function () {
                    $(this).removeClass('jstree-unchecked').addClass('jstree-checked');
                });
         });
    
    • Heh thanks Stephen.

      Bit of a shame that I only noticed your comment now, after I already spent a few hours getting up to speed with the latest jsTree and writing another blog post about it!

  6. i want to display tree with two nodes is root and a node of the level 2 on the first page when loaded? like a picture on the example…help me

Comments are closed.