Persisting jsTree changes in Database

In my last posts I demonstrated how you can bind  jsTree with  database in ASP.NET MVC. In this post I'll  demonstrate following actions on jsTree nodes and save the changes in database in ASP.NET MVC.

  • Moving a node/Changing order (changing employee’s manager)
  • Adding a new node (adding new employee under a manager)
  • Renaming a node (Changing name of an employee)
  • Deleting a node (Removing an employee from the hierarchy)

Please note that jsTree has “callback” option attribute to define the callbacks required to achieve these tasks. It exposes several callbacks which can be used as per needed. Here are the different callbacks we’ll have to implement.

$("#empgraph").tree({
        data: {
            type: "json",
            url: "/Home/GetHiearchicalNodesInfo",
            async: true
        },
        rules: {
            //ommitted for brevity
        },
        callback: {  //skeleton of all required callbacks
             onmove: function(NODE, REF_NODE, TYPE, TREE_OBJ, RB) { },
             oncreate: function(NODE, REF_NODE, TYPE, TREE_OBJ, RB) { },
             ondelete: function(NODE, TREE_OBJ, RB) { }
             onrename: function(NODE, LANG, TREE_OBJ, RB) { }
        }
 });

Before implementing these callbacks we should know what information we require to be sent to server to perform these actions. Lets have a look on these actions one by one.

Moving Nodes/Changing Order of Nodes


image

To save the changed hiearchy we need node’s id and its parent’s id. onmove call back has several arguments , three of which NODE , REF_NODE and  TYPE are important to note, NODE has the reference of the moving node, REF_NODE is the one  it is moving toward. When TYPE will be “inside”, REF_NODE will be its parent node. Lets implement this.

onmove: function(NODE, REF_NODE, TYPE, TREE_OBJ, RB) {
    var parent_id = 0;

    if (TYPE === "inside") {
        parent_id = $(REF_NODE).attr('id');
    }
       saveHiearchy(NODE.id, parent_id);
    }

After getting child (node) id and parent, simply call a method that could make a POSt request on sever to save this changed information in database.


function saveHiearchy(node_id, parent_id) {
    var actionUrl = "/Home/SaveHiearchy?childId=" + node_id + "&parentId=" + parent_id;
    $.ajax(
        {
            type: "POST",
            url: actionUrl,
            data: null,
            dataType: "json",
            success: function(data) {
            },
            error: function(req, status, error) {
            }
        });
}  

Adding a new node

image

To create a node we need node’s parent’s id and the name of the node. oncreate callback also has the same arguments as onmove had, in this case hwe again require NODE , REF_NODE and TYPE  to create a new node as the following code shows.

oncreate: function(NODE, REF_NODE, TYPE, TREE_OBJ, RB) {
    var parent_id = 0;

    if (TYPE === "inside") {
        parent_id = $(REF_NODE).attr('id');
    }
    createNode(NODE, parent_id);
}

It is interesting to note when you when you’ll create a node, oncreate will fire and a node with name “New Folder” will be created in database. When you will enter your required name , the onrename will be called to update the previously saved name.. But to achieve this we have to send the newly created node’s id from server back to client and on onsuccess of  oncreate callback we have to save this id in some global variable to be used in rename callback; createdNodeId  is being assigned the id. Please note createNode node method gets the name associated with the node and passes the information to server.


function createNode(NODE, parentId) {
    var folderName = $(NODE.innerHTML).filter("a")[0].innerText;
    var actionUrl = "/Home/CreateFolder?folderName=" + folderName + "&parentId=" + parentId;
    $.ajax(
        {
            type: "POST",
            url: actionUrl,
            data: null,
            dataType: "json",
            success: function(data) {
                createdNodeId = data.nodeId;
            },
            error: function(req, status, error) {
            }
        });
}



Renaming a node

image

Renaming a node requires its id and the changed name. This information can be available as shown below.


onrename: function(NODE, LANG, TREE_OBJ, RB) {
    renameNode(NODE.id, $(NODE.innerHTML).filter("a")[0].innerText);
  }


Since this method also gets called when a new node is created; it is the case when node id is empty so use the createdNodeId otherwise it will have node id.

function renameNode(nodeId, nodeNewTitle) {
    if ($.trim(nodeId) == "")
    nodeId = createdNodeId;

    var actionUrl = "/Home/RenameNode?nodeId=" + nodeId + "&nodeNewTitle=" + nodeNewTitle;
    $.ajax(
        {
            type: "POST",
            url: actionUrl,
            data: null,
            dataType: "json",
            success: function(data) {
            },
            error: function(req, status, error) {
            }
        });
}


Deleting a node

image


Deleting a node is the simplest case, in this case we just need id of the node to be deleted.

ondelete: function(NODE, TREE_OBJ, RB) {
            deleteSubNode(NODE.id);
        }

The related ajax routine



