The Infragistics Tree with a Context Menu

Stanimir Todorov / Wednesday, September 12, 2012

Introduction

 

igTree simplifies the presentation of your hierarchical data into a web-based tree structure, depicting familiar parent-child relationships between nodes of information. This blog post demonstrates how to achieve custom implementation of a context menu for the igTree. Our goal is to make a context menu that appears upon user interaction with the igTree. The context menu offers choices related to the selected node – new sibling node, new child node, edit node and delete node. The screenshot bellow demonstrates the final result.

 

clip_image001

 

Before you start

 

Before you start you must satisfy the prerequisites for working with NetAdvantage for jQuery and in particular with the Infragistics jQuery Tree:

Requirements:

· jQuery library (version 1.4.4 or newer)

· jQuery UI (version 1.8.1 or newer)

· NetAdvantage for jQuery 2012 Vol.2

 

Code Snippet
  1. // Initialize Infragistics loader
  2.         $.ig.loader({
  3.             scriptPath: "http://localhost/ig_ui/js/",
  4.             cssPath: "http://localhost/ig_ui/css/",
  5.             resources: "igTree,igDialog,igEditors,igValidator"
  6.         });

 

Create an igTree with a Context Menu

 

To create an igTree with a context menu, you need to accomplish the following steps:

· Initialize the igTree

· Databind the igTree to a data source

· Implement a custom context menu for the igTree

 

Initialize the igTree

 

Before initializing the igTree you must initialize the Infragistics loader. Paths to the JavaScript and CSS files are set to point to localhost. In order to load resources for the igTree, you need to set the resources option to the string “igTree”, after that pass a function to the Infragistics loader which initializes an instance of the igTree in an HTML DIV.

 

Code Snippet
  1. <script type="text/javascript">
  2.     // Initialize Infragistics loader
  3.     $.ig.loader({
  4.         scriptPath: "http://localhost/ig_ui/js/",
  5.         cssPath: "http://localhost/ig_ui/css/",
  6.         resources: "igTree"
  7.     });
  8.  
  9.     $.ig.loader(function () {
  10.         // Initialize igTree
  11.         $("#tree").igTree({
  12.             ...
  13.         });
  14.     });
  15. script>

 

In HTML:

Code Snippet
  1.     <div id="tree"><div>

 

Databind the igTree to a data source

 

The data source which is going to be used for data binding is a JSON object. The JSON object represents a simple table of contents from the Infragistics Help.

 

Code Snippet
  1. // JSON object represent Table of Content from the Infragistics Help
  2.             var toc = [{
  3.                 Text: "NetAdvantage jQuery 2012.2",
  4.                 Nodes: [{
  5.                     Text: "NetAdvantage for jQuery Overview"
  6.                 }, {
  7.                     Text: "What's New",
  8.                     Nodes: [{
  9.                         Text: "What's New in 2012 Volume 2"
  10.                     }, {
  11.                         Text: "What's New Revision History"
  12.                     }, {
  13.                         Text: "Touch Support for NetAdvantage for jQuery Controls"
  14.                     }]
  15.                 }, {
  16.                         Text: "Deployment Guide",
  17.                         Nodes: [{
  18.                             Text: "Using JavaScript Resouces in NetAdvantage for jQuery"
  19.                         }, {
  20.                             Text: "JavaScript Files in NetAdvantage for jQuery"
  21.                         }, {
  22.                             Text: "Styling and Theming NetAdvantage for jQuery"
  23.                         }, {
  24.                             Text: "Infragistics Content Delivery Network (CDN) for NetAdvantage for jQuery"
  25.                         }]
  26.                 }]
  27.             }];

 

To successfully databind the igTree, you have to set the dataSourceType option to “json”, the dataSource to the toc variable and set the bindings options - textKey to toc’s property Text and childDataProperty to toc’s property Nodes.

 

Code Snippet
  1. // Initialize igTree
  2.             $("#tree").igTree({
  3.                     dataSourceType: "json",
  4.                     dataSource: toc,
  5.                     bindings: {
  6.                         textKey: "Text",
  7.                         childDataProperty: "Nodes"
  8.                     }
  9.             });

 

The following screenshot demonstrate the result of these steps.

 

clip_image002

 

Implement a custom context menu for the igTree

 

The custom context menu is going to offer commands for creating new sibling nodes, new child nodes, editing nodes and deleting nodes.

For each command an HTML DIV element is created with class name being the corresponding command (this is used later to handle commands in JavaScript).

 

