Help to save a modified treeview

Feb 24, 2012 at 7:13 PM

Any of you guys have an example of saving the model associated to a treeview after this has been modified.

Any help will be really appreciated.

Coordinator
Feb 25, 2012 at 12:48 PM

The way to save the rcursive model of a TreeView depends on the kind of Tree you use and if you are using an ORM or not.

Below I will sketch an algorithm for the case all nodes of the tree are of the same type and accordingly can be saved in the same database table.

Firts of all You need a Table with a one to many relation with itself to represent the relation father-children. Moreover if you would like to remember also the exact position of each child in the children list of a node your table must have an Order column to store this information as an integer.

Accordingly, your DTO objectcs will have the following properties, or similar properties:

public class TreeNode{

public int Position {get; set;}

public int Id {get; set;} //principal key

public int FatherId {get; set;} //principal key of the father 

Publich virtual ICollection<TreeNode> Children {get; set;}

public string TreeName {get; set;}

}

For an efficient interaction with the orm YOU MUST NOT MANIPULATE the Children collection directly...otherwise the ORM will try to modify automatically the FatherId of the children...in a not very efficient way. YOU MUST HANDLE DIRECTLY the FatherID property that is the column actually stored in the database...The Children collection is mantained for you by the ORM...Don't touch it...the Orm don't know your algorithm so it will not behave in a cooperative way!

I supposed also that each node has a property that says the Tree it belongs to ...this simplify the algorithms.

That said, the algorithm is as follows:

 

  1. Open a new DB Context, let call it: context. Suppose also the name of the database table is TreeTable.
  2. Retrieve the old copy of the tree from the database with something like var allOldNodes= for x in context.TreeTable select x where x.TreeName == "MyTree";
  3. Put all retrieved node into a dictionary indexed by their principal key. In the example above you need a Ditionary<int, TreeNode>
  4. Do a recursive visit to the new tree. The recursive call must pass to each child the key of the father, and the position of the child(you know all this when you are calling the recursive visit function from a father on one of its children) and for each node N of the tree you do what follows:
    1. Look up in the dictionary the old node with the same principal key of N
      1. If you dont't find it you must Add N as a new node of the tree. Create a new DTO node fill its field with the properties of N. Fill also FatherKey, and Position, using the parameters passed to the recursive call. Add the new DTO node to the table context.TreeTable.
      2. if you find it, copy all data of N into it, and copy also also FatherKey, and Position, from the parameters passed to the recursive call(its father or Position might have changed).
    2. Remove the key, value pair(if you found it...) from the Dictionary, to remember you have processed the old node.
  5. At the end of the visit, all old nodes that were also in the New tree have been removed from the dictionary, so the dictionary contains just the Old nodes that need to be removed. Do a for loop an all entries of the ditionary and delete all DTO objects you find from context.TreeTable.
  6. Do a SaveChanges on context to persist all changes to the DB.

 

 

 

 

Feb 27, 2012 at 5:00 PM

Thanks Frank but what I really need is on how to send the modified tree back to the controller, there is not any example about that. The documentation says that the treeview is sent ack to the server in the model when it is in edit mode, but it is not working like that for me.

Coordinator
Feb 27, 2012 at 5:16 PM

An example of the TreeView working is the BinariesWithSimpleExamples. Run the example WITHOUT debugging (otherwise it is very slow because there are a lot of js scripts in the page), and you will see that after you submit the page...the controller will built again the same tree with all modifications you have done. The tree arrive to the controller ONLY if it is in EDIT mode. You can put it in Edit mode or displaying it directly in edit mode or with the edit/display toggle button. If when you submoit the page the tree is in display mode...no information is sent to the controller and after the submit you will get a null tree in the refreshed page.

For security reasons all TreeView nodes MUST implement the interface ISafeCreation. It is a dummy inteface you don't need to implement any member, but just to make your class inherit from it. This says to the model binder that it can safely create an instance of that class. Probably you have not implemented this interface...and consequentely the model binder reject all nodes...and you get a null tree. Whitout the ISafeCreation interface since the each tree node may belong to a different class, and since the information on the type to create is stored on the clent side, a malicious user might change this type info, forcing the creation of a "dangerous" type...performing this way an attack.

Below the code that you will find also the file  BinariesWithSimpleExamples, that show how to create an edit/display tree. As you can see there is also an edit display toggle.

<%:
                Html.TreeViewFor(
                    m => m.EmailFolders,
                    i => i == 0 ? "Children" : null,
                    ExternalContainerType.span,
                               "filetree treeview-red treeToedit",
                    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 treeToedit",
                    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><input id="Button10" type="button" value="button" />
           
             
            <div><%:Html.TreeViewToggleEditButtonFor(
                 m => m.EmailFolders,
                 "Edit Folders", "TreeViewEdit",
                 "Undo Changes", "TreeViewUndo",
                 "Redo Changes", "TreeViewRedo")
                 %></div>

Feb 27, 2012 at 5:22 PM

I have a treeview working and my class is implementing ISafeCreation

    public class NavigationItemModel : ISafeCreation    {        public int? PageId { get; set; }
        public string MenuLabel { get; set; }
        public int NavigationId { get; set; }
        public int LayoutId { get; set; }
        public List<NavigationItemModel> Children { get; set; }    }

The problem is when I send the data back to the server I got a null object. The example is good but it does not show how to save the modification on the treeview (adding nodes, deleting nodes, etc)

Coordinator
Feb 27, 2012 at 5:48 PM

"The example is good but it does not show how to save the modification on the treeview (adding nodes, deleting nodes, etc)"

It is automatic! the controller receive a tree of objects! 

 

Suppose that in your case your ViewModel as a property:

 

public List< NavigationItemModel> MyTree {get; set;}

 

The Action where you submit your page MUST HAVE a property with the same name and same type:

public List< NavigationItemModel> MyTree {get; set;}


This is all you need to do to have your treeview saved on the server.

 

This property will be automatically bound with the modified tree when your page is submitted.

IF THE TWO POPERTTY NAMES DO NOT MATCH OR IF YOU SAVE THE TREEVIEW IN DISPLAY MODE YOU WILL GET A NULL TREE ON THE CONTROLLER.

Is your Tree in a form that is submitted to the controller? Or is it out of the form you submit?...

 


Feb 27, 2012 at 6:12 PM

The treeview is in the form I submit to the server.

 

These are the classes I'm using for creating the tree:

    public class NavigationModel    {        public List<NavigationItemModel> NavigationItems { get; set; }
          }

    public class NavigationItemModel : ISafeCreation    {        public int? PageId { get; set; }
        public string MenuLabel { get; set; }
        public int NavigationId { get; set; }
        public int LayoutId { get; set; }
        public List<NavigationItemModel> Children { get; set; }    }

 

This is the treeview:

 

@model DWSContentBuilder.ValueObjects.Models.NavigationModel

<h2>
	Navigation Builder
</h2>
<br />
<br />
<br />
<br />
<br />
<br />
<h1> Navigation </h1>
@using (Html.BeginForm("NavigationBuilder", "ContentBuilder", FormMethod.Post))
{
<div class="navigation-builder-frames">
	<div class="div-table-row">
		<div class="navigation-tree-view">
			@Html.TreeViewFor(
                                                    m => m.NavigationItems,
                                                    i => i == 0 ? "Children" : null,
                                                    ExternalContainerType.span,
                                                    "filetree treeview-red",
                                                    new object[]
													{
														_S.L<ValueObjects.Models.NavigationItemModel>(h =>
															"<span class='folder' id='" + h.DisplayFor(m => m.PageId).ToString() + "_" + h.DisplayFor(m => m.LayoutId).ToString() + "' >" +h.DisplayFor(m => m.MenuLabel).ToString()+"<></span>"),
														},
                                                        (x, y) => x is ValueObjects.Models.NavigationItemModel ? 0 : 1,
                                                        "filetree treeview-red",
                                                        new object[]
														{
															_S.L<ValueObjects.Models.NavigationItemModel>(h => string.Format(
															"<div class='folder' >{0} {1} {2} {3} {4}</div>",
															h.TypedEditDisplayFor(m => m.MenuLabel, simpleClick: true).ToString(),
															h.TreeViewDeleteButton(Url.Content("~/Content/images/folder_delete_16.jpg"),
															ManipulationButtonStyle.Image).ToString(),
															h.TreeViewAddButton(1, Url.Content("~/Content/images/document_add_16.jpg"),
															ManipulationButtonStyle.Image).ToString(),
															h.TreeViewAddButton(0, Url.Content("~/Content/images/folder_add_16.jpg"),
															ManipulationButtonStyle.Image).ToString(),
															h.Hidden("IsFolder", true).ToString())),
															},
                                                        (x, y) => x is ValueObjects.Models.NavigationItemModel ? 0 : 1,
                                                        TreeViewMode.InitializeDisplay,
                                                        (x) => "allnodes",
                                                        (x, y) => TreeViewItemStatus.initializeShow,
                                                            new TreeViewOptions()
                                                            {
                                                                Animated = 1,
                                                                Unique = false,
                                                                Opacity = 1,
                                                                CanMove = true,
                                                                CanAdd = true,
                                                                Persist = TreeViewPersistencyMode.EditOnly,
                                                                CookieId = "treeview"
                                                            })
		</div>
		<div id="LayoutDiv" class="navigation-layout-view">
		</div>
		<div id="contentsDiv" style="display: none;">
		</div>
	</div>
</div>
<div>@Html.TreeViewToggleEditButtonFor(
                                                m => m.NavigationItems,
                                                "Edit Folders", "TreeViewEdit",
                                                "Undo Changes", "TreeViewUndo",
                                                "Redo Changes", "TreeViewRedo")
</div>
Here's the submit button
}

This is the Action on the controller:

        [HttpPost]
        public ActionResult NavigationBuilder(NavigationModel model)
        {
            var x = model;
            return View();
        }

I got null all the times for 'model'

 

What I am missing?

 

Thanks so much for your help Frank

Coordinator
Feb 27, 2012 at 6:36 PM

Since you have just 1 type of template  you don't need 

(x, y) => x is ValueObjects.Models.NavigationItemModel ? 0 : 1

  but just (x, y) => 0

since you will always use the template whose index in the object [] array is 0.

but this is not a big problem...just an uneuseful thing.

 

I don't see evident errors. Does all other controls work properly? Maybe you have properly installed the Mvc Controls Toolkit. In particulat the custom model binder was not installed. Have you installed it through Nuget? or Manually?

 

If you installed it through Nuget it MUST work. 

Verify the installation issue...and if installation is ok pls send me your solution to me by using the contact form of my blog: 

http://www.dotnet-programming.com/contact.aspx

Feb 27, 2012 at 6:52 PM

Frank,

Thanks so much for your help, I finally solved the issue. 

I installed the toolkit through Nugget. Nugget adds the MySuperPackage class with a PreStart method, however the code inside that method was commented, I uncommented those lines and now everything is working fine.

Coordinator
Feb 27, 2012 at 7:00 PM

Ok good!

If you like the Mvc Controls Toolkit and if it was useful to you consider giving a 5 star review to the current release.

Moreover we have a linkedin group of Mvc Controls Toolkit Users.

and also Google + group.

Now they are small, because we just started them, but there are more than 10000 Mvc Controls Toolkit installations, so they will grow. If they grow they will become good places to find what you need, such as plugins, jobs, and so on.

Dec 22, 2012 at 7:41 AM

Hi there, I faced the same problem, seems to work now but when the tree returns to the controller, all the new children (grandchildren also) are returned as childs of the root node. For example:

 

original tree:

  • (root)
    • child A
    • child B
      • grandchild B1

 

client adds and changes nodes:

  • (root)
    • child A is smart
      • grandchild A1
      • grandchild A2
    • child B
      • grandchild B1
      • grandchild B2

controller recevies

  • (root)
    • child A is smart
    • child B
      • grandchild B1
    • grandchild A1
    • grandchild A2
    • grandchild B2

How does the treeview assign children? is there anything I am doing wrong? Do the children collection shall be named Children or is there any script missing?

Sorry if should not be posted here, just thought I am facing the same problem.

 

Thanks in advance.

Greetings,

Jorge

Coordinator
Dec 22, 2012 at 1:08 PM

The Mvc Controls Toolkit uses a custom model binder to make all controls work properly. 99% for some reason The custom model binder was not installed for some reason. 

 

If you install the Mvc Controls toolkit from Nuget it should install the custom binder auromatically. On the other side if you downloaded it from codeplex and added manually a reference to it to your project you must install the model binder...and other stuffs manually as explained here: http://mvccontrolstoolkit.codeplex.com/wikipage?title=Installation

Dec 24, 2012 at 8:23 AM

Thanks for the guiding! it works! also made some research through firebug and noted that my problem was the following line

was in top of the other script tags so a parseExt function failed to execute because was not found.