function deleteSubNode(nodeId) {
    var actionUrl = "/Home/DeleteSubNode?folderId=" + nodeId;
    $.ajax(
    {
        type: "POST",
        url: actionUrl,
        data: null,
        dataType: "json",
        success: function(data) {
        },
        error: function(req, status, error) {
        }
    });
}

Here is the complete jsTree code, with callbacks implemented.

$("#empgraph").tree({
            data: {
                type: "json",
                url: "/Home/GetHiearchicalNodesInfo",
                async: true
            },
            ui: { theme_name: "classic" },
            rules: {
                metadata: "mdata",
                use_inline: true,
                clickable: ["all"],
                deletable: ["all"],
                renameable: ["all"],
                creatable: ["all"],
                draggable: ["all"],
                // dragrules: ["child * child", "child inside root", "tree-drop inside root"],
                createat: ["top"],
                drag_button: "left",
                droppable: ["tree-drop"]
            },

            callback: {

                onmove: function(NODE, REF_NODE, TYPE, TREE_OBJ, RB) {
                    var parent_id = 0;

                    if (TYPE === "inside") {
                        parent_id = $(REF_NODE).attr('id');
                    }
                    saveHiearchy(NODE.id, parent_id);
                },
                onrename: function(NODE, LANG, TREE_OBJ, RB) {
                    renameNode(NODE.id, $(NODE.innerHTML).filter("a")[0].innerText);
                },
                oncreate: function(NODE, REF_NODE, TYPE, TREE_OBJ, RB) {
                    var parent_id = 0;

                    if (TYPE === "inside") {
                        parent_id = $(REF_NODE).attr('id');
                    }
                    createNode(NODE, parent_id);
                },
                ondelete: function(NODE, TREE_OBJ, RB) {
                    deleteSubNode(NODE.id);
                }
            }
        });

Here are the corresponding controller actions, they can be implemented with your own data access layer or LINQ or any type ORM.


public ActionResult SaveHiearchy(string childId, string parentId)
{
    JsTreeDAO.SaveNodeRelationship(childId,parentId);

    return null; //you may return any success flag etc
}
 public ActionResult RenameNode(string nodeId, string nodeNewTitle)
 {
     JsTreeDAO.RenameNode(nodeId, nodeNewTitle);

     return null; //you may return any success flag etc
 }

 public JsonResult CreateFolder(string folderName, string parentId)
 {
     int newNodeId = JsTreeDAO.AddSubNode(parentId, folderName);

     return Json(new { nodeId = newNodeId });//id of newly created node is required for rename callback.
 }

 public ActionResult DeleteSubNode(string folderId)
 {
     JsTreeDAO.DeleteSubNode(folderId);

     return null; //you may return any success flag etc
 }

Please note that I have not provided the details of the DAO class being used here and also have not used LINQ in these examples just to keep things simple because LINQ does not have direct equivalent for "Hierarchy" common table expression, otherwise we would have to resort to LINQ extensions.

Hope this helps.

13 comments:

Nobi said...

Nice works!!!, Thanks.

Ravi Kumar Meesala said...

Hi ,

Your post was nice.you explained individual changes to persist at a time . Is there any way which completely upload the JSON of JSTree from Javascript to Servercode .

admit said...

Thank you, the post was very useful 4me!

Unknown said...

Thanks for the hints! Saved some time indeed.

Unknown said...

Hi,
thanks for your post, it was very useful, one only thing I wanted to ask you: what if the TYPE argument was different from "inside"?

Pierre said...

Great article!
Very helpful and concise.

I have the same question as the previous poster... What if the TYPE is different? I have tried but sometimes the id of the parent thrown is equal to zero especially when trying to insert a second child on a node.
Many thanks again for this great post!

Anonymous said...

Great!

I'm trying to save the tree in sql db over ajax -> php.
The node order is one problem to solve. You have to change the node ids in the jstree with every change in the structure.

Would be great if someone got an other solution!

DaveBowman said...

Very useful article, thank you. I wish Ivan would create similar examples in documentation for jsTree. It's all quite simple and logical, but you gotta work your way into the internals at first.

Amit Mishra said...

hi this is very good article ..but i tried it an i found it more interesting as well . somewhere i need to take your help i need some suggestion of yours as well i want to implement an jstree along with next and prev button on which i want jstree has an node that has link to separate pages and those node it will be dynamic added to database as well where navigation can be managed through next prev and node injstree will be highlighted and navigate to another page on click of next or prev page it should map to next and prev button

Anonymous said...

Callbacks dont work !!!!

Eli said...

Great!!!
How can I get the actial project files?

Thanks!,
Eli

Unknown said...

It's 2019 but I'm still getting benefit from this. Thank you.

investor_dude said...

Hello,
Thank you for sharing this code. It will be very helpful if you can share the Visual Studio project so we can download it and play around with it.

Thanks,

Post a Comment