ASP.NET MVC–Displaying a tree view using a recursive declarative helper and jQuery


Problem

  • You want to display your model using a a nested <ul>, <li> structure

  • You want to be able to click on a leaf and perform an action

Context

We will use the model defined here and in order to hold our nested structure we will define a class named Folder:

public class Folder
{
    public Folder( )
    {
        this.Subfolders = new List<Folder>( );
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Type { get; set; }
    public IList<Folder> Subfolders { get; private set; }
    public bool IsLeaf
    {
        get
        {
            return this.Subfolders.Count == 0;
        }
    }
}

There is nothing complicated about this class. It has some properties and a list of subfolders.

Let’s create a controller called TreeViewController:

public partial class TreeViewController : Controller
{
    private readonly IContinentRepository _continentRepository;
    private readonly ICityRepository _cityRepository;

    // If you are using Dependency Injection, you can delete the following constructor
    public TreeViewController( ) : this( new ContinentRepository( ) , new CityRepository( ) ) { }

    public TreeViewController( IContinentRepository continentRepository , ICityRepository cityRepository )
    {
        this._continentRepository = continentRepository;
        this._cityRepository = cityRepository;
    }

    [HttpGet]
    public virtual ActionResult Index( )
    {
        return View( this.Atlas( ) );
    }

    [HttpGet]
    public virtual ActionResult GetCity( int cityId )
    {
        var city = this._cityRepository.Find( cityId );

        return Json(
            new { Name = city.Name , Population = city.Population } ,
            JsonRequestBehavior.AllowGet );
    }

    private IList<Folder> Atlas( )
    {
        IList<Folder> continents = new List<Folder>( );

        foreach ( var continent in this._continentRepository.All )
        {
            var continentFolder = new Folder
            {
                Id = continent.Id ,
                Name = continent.Name ,
                Type = "continent"
            };

            foreach ( var country in continent.Countries )
            {
                var countryFolder = new Folder
                {
                    Id = country.Id ,
                    Name = country.Name ,
                    Type = "country"
                };

                foreach ( var city in country.Cities )
                {
                    var cityFolder = new Folder
                    {
                        Id = city.Id ,
                        Name = city.Name ,
                        Type = "city"
                    };

                    countryFolder.Subfolders.Add( cityFolder );
                }

                continentFolder.Subfolders.Add( countryFolder );
            }

            continents.Add( continentFolder );
        }

        return continents;
    }
}

We have a Index action that will return our view. The Index view receives a list of continents, countries and cities wrapped in a nested Folder structure (the Atlas function is just a quick and dirty way of creating the nested Folder structure). There is also a GetCity action that will return the city information when we will click it in the tree view.

Index.cshtml:

@model IEnumerable<Folder>
@helper TreeView( IEnumerable<Folder> folders )
{
    foreach ( var folder in folders )
    {
        <li>
            @if ( folder.IsLeaf )
            {
                <span class="leaf @folder.Type" id="@folder.Id">@folder.Name</span> 
            }
            else
            {
                <span class="folder">@folder.Name</span>
                <ul>
                    @TreeView( folder.Subfolders )
                </ul>
            }
        </li>
    }
}
<ul id="continentFolders">
    @TreeView( Model )
</ul>

The @helper keyword  is a new feature introduced by the Razor view engine, called a declarative helper (for a comparison of MVC 3 Helpers see Jon Galloway’s article in the References section). The FolderTree helper is nothing more than a recursive function that will output <ul> and <li> tags based on the folders parameter.

If we run this now it will look like this:

Adding a jQuery Tree view plugin

We can make our tree view more functional by using a jQuery tree plugin (see the References section):

<script type="text/javascript">
    $(document).ready(function () {
        $('#continentFolders').treeview({ collapsed: true });

        $(".leaf.city").click(function () {
            $.ajax({
                url: '@Url.Action( MVC.Various.TreeView.GetCity( ) )',
                data: { cityId: $(this).attr("id") },
                type: 'GET',
                success: function (data) {
                    alert("Selected city: " + data.Name + ", population: " + data.Population.toString());
                }
            });
        });
    });
</script>

when a city is clicked we make an ajax request to the GetCity action and show some city details.

See it in action

ASP.NET MVC 3 - TreeView

References

Comparing MVC 3 Helpers, by Jon Galloway | jQuery plugin: Treeview | Another implementation I have found, by Roberto Hernández

Download

Download code

 

8 Comments

Comments have been disabled for this content.