TreeView Helper.

Requires MVCControlToolkit.Controls.Core-x.x.x.min.js,, MVCControlToolkit.Controls.ServerTreeview-x.x.x.css , MVCControlToolkit.Controls.ServerTreeview-x.x.x.js that are all included in the distribution.

Moreover if you need branch dragging, you must include either JQuery UI or the jquery-ui-interactions.min.js file  included in the distribution.

 

The TreeView Helper allows the rendering and editing of a hierarchical data structure. The nodes of this hierarchical data structure may have different Types, and may be rendered with different templates, where the choice of the templates is done at rendering time through a "template selection function". A similar "collection selection function" decide the collection to be used as source for the "children" of the node. All properties of a node but its children are rendered with the help of the selected template. The children of a node are recoursively rendered in a tree-like fashion. 

The TreeView helper works as a server control, that is, all Html is rendered on the server side. The advantages  are: better SEO performance, and easier communication with the server (standard form submit). The disadvantages are: dynamic loading of subtrees is quite difficult which, in turn, prevent  the use of very big trees, and in general a quite low javascript interactivity on the client side. A sophisticated Client TreeView is contained in the commercial version of the Toolkit (Data Moving Control Suite).

All nodes may have different types, also nodes belonging to the same collection. For instance in a file TreeView folders and documents may be represented with two different classes. Notwithstanding this they may be mixed in any location of the tree. However, if the tree is used in Edit mode, for security reasons, all nodes must implement the ISafeCreation interface. It is a pure declarative interface, no property or method needs to be implemented. All you have to do is just to put ISafeCreation as an ancesstor of the class. This way we declare to the model binder that the type can be safely created.

Both the edit templates and all the templates are passed as arrays of type object. In all operations templates are refferred to, through their indexes in such arrays.

When the TreeView is in edit mode all properties can be edited through the selected "edit templates". Moreover, nodes, or subtrees can be moved with the mouse and placed in a different location. When the View is posted all changes are reflected on the underlying data structures.

Below a tree in display mode:

 

And the same tree in edit mode: 

As you can see all labels are substituted by TextBoxes. The Node template in this case id very simple, but there is no limit on the structure of the templates. Also the overall structure of the tree can be changed, in particular one is not forced to use a file system style.

When in display mode labels are either links or clickable, and clicking them may trigger, different kind of actions. When in edit mode instead one can change all labels, add new nodes and move whole subtree to a different location by dragging it with the mouse, as shown by the images sequence below:

To move a child in a different position,  we select the checkbox of the new father, first, and then we move the child to the desired position among the children of the chosen father. 

A TreeView can be rendered either in display only mode, or in edit only mode or in edit/display mode. In the last case the user can switchs display/edit mode with the TreeViewToggleEditButtonFor whose signature is:

 

        public static MvcHtmlString TreeViewToggleEditButtonFor<VM, T>(
            this HtmlHelper<VM> htmlHelper,
            Expression<Func<VM, List<T>>> expression,
            string textOrUrlEdit,
            string cssClassEdit,
            string textOrUrlUndoEdit,
            string cssClassUndoEdit,
            string textOrUrlRedoEdit,
            string cssClassRedoEdit,
            ManipulationButtonStyle manipulationButtonStyle = ManipulationButtonStyle.Button)

 

The second parameter is the ViewModel property to be rendered with the ThreeView, the last parameter defines the type of button(standard button, link, image), while all others are text and Css styles to apply in the different states of the button. The ones ending in "Edit" are applied when the user may go in edit mode for the first time, the ones ending in "UndoEdit" are applied when the user may return in display mode, and finally the ones ending in "RedoEdit" allows the user go back in edit mode another time.

A Tree with all modifications applied by the user is returned to the controller only if the page is submitted when the TreeView is in edit mode. If the page is submitted with the TreeView is in display mode all chasnges are undone and the controller just get a null pointer, since in display mode data are not persisted.

Therefore, when we receive a null pointer we need to rebuild the Tree on the Server. Attention, if the user deletes all nodes in edit mode the controller receive an empty list, and not a null pointer!