Code Snippet
  1.     <div class="context-menu">
  2.        <div class="new-node"><span>New Sibling Nodespan>div>
  3.        <div class="new-child-node"><span>New Child Nodespan>div>
  4.        <div class="edit-node"><span>Edit Nodespan>div>
  5.        <div class="delete-node"><span>Delete Nodespan>div>
  6.     div>

 

And the DIVs are wrapped in another DIV with a context-menu class applied. These elements are styled with the following CSS:

 

Code Snippet
  1. <style type="text/css">
  2.        .context-menu
  3.        {
  4.             border:1px solid #aaa;
  5.             position:absolute;
  6.             background:#fff;    
  7.             display:none;
  8.             font-size:0.75em;
  9.         }
  10.         
  11.        .context-menu span
  12.        {
  13.             width:100px;
  14.             display:block;
  15.             padding:5px 10px;
  16.             cursor:pointer;
  17.         }
  18.         
  19.        .context-menu span:hover
  20.        {
  21.             background:#3BB7EB;
  22.             color:#fff;
  23.        }
  24.     style>

 

The final step is to handle the right click of a tree node. In order to do that, you have to handle the contextmenu event when right click is fired on the node anchor. You have to prevent the default behavior of the browser (which is to open the default browser context menu). To get a reference to the tree node element, you would use the igTree API. Get the closest node from igTree through the method nodeFromElement and store it. Later, this node is going to help us with the command functionality.

It is time to build the context menu and shown it to the end-user. Grab the DIV with context-menu class via jQuery and store it in a variable. Then create overlay HTML div and append it to the body.

Note:The context menu has to have a zIndex greater than the zIndex of the overlay.

You would hide the element by subscribing to the click event of the overlay DIV.

 

