You are here:   Blog
Register   |  Login

 

Aug 16

Written by: Michael Washington
8/16/2015 6:10 PM  RssIcon

image

We can create a database driven tree with unlimited levels of nesting of child nodes in the LightSwitch HTML Client.

A Tree With Unlimited Child Nodes

image

If you have an application that only needs one level of nesting of entities, the following article may provide what you need: JQuery Mobile Tree Using Collapsible Sections and Dynamic Views in LightSwitch.

However, as covered in Rethinking The Help Desk:

“Unlimited levels of nested categories allow for hundreds of categories to be easily grouped and understood.”

This example demonstrates how this can be achieved in LightSwitch.

Walk-thru

image

When you run the application, the collapsible nodes are displayed.

Clicking on a node allows you to edit that node.

Clicking the New Node button allows you to create a new node.

image

When you create a new node, if you do not select a parent, it will be a root node. Otherwise, you can click the “+” icon on the search box to select a parent node.

You can also change the parent node to automatically move the selected node and any child nodes that it has.

You can also delete the selected node, however you must first delete any child nodes that it has first.

The Data

image

The data structure is simple. We have a single table called Node.

image

It has a single self-referencing relationship that allows us to associate a node to a parent.

This is important to understand, we don’t associate child nodes to a parent node, we associate a node to a single parent. This results in a collection of nodes that may have child nodes.

image

We can then right-click on the Screens folder in the HTML Client project and select Add Screen

image

We can create screens to allow us to edit the data.

image

We use the screens to add sample data.

jqTree

image

We will use the jQuery widget, jqTree to display the tree in the LightSwitch application.

image

To set it up, we add the jqtree.css and the tree.jquery.js to the LightSwitch HTML Client project, and add a reference to them in the default.htm file.

Providing Data to jqTree Using a Generic File Handler

image

To provide the data from the Node table to the jqTree control, we add a Generic File Handler to the Server project of the LightSwitch solution.

First we create two classes. DataNode to hold the data from the Node table, and TreeNode to contain the nested collection of nodes that will be provided to the jqTree control:

    #region Classes
    public class DataNode
    {
        public int Id { get; set; }
        public string NameName { get; set; }
        public int? NodeParentId { get; set; }
    }
    public class TreeNode
    {
        public int Id { get; set; }
        public string label { get; set; }
        public List<TreeNode> children { get; set; }
    }
    #endregion

 

ProcessRequest is the main method that gets the data and create the root nodes.

It calls the AddChildren method to populate the child nodes of each root node:

 

    public void ProcessRequest(HttpContext context)
    {
        using (var serverContext = ServerApplicationContext.CreateContext())
        {
            // Collection to hold final TreeNodes
            List<TreeNode> colTreeNodes = new List<TreeNode>();
            // Get all the Nodes from the database
            var colNodes = (from objNode in serverContext.DataWorkspace
                            .ApplicationData.Nodes
                            select new DataNode
                            {
                                Id = objNode.Id,
                                NameName = objNode.NameName,
                                NodeParentId = objNode.NodeParent.Id
                            }).Execute().ToList();
            // Loop through Parent nodes
            foreach (DataNode objNode in colNodes
                .Where(x => x.NodeParentId == null))
            {
                // Create a new Node
                TreeNode objNewNode = new TreeNode();
                objNewNode.Id = objNode.Id;
                objNewNode.label = objNode.NameName;
                objNewNode.children = new List<TreeNode>();
                colTreeNodes.Add(objNewNode);
                // Add Child Nodes
                AddChildren(colNodes, colTreeNodes, objNewNode);
            }
            // Create JavaScriptSerializer
            JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
            // Output as JSON
            context.Response.Write(jsonSerializer.Serialize(colTreeNodes));
        }
    }

 

The AddChildren method is called recursively to populate the child nodes of each node being currently processed:

 

    private void AddChildren(
        List<DataNode> colNodeItemCollection,
        List<TreeNode> colTreeNodeCollection,
        TreeNode paramTreeNode)
    {
        // Get the children of the current item
        // This method may be called from the top level 
        // or recuresively by one of the child items
        var ChildResults = from objNode in colNodeItemCollection
                            where objNode.NodeParentId == paramTreeNode.Id
                            select objNode;
        // Loop thru each Child of the current Node
        foreach (var objChild in ChildResults)
        {
            // Create a new Node
            var objNewNode = new TreeNode();
            objNewNode.Id = objChild.Id;
            objNewNode.label = objChild.NameName;
            objNewNode.children = new List<TreeNode>();
            // Search for the Node in colTreeNodeCollection
            // By looping through each root Node
            foreach (DataNode objNode in colNodeItemCollection
                .Where(x => x.NodeParentId == null))
            {
                // See if Parent is in the colTreeNodeCollection
                TreeNode objParent = 
                    colTreeNodeCollection.Where(x => x.Id == objNode.Id).FirstOrDefault();
                if (objParent != null) // Parent exists in the colTreeNodeCollection
                {
                    // Get the Parent Node for the current Child Node
                    TreeNode objParentTreeNode = objParent.Descendants()
                        .Where(x => x.Id == paramTreeNode.Id).FirstOrDefault();
                    if (objParentTreeNode != null)
                    {
                        // Add the Child node to the Parent
                        objParentTreeNode.children.Add(objNewNode);
                    }
                }
            }
            //Recursively call the AddChildren method adding all children
            AddChildren(colNodeItemCollection, colTreeNodeCollection, objNewNode);
        }
    }

 