In order to enable insertion/deletion of nodes when the TreeView is in edit mode we can add delete and add buttons to the templates involved. While delete nodes buttons can be applied to any template, add nodes buttons can be applied only to templates that are used by nodes that may have children, because once the add button of a node is clicked the new node id added as its child. Below, a a tree with delete and add buttons:

The above is just a possible way of arranging the buttons. We have the possibility to change images, the place where the buttons are, and so on, by adequately designing the node templates.

It is worth to note that the above tree has two kind of add buttons: one for adding a document node and the other for adding a folder node. In general the add button helper accepts the template to to use to for the new node as input(actually its index in the templates array)  . This way we can put different buttons in our templates one for each type of node that it is possible to add as child.

The signature of the delete button is:

 

public static MvcHtmlString TreeViewDeleteButton<VM>(
            this HtmlHelper<VM> htmlHelper,
            string textOrUrl,
             ManipulationButtonStyle manipulationButtonStyle = ManipulationButtonStyle.Button,
            IDictionary<string, object> htmlAttributes = null,
            string name = null)

 

The second parameter, is either the text to be used in the button, or the url of an image to use as button, the thrird parameter is an enumeration that specify the style of the button(standard button, lin button, and image button), the fourth parameter contains all Html attributes, and finally the optional name to give to the button.

The add button has on argument more, the index of the template to use for the new node:

 

public static MvcHtmlString TreeViewAddButton<VM>(
            this HtmlHelper<VM> htmlHelper,
            int templateToUse,
            string textOrUrl,
             ManipulationButtonStyle manipulationButtonStyle = ManipulationButtonStyle.Button,
            IDictionary<string, object> htmlAttributes = null,
            string name = null)

 

The signature of the TreeView helper is:

        public static MvcHtmlString TreeViewFor<VM, TItem>(
            this HtmlHelper<VM> htmlHelper,
            Expression<Func<VM,List<TItem>>> expression,
            Func<int, string>  collection,
            ExternalContainerType itemContainer=ExternalContainerType.span,
            string rootClassDisplay = null,
            object[] displayTemplates = null,
            Func<object, int, int> itemTemplateSelectorDisplay= null, 
            string rootClassEdit = null,
            object[] editTemplates = null,
            Func<object, int, int> itemTemplateSelectorEdit=null,
            TreeViewMode mode = TreeViewMode.InitializeDisplay,
            Func<int, string> itemClassSelector = null,
            Func<object, int, TreeViewItemStatus> itemStatus = null,
            TreeViewOptions treeViewOptions = null)

The second parameter is a list of data structures that will be displayed as the first level of children. Such children are fixed: they can be edited or deleted, but they cannot be moved. I suggest to use also a template with no delete button, since the represents the mai "categories" of the TreeView. The remainder of the tree is built by using recoursively children collections contained in the previous level nodes. The items of the various collections may have different Types.

The itemContainer parameter specifies an Html container where the html generated by the template of each node is inserted. Possible children of each node are inserted after this container into an <ul> container.

There is a set of parameters that is duplicated. One set is for display mode and the other is for edit mode. Namely:

Display Mode Edit Mode
rootClassDisplay rootClassEdit
displayTemplate editTemplates
itemTemplateSelectorDisplay itemTemplateSelectorEdit

 

displayTemplates, and editTempates are arrays containing the templates used to display the various nodes. For an introductions to templates, please see Use of Templates. Each template can be either a Razor helper, or a LambdaExpression or the name of a partial view; however, partial views can not be used if the template must be used to create new nodes. Each template can be strongly typed with a different type. When a node is created with and add button, it is given the data type of the template(templates must be strongly typed) referenced by the button.

If only displayTemplates is not null the TreeView is rendered in display only mode, if only editTemplates is not null the TreeView is rendered in edit only mode, finally if both are not null the TreeView is rendered in edit/display mode and a TreeViewToggleEditButtonFor must be used to toggle between the two modes. In case the TreeView is in edit/display mode, the treeViewMode parameter decides in which mode it is displayed initially:

  • treeViewMode == TreeViewMode.InitializeDisplay - In display mode the first time the View is rendered, then it remembers the state selected by the user.
  • treeViewMode == TreeViewMode.Display - In display mode .
  • treeViewMode == TreeViewMode.InitializeEdit - In edit mode the first time the View is rendered, then it remembers the state selected by the user.
  • treeViewMode == TreeViewMode.Edit - In edit mode.

rootClassDisplay and rootClassEdit are strings containing the Css classes to be applied to the TreeView. If they are not supplied the "treeview" class is automatically applied. The treeview class is applied allso if no class whose string starts with "treeview" is provided. A Css file with some class definitions is necessary for the correct operation of the TreeView. I added a slightly modified version of the Css file of the TreeView plugin jQueryplugin here. This file contains a definition of the "treeview"  that define the appearence of the toggler to open/close branches, and of the lines. The Css file contains also other variants you may try: "treeview-red",  "treeview-black", "treeview-gray", and "treeview-famfamfam"(that uses no lines) . It contains also the "filetree" class that is responsible for the appaearence of the folder and document icons, and that you must add to one of the previous classes.  The TreeView I used in the previous example has both rootClassDisplay and rootClassEdit set to "treeview-red filetree".

itemTemplateSelectorDisplay and itemTemplateSelectorEdit are functions that can also be defined on-line as lambda Expressions. They return the index of the template to use as a function of the current object and of the level in the tree where the object is located. In deciding the Type of each template consider that when a new node is created it has the same type as the one associated to the template.

The collection parameter is a function that returns a property where to find the children of each node. Such a property must be any Interface or Type that can accept a List<M> object. The property name is returned as a function of the template index chosen for the current node. If all nodes associated with that template can't have children this function returns null. This is different from the case of a node that may have children but that has currently no children. In the last case, the collection function must return the name of the property that may contain the children also if the value of this property is currently null or an empty collection.

The itemStatus function determines if a node that may have children is in rendered in open or cloded state, as a funtion of both the node object an the level of the Tree it is in. The value returned are:

  •  TreeViewItemStatus.initializeShow - open state the first time the View is rendered, then the TreeView remembers the state selected by the user.
  • TreeViewItemStatus.Show -open state .
  • TreeViewItemStatus.InitializeHide - closed state the first time the View is rendered, then the TreeView remembers the state selected by the user.
  • TreeViewItemStatus.Hide - closed state.

The TreeView can always remember the open/closed state selected by the user when it is in edit mode. When it is in display mode, instead, it remembers this state only if the persistency option is  selected in the treeViewOptions(see below).  

The itemClassSelector function associates a string represnting a class to each node , as a funtion of the index of the template selected for that node. Only nodes with the same class may exchange children. If no such a function is provided nodes can't exchange childrens. If you want all nodes(that may have children) exchange children among them, just make this function return a constant string.

Finally, the TreeViewOptions class allows the specification of some options. If it is not provided the default value of all option is used. The options that we may provide are:

 

        public uint Animated { get; set; }//default 1 millisecond
        public bool Unique { get; set; } //default false
        public float Opacity { get; set; } //default 1
        public bool CanMove { get; set; } // default true
        public bool CanAdd { get; set; } // default true
        public TreeViewPersistencyMode Persist { get; set; }// default edit only
        public string CookieId { get; set; }// default "treeview"

 

Animated set the duration of the animation that opens and closes the branches.

Unique, when true just one among all children of each node may be open. Opening a new node closes all its siblings.

Opacity is a float between 0 and 1 that specifies the opacity of the branches when they are dragged with the mouse.

CanMove, enable node moving with the mouse. It works only in edit mode.

CanAdd, enables the creation of new nodes with add buttons. It works only in edit mode, and if either unobtrusive or no client validation is on. Therefore, in Mvc 3 you may allow additions by selecting unobtrusive ajax and validation, while in Mvc 2 node addition is possible just when there is no client validation.

Persist. Normally the open/closed status of nodes is remembered only in the edit mode. When Persist is set to TreeViewPersistencyMode.Cookie, this status is persisted in a cookie and it is available also in display mode. The name of the cookie can be set by means of the CookieId property. When Persist is set to TreeViewPersistencyMode.Location, then the TreeView put automatically in the open state all nodes in the path of the link that was used to navigate to a new page. This option is useful when there are several Views containing the same TreeView and we use links contained in the branches of the TreeView to navigate them. 

Below the code of the folder and documents example that I used all over this page. In general there are two ways to handle the difference between two kinds of nodes(such as folders and documents): either using different data types or using different settings for some properties. In this example I used the boolean property IsFolder. Please notice that when we use property settings to distiguish among different nodes we have to put a fixed value for the involved properties in the various templates, in such a way to specialize them for a node also when they are created with an empty mode, that is when a new node is created: only in this way we can create different type of nodes. In my example I rendered the IsFalse into an hidden field with the constant value false for the documenttemplate and the constant value true for the folders template:

 

<div>
   
            @Html.TreeViewFor(
                    m => m.EmailFolders,
                    i => i == 0 ? "Children" : null,
                    ExternalContainerType.span,
                    "filetree treeview-red",
                    new object[] 
                    {
                        _S.L<EmailFolder>(h => 
                            "<span class='folder'>" +h.DisplayFor(m => m.Name).ToString()+"</span>"),
                            
                        _S.L<EmailDocument>(h => 
                            "<span class='file'>" +h.DisplayFor(m => m.Name).ToString()+"</span>") 
                    },
                    (x, y) => x is EmailFolder ? 0 : 1,
                    "filetree treeview-red",
                    new object[] 
                    {
                        _S.L<EmailFolder>(h => string.Format(
                            "<div class='folder' >{0} {1} {2} {3} {4}</div>", 
                            h.TypedEditDisplayFor(m => m.Name, simpleClick: true).ToString(),
                            h.TreeViewDeleteButton(Url.Content("~/Content/folder_delete_16.png"), 
                                ManipulationButtonStyle.Image).ToString(), 
                            h.TreeViewAddButton(1, Url.Content("~/Content/document_add_16.png"), 
                                ManipulationButtonStyle.Image).ToString(),
                            h.TreeViewAddButton(0, Url.Content("~/Content/folder_add_16.png"), 
                                ManipulationButtonStyle.Image).ToString(),
                            h.Hidden("IsFolder", true).ToString())),
                            
                        _S.L<EmailDocument>(h => string.Format(
                            "<div class='file' >{0} {1} {2}</div>",
                            h.TypedEditDisplayFor(m => m.Name, simpleClick: true, editEnabled: true).ToString(),
                            h.TreeViewDeleteButton(Url.Content("~/Content/document_delete_16.png"), 
                                ManipulationButtonStyle.Image).ToString(),
                            h.Hidden("IsFolder", false).ToString())) 
                    },
                    (x, y) => x is EmailFolder ? 0 : 1,
                    TreeViewMode.InitializeDisplay,
                    (x) => "allnodes",
                    (x, y) => TreeViewItemStatus.initializeShow)
                    
       
            
            </div>
            <div>@Html.TreeViewToggleEditButtonFor(
                 m => m.EmailFolders,
                 "Edit Folders", "TreeViewEdit",
                 "Undo Changes", "TreeViewUndo",
                 "Redo Changes", "TreeViewRedo")
            </div>

Last edited Jun 22, 2014 at 10:48 AM by frankabbruzzese, version 39

Comments

frankabbruzzese Feb 17, 2014 at 11:43 PM 
JohnBaley...It was an error..

ojemuyiwa,
The BinariesWithSimpleExamples project in the download area contains an Example TreevIew. There you may found all data structures referred in the example....No it is not System.IO.DirectoryInfo...but a custom class. Please post your quastions in the discussion area

ojemuyiwa Dec 17, 2013 at 2:28 PM 
Please can we have a sample project in order to use any of these controls? None of these make sense e.g. m => m.EmailFolders? What strongly typed Model is this refering to? certainly not System.IO.DirectoryInfo[]

JohnBailey Aug 27, 2013 at 9:11 PM 
Where this documentation is talking about templates, are the headings backwards? displayTemplate is under the Edit Mode header and editTemplates is under the Display Mode header. This is either incorrect or very counter-intuitive.