Code Snippet
  1. // Context menu
  2.             var node = "";
  3.             $("body").delegate("li[data-role=node] > a", "contextmenu", function (evt) {
  4.                 evt.preventDefault();
  5.                 
  6.                 node = $('#tree').igTree('nodeFromElement',
  7.                     $(evt.target).closest('li[data-role=node]'));
  8.                     
  9.                 var cmenu = $(".context-menu");
  10.                 
  11.                 $("
    ").
  12.                     css({
  13.                         left: "0px",
  14.                         top: "0px",
  15.                         position: "absolute",
  16.                         width: "100%",
  17.                         height: "100%",
  18.                         zIndex: "100"
  19.                     }).
  20.                     click(function() {
  21.                         $(this).remove();
  22.                         cmenu.hide();
  23.                     }).
  24.                     appendTo(document.body);
  25.                 
  26.                 cmenu.
  27.                     css({
  28.                         left: evt.pageX,
  29.                         top: evt.pageY,
  30.                         zIndex: "101"
  31.                     }).
  32.                     show();
  33.                 return false;
  34.             });

 

And the result after right-clicking on the “What’s New in 2012 Volume 2” node from the igTree is illustrated in the following screenshot:

 

 

Add Functionality to the context menu commands

 

The context menu is now built. Next step is to implement the following commands – adding a new sibling node, adding a new child node, editing a node and deleting a node. In order to create the best end-user experience we also use other Infragistics components such as igDialog, igEditors, igButton, and igValidators.

Note: To load them in the project you need to add them in Infragistics loader option resources. Infragistics Button requires the igShared resource, but in our case it is not necessary to load, because it already loaded by the igTree.

 

Code Snippet
  1. // Initialize Infragistics loader
  2.         $.ig.loader({
  3.             scriptPath: "http://localhost/ig_ui/js/",
  4.             cssPath: "http://localhost/ig_ui/css/",
  5.             resources: "igTree,igDialog,igEditors,igValidator"
  6.         });

 

The click event is handled for each command in the context menu as follows:

 

Code Snippet
  1. // Add child node
  2. $("body").delegate(".context-menu .new-child-node", "click", function (evt) {
  3.     if ($(this).children().size() == 1 && node != "") {
  4.         // Add command functionality here                 
  5.     }
  6. });
 
Adding a New Sibling node

 

An HTML DIV element is created on the fly for the dialog window with an HTML INPUT and a BUTTON to save the new value from the input.

 

Code Snippet
  1. // Adding a new sibling node
  2. $("body").delegate(".context-menu .new-node", "click", function(evt) {
  3.     if( $(this).children().size() == 1 && node != "") {
  4.         // Add command functionality here
  5.         $('
    ').
  6.         appendTo("body");
  7.          
  8.     }
  9. });

 

The dialog window is initialized with a custom header text, custom image icon in the header and an event handler for the stateChanging event (if the action is close, dialog is destroyed and removed from DOM).

 

Code Snippet
  1. // Initialize igDialog
  2. $("#dialog").igDialog({
  3.     headerText: "Save new node",
  4.     imageClass: "ui-icon ui-icon-info",
  5.     width: "340px",
  6.     height: "110px",
  7.     stateChanging: function (evt, ui) {
  8.         // Check the igDialog state
  9.         if (ui.action === "close") {
  10.             $("#dialog").igDialog("destroy");
  11.             $("#dialog").remove();
  12.         }
  13.     }
  14. });

 

The editor is initialized with null text and required. The input is checked onblur and onchange whether it is valid.

 

Code Snippet
  1. // Initialize igEditor
  2. $("#newValue").igEditor({
  3.     nullText: "Enter new value",
  4.     required: true,
  5.     validatorOptions: {
  6.         onblur: true,
  7.         onchange: true,
  8.         onsubmit: true,
  9.         animationHide: 0
  10.     }
  11. });
  12.  
  13. $("#newValue").focus();

 

The button is initialized with the label text being “Save” and a click event handler is added. In the event handler we check whether the input is valid and if it is, a new sibling node is added via the addNode method of the igTree.

Note: To determine the parent node of a clicked node, you have to use the parentNode method of the igTree and pass the jQuery element of the clicked node.

After that the dialog is destroyed and removed from DOM.

 

Code Snippet
  1. // Initialize igButton
  2. $("#btnSave").igButton({
  3.     labelText: "Save",
  4.     click: function () {
  5.         if ($("#newValue").igEditor("validate")) {
  6.             $("#tree").igTree("addNode", {
  7.                 Text: $("#newValue").val()
  8.             },
  9.                 $("#tree").igTree("parentNode", node.element)
  10.             );
  11.             $("#dialog").igDialog("destroy");
  12.             $("#dialog").remove();
  13.         }
  14.     }
  15. });

 

The following code handles keyboard input while the editor is on focus. So, upon pressing the enter button, click is simulated on the save button.

 

Code Snippet
  1. // Submit on enter
  2. $("#newValue").keyup(function (event) {
  3.     if (event.keyCode == 13) {
  4.         $("#btnSave").click();
  5.     }
  6. });

 

Upon pressing of the escape button, the dialog is closed, destroyed and removed from DOM.

 

Code Snippet
  1. // Close igDialog and destroy on ESC
  2. $("#newValue").keyup(function (event) {
  3.     if (event.keyCode == 27) {
  4.         $("#dialog").igDialog("close");
  5.         $("#dialog").igDialog("destroy");
  6.         $("#dialog").remove();
  7.     }
  8. });

 

Finally, the context menu and overlay are hidden through this code:

 

Code Snippet
  1. $(".context-menu").hide();
  2.  
  3. $(".overlay").hide();

 

That is how you actually add the new sibling node:

 

Code Snippet
  1. // Add new sibling node
  2.             $("body").delegate(".context-menu .new-node", "click", function(evt) {
  3.                 if( $(this).children().size() == 1 && node != "") {                    
  4.                     $('
    ').
  5.                         appendTo("body");
  6.                     
  7.                     // Initialize igDialog    
  8.                     $("#dialog").igDialog({
  9.                         headerText: "Save sibling node",
  10.                         imageClass: "ui-icon ui-icon-info",
  11.                         width: "340px",
  12.                         height: "110px",
  13.                         stateChanging: function (evt, ui) {
  14.                             // Check the igDialog state
  15.                             if (ui.action === "close") {
  16.                                 $("#dialog").igDialog("destroy");
  17.                                 $("#dialog").remove();
  18.                             }
  19.                         }
  20.                     });
  21.                     
  22.                     // Initialize igEditor
  23.                     $("#newValue").igEditor({
  24.                         nullText: "Enter new value",
  25.                         required: true,
  26.                         validatorOptions: {
  27.                             onblur: true,
  28.                             onchange: true,
  29.                             animationHide: 0
  30.                         }
  31.                     });
  32.                     
  33.                     $("#newValue").focus();
  34.                     
  35.                     // Initialize igButton
  36.                     $("#btnSave").igButton({
  37.                         labelText: "Save",
  38.                         click: function () {
  39.                             if ($("#newValue").igEditor("validate")) {
  40.                                 $("#tree").igTree("addNode", {
  41.                                         Text: $("#newValue").val()
  42.                                     },
  43.                                     $("#tree").igTree("parentNode", node.element)
  44.                                 );
  45.                             
  46.                                 $("#dialog").igDialog("destroy");
  47.                                 $("#dialog").remove();
  48.                             }
  49.                         }
  50.                     });
  51.  
  52.                     // Submit on enter
  53.                     $("#newValue").keyup(function(event) {
  54.                         if(event.keyCode == 13){
  55.                             $("#btnSave").click();
  56.                         }
  57.                     });
  58.                     
  59.                     // Close igDialog and destroy on ESC
  60.                     $("#newValue").keyup(function(event) {
  61.                         if(event.keyCode == 27){
  62.                             $("#dialog").igDialog("close");
  63.                             $("#dialog").igDialog("destroy");
  64.                             $("#dialog").remove();
  65.                         }
  66.                     });
  67.                     
  68.                     $(".context-menu").hide();
  69.                     $(".overlay").hide();
  70.                 }
  71.             });

 

And the result:

 

clip_image003

 
Adding a New Child Node

 

The way a new child node is implemented is the same. The only difference is that you do not need to take the parent node of the node that has been click as in the previous command implementation. Just point to the jQuery element of the clicked node:

 

Code Snippet
  1. $("#tree").igTree("addNode", {
  2.     Text: $("#newValue").val()
  3. },
  4. node.element
  5. );

 

The following is the implementation for the new child node:

 

Code Snippet
  1. // Add child node
  2.             $("body").delegate(".context-menu .new-child-node", "click", function(evt) {
  3.                 if( $(this).children().size() == 1 && node != "") {
  4.                     $('
    ').
  5.                         appendTo("body");
  6.                     
  7.                     // Initialize igDialog                    
  8.                     $("#dialog").igDialog({
  9.                         headerText: "Save child node",
  10.                         imageClass: "ui-icon ui-icon-info",
  11.                         width: "340px",
  12.                         height: "110px",
  13.                         stateChanging: function (evt, ui) {
  14.                             // Check the igDialog state
  15.                             if (ui.action === "close") {
  16.                                 $("#dialog").igDialog("destroy");
  17.                                 $("#dialog").remove();
  18.                             }
  19.                         }
  20.                     });
  21.                     
  22.                     // Initialize igEditor
  23.                     $("#newValue").igEditor({
  24.                         nullText: "Enter new value",
  25.                         required: true,
  26.                         validatorOptions: {
  27.                             onblur: true,
  28.                             onchange: true,
  29.                             animationHide: 0
  30.                         }
  31.                     });
  32.                     
  33.                     $("#newValue").focus();
  34.                     
  35.                     // Initialize igButton
  36.                     $("#btnSave").igButton({
  37.                         labelText: "Save",
  38.                         click: function () {
  39.                             if ($("#newValue").igEditor("validate")) {
  40.                                 $("#tree").igTree("addNode", {
  41.                                         Text: $("#newValue").val()
  42.                                     },
  43.                                     node.element
  44.                                 );
  45.                                 
  46.                                 $("#dialog").igDialog("destroy");
  47.                                 $("#dialog").remove();
  48.                             }
  49.                         }                        
  50.                     });
  51.                     
  52.                     // Submit on enter
  53.                     $("#newValue").keyup(function(event) {
  54.                         if(event.keyCode == 13){
  55.                             $("#btnSave").click();
  56.                         }
  57.                     });
  58.                     
  59.                     // Close igDialog and destroy on ESC
  60.                     $("#newValue").keyup(function(event) {
  61.                         if(event.keyCode == 27){
  62.                             $("#dialog").igDialog("close");
  63.                             $("#dialog").igDialog("destroy");
  64.                             $("#dialog").remove();
  65.                         }
  66.                     });
  67.  
  68.                     $(".context-menu").hide();
  69.                     $(".overlay").hide();
  70.                 }
  71.             });

 

And the result:

 

clip_image004

 
Edit Node

 

The way node editing is implemented is identical. The only difference is that you need to update the node with the new value from the input.

 

Code Snippet
  1. $(node.element).
  2.                                     find("a:first").
  3.                                     text($("#newValue").
  4.                                     val()
  5.                                 );

 

Note:Text is changed only in the HTML on the client side.

The following is the implementation for the edit node:

 

Code Snippet
  1. // Edit node
  2.             $("body").delegate(".context-menu .edit-node", "click", function(evt) {
  3.                 if( $(this).children().size() == 1 && node != "") {
  4.                     // Note: Text is changed only on client side.
  5.                     // To change on the server side, you have to update data source.
  6.                     $('
    ').
  7.                         appendTo("body");
  8.                     
  9.                     // Initialize igDialog
  10.                     $("#dialog").igDialog({
  11.                         headerText: "Edit node text",
  12.                         imageClass: "ui-icon ui-icon-info",
  13.                         width: "330px",
  14.                         height: "110px",
  15.                         stateChanging: function (evt, ui) {
  16.                             // Check the igDialog state
  17.                             if (ui.action === "close") {
  18.                                 $("#dialog").igDialog("destroy");
  19.                                 $("#dialog").remove();
  20.                             }
  21.                         }
  22.                     });
  23.                     
  24.                     // Initialize igEditor
  25.                     $("#newValue").igEditor({
  26.                         value: node.element.children('a').text(),
  27.                         required: true,
  28.                         validatorOptions: {
  29.                             onblur: true,
  30.                             onchange: true,
  31.                             onsubmit: true,
  32.                             animationHide: 0
  33.                         }
  34.                     });
  35.                     
  36.                     $("#newValue").focus();
  37.                     
  38.                     // Initialize igButton
  39.                     $("#btnEdit").igButton({
  40.                         labelText: "Edit",
  41.                         click: function () {
  42.                             if ($("#newValue").igEditor("validate")) {
  43.                                 $(node.element).
  44.                                     find("a:first").
  45.                                     text($("#newValue").
  46.                                     val()
  47.                                 );
  48.                                 
  49.                                 $("#dialog").igDialog("destroy");
  50.                                 $("#dialog").remove();
  51.                             }
  52.                         }
  53.                     });
  54.                     
  55.                     // Submit on enter
  56.                     $("#newValue").keyup(function(event) {
  57.                         if(event.keyCode == 13) {
  58.                             $("#btnEdit").click();
  59.                         }
  60.                     });
  61.                     
  62.                     // Close igDialog and destroy on ESC
  63.                     $("#newValue").keyup(function(event) {
  64.                         if(event.keyCode == 27){
  65.                             $("#dialog").igDialog("close");
  66.                             $("#dialog").igDialog("destroy");
  67.                             $("#dialog").remove();
  68.                         }
  69.                     });
  70.  
  71.                     $(".context-menu").hide();
  72.                     $(".overlay").hide();
  73.                 }
  74.             });

 

And the result:

 

clip_image005

 
Delete Node

 

Upon deleting a run-time confirmation dialog is created and a node is created to confirm deleting of the clicked node.

 

Code Snippet
  1. $('

    ').
  2. appendTo("body");
  3. $("#message").html("Are you sure to delete " + node.element.children('a').text() + " permanently?");

 

The dialog window is implemented similarly to the previous commands.

 

Code Snippet
  1. // Initialize igDialog
  2. $("#dialog").igDialog({
  3.     headerText: "Delete node",
  4.     imageClass: "ui-icon ui-icon-trash",
  5.     width: "620px",
  6.     height: "150px",
  7.     stateChanging: function (evt, ui) {
  8.         // Check the igDialog state
  9.         if (ui.action === "close") {
  10.             $("#dialog").igDialog("destroy");
  11.             $("#dialog").remove();
  12.         }
  13.     }
  14. });

 

The button’s click event gets the node path of the clicked node and calls the removeAt method of the igTree to delete a node from the tree. After that the dialog is closed, destroyed and removed from the DOM.

Here is the code for deleting a node:

 

Code Snippet
  1. // Delete node
  2.             $("body").delegate(".context-menu .delete-node", "click", function(evt) {
  3.                 if( $(this).children().size() == 1 && node != "") {
  4.                     $('

    ').
  5.                         appendTo("body");
  6.                     
  7.                     $("#message").html("Are you sure to delete " + node.element.children('a').text() + " permanently?");
  8.                     
  9.                     // Initialize igDialog
  10.                     $("#dialog").igDialog({
  11.                         headerText: "Delete node",
  12.                         imageClass: "ui-icon ui-icon-trash",
  13.                         width: "620px",
  14.                         height: "150px",
  15.                         stateChanging: function (evt, ui) {
  16.                             // Check the igDialog state
  17.                             if (ui.action === "close") {
  18.                                 $("#dialog").igDialog("destroy");
  19.                                 $("#dialog").remove();
  20.                             }
  21.                         }
  22.                     });
  23.                     
  24.                     // Initialize igButton
  25.                     $("#btnOk").igButton({
  26.                         labelText: "OK",
  27.                         click: function () {
  28.                             $("#tree").igTree("removeAt", node.path);
  29.                             $("#dialog").igDialog("close");
  30.                             $("#dialog").igDialog("destroy");
  31.                             $("#dialog").remove();
  32.                         }
  33.                     });
  34.                     
  35.                     // Submit on enter
  36.                     $(document).keyup(function(event) {
  37.                         if(event.keyCode == 13){
  38.                             $("#btnOk").click();
  39.                         }
  40.                     });
  41.                     
  42.                     $(".context-menu").hide();
  43.                     $(".overlay").hide();
  44.                 }
  45.             });

 

And the result:

 

clip_image007

 

View the working sample here.