The Descendants linq extension allows for the deep searching of child nodes:

 

    public static class Extensions
    {
        public static IEnumerable<TreeNode> Descendants(this TreeNode root)
        {
            var nodes = new Stack<TreeNode>(new[] { root });
            while (nodes.Any())
            {
                TreeNode node = nodes.Pop();
                yield return node;
                foreach (var n in node.children) nodes.Push(n);
            }
        }
    }

 

Display and Edit the Tree

image

The final step is to consume the data and bind it to the jqTree control.

First, we edit the JavaScript file that is attached to the screen:

 

// Create a Div to contain the tree
var TreeDiv = $("<div></div>");
var objScreen;
myapp.BrowseNodes.created = function (screen) {
    // Set a global for screen
    objScreen = screen;
};

 

image

In the screen designer, we add a Custom Control to the screen.

image

In the Properties for the control, we select it’s Render method.

We use the following code for the method. This will create an instance of the tree and create methods that will handle editing of the nodes.

The tree is filled with data in the UpdateTree method called at the end:

 

myapp.BrowseNodes.TreeDiv_render = function (element, contentItem) {
    // clear the element
    element.innerHTML = "";
    // Create the element
    var objElement = $(element);
    // Append the tree to the element  
    TreeDiv.appendTo(objElement);
    // Create the Tree
    TreeDiv.tree({
        autoOpen: true,
        dragAndDrop: false
    }).bind(
    'tree.select',
    function (event) {
        if (event.node) {
            // node was selected
            var node = event.node;
            var NodeId = node.Id;
            // Get the Node
            myapp.activeDataWorkspace.ApplicationData.Nodes_SingleOrDefault(NodeId)
                .execute().then(function (result) {
                    // Set the selected Node
                    objScreen.Nodes.selectedItem = result.results[0];
                    EditSelectedNode();
            });
        }
        else {
            // event.node is null
            // a node was deselected
            // e.previous_node contains the deselected node
        }
    });
    UpdateTree();
};

 

The UpdateTree method calls the Generic File Handler and populates the tree:

 

function UpdateTree() {
    // Get the data for the Tree
    $.getJSON(
    '/Web/TreeData.ashx',
    function (data) {
        TreeDiv.tree('loadData', data);
    });
}

 

When a node is being edited, the following method is called:

 

function EditSelectedNode() {
    myapp.showAddEditNode(null, {
        beforeShown: function (addEditScreen) {
            addEditScreen.Node = objScreen.Nodes.selectedItem;
        },
        afterClosed: function (addEditScreen, navigationAction) {
            // If the user commits the change, refresh the Tree
            if (navigationAction === msls.NavigateBackAction.commit) {
                UpdateTree();
            }
        }
    });
}

 

When a new node is created, the following method is called:

 

myapp.BrowseNodes.NewNode_execute = function (screen) {
    myapp.showAddEditNode(null, {
        beforeShown: function (addEditScreen) {
            // Create new Node here so that
            // discard will work.
            var newNode = new myapp.Node();
            // If there is a currently selected Node
            // set is as the parent node
            if (screen.Nodes.selectedItem !== null) {
                newNode.NodeParent = screen.Nodes.selectedItem;
            }
            addEditScreen.Node = newNode;
        },
        afterClosed: function (addEditScreen, navigationAction) {
            // If the user commits the change, refresh the Tree
            if (navigationAction === msls.NavigateBackAction.commit) {
                UpdateTree();
            }
        }
    });
};

 

Links

JQuery Mobile Tree Using Collapsible Sections and Dynamic Views in LightSwitch

Creating Advanced LightSwitch HTML Screen Templates

The LightSwitch Data Binding Framework Using EaselJS

How Does A LightSwitch HTML Client Application Work?

HUY Volume II - Visual Studio LightSwitch Advanced JavaScript Examples

Walk-thru Examples of Common Visual Studio LightSwitch JavaScript

 

Download Code

The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

You must have Visual Studio 2015 (or higher) with LightSwitch installed to run the code (if you have Visual Studio Community Edition see How To Get Visual Studio LightSwitch For Free)

Tags: Advanced
Categories:

3 comment(s) so far...


Gravatar

Re: LightSwitch HTML Tree Administrator

Brilliant!

Thanks Michael for pushing LS forward.

By Johan on   8/17/2015 3:41 AM
Gravatar

Re: LightSwitch HTML Tree Administrator

Thanks Michael, this looks so promising!
However, I'm a novice user and have some difficulties converting the generic file handler from c# to vb. Would you have a vb version available too?
Thanks!

By MarcPadros on   5/25/2016 4:19 AM
Gravatar

Re: LightSwitch HTML Tree Administrator

@MarcPadros - Sorry no VB samples :(

By Michael Washington on   5/25/2016 4:19 AM
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation