-1

I have an Angular project but this is not directly related to Angular and I just need the logic of create dynamic menu using tree that can also be similar as in ASP.NET MVC project. So, your suggestion for ASP.NET MVC, etc. will also be helpfu for me.

I use PrimeNG Tree and want to obtain menu from a table in MSSQL database:

Menu Table (the data was changed for example usage):

Id     |     Order     |     ParentId     |     Name     |

1            1               0                  Documents
2            1               1                  Work
3            1               2                  Expenses.doc
4            2               2                  Resume.doc
5            2               1                  Home
6            1               5                  Invoices.txt
...

In order to populate the menu items, I need to generate a JSON string as shown below:

{
    "data": 
    [
        {
            "label": "Documents",
            "data": "Documents Folder",
            "expandedIcon": "fa-folder-open",
            "collapsedIcon": "fa-folder",
            "children": [{
                    "label": "Work",
                    "data": "Work Folder",
                    "expandedIcon": "fa-folder-open",
                    "collapsedIcon": "fa-folder",
                    "children": [{"label": "Expenses.doc", "icon": "fa-file-word-o", "data": "Expenses Document"}, {"label": "Resume.doc", "icon": "fa-file-word-o", "data": "Resume Document"}]
                },
                {
                    "label": "Home",
                    "data": "Home Folder",
                    "expandedIcon": "fa-folder-open",
                    "collapsedIcon": "fa-folder",
                    "children": [{"label": "Invoices.txt", "icon": "fa-file-word-o", "data": "Invoices for this month"}]
                }]
        },
        ... //omitted for brevity
    ]
}

So, I have really no idea about the logic and database table design (menus). Should I generate the JSON above on the Controller or another place? Could you please post suggestions and sample approaches regarding to this issue?

Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
Jack
  • 1
  • 21
  • 118
  • 236
  • @StephenMuecke Hi Stephane, sorry but I have really no experience about this issue. If you have time, could you please post an example? Or suggest me an example usage page on the web? Thanks in advance... – Jack Oct 04 '17 at 09:06
  • @StephenMuecke Dear Stephane, I was ill and cannot look at your answer. Why did you delete it? Could you please post it again? :(( – Jack Oct 06 '17 at 14:29

1 Answers1

2

Your database Menu table is fine to generate the treeview using the PrimeNG Tree plugin except that you may want to include an additional property for the data property if you want. I would however suggest you make the ParentId property nullable so that your top level item (Documents) has a null value rather that 0.

In order to pass json in that format, your model need to be

public class MenuVM
{
    public int Id { get; set; } // this is only used for grouping
    public string label { get; set; }
    public string expandedIcon { get; set; }
    public string collapsedIcon { get; set; }
    public string icon { get; set; }
    public IEnumerable<MenuVM> children { get; set; }
}

You might also include other properties such as

public string data { get; set; }

to match the properties in the api

You also need a parent model for the data property

public class TreeVM
{
    public IEnumerable<MenuVM> data { get; set; }
}

To generate the model, you controller code would be (note this is based on the ParentId field being null for the top level item as noted above)

// Sort and group the menu items
var groups = db.Menus
    .OrderBy(x => x.ParentId).ThenBy(x => x.Order)
    .ToLookup(x => x.ParentId, x => new MenuVM
    {
        Id = x.Id,
        label = x.Name
    });
// Assign children
foreach (var item in groups.SelectMany(x => x))
{
    item.children = groups[item.Id].ToList();
    if (item.children.Any())
    {
        .... // apply some logic if there a child items, for example setting 
             // the expandedIcon and collapsedIcon properties
    }
    else
    {
        .... // apply some logic if there are no child items, for example setting 
             // the icon properties - e.g. item.icon = "fa-file-word-o";
    }
}
// Initialize model to be passed to the view
TreeVM model = new TreeVM
{
    data = groups[null].ToList();
}
return Json(model, JsonRequestBehavior.AllowGet);

For your icons, you should consider some const values or an enum rather than hard-coding strings.

  • Oh, thanks a lot!.. I will try it asap and inform you about the result... Voted+ – Jack Oct 10 '17 at 11:41
  • Dear Stephane, sorry but I have just returned to this project and have tried this solution. There is an error after **var groups = db.Menus...** line. I received Menu as ViewModel at this line and received this error: *'IEnumerable' does not contain a definition for 'OrderBy' and no extension method 'OrderBy' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?*. Any idea? – Jack Mar 19 '18 at 15:03
  • 1
    I can only assume you have not included `using System.Linq;` –  Mar 19 '18 at 23:41
  • Now it is working perfectly!.. Many thanks Stephane, I would not success this without your kind helps :) I read and understand the logic and code except from the following points. If you have time could you pls explain a little bit (I read on the web but I need to be clarified about their duties at here). If you do not have time forget it, important part is ok for me :) *???* What does .ToLookup and SelectMany exactly do at here? What is null in groups[null]? Regards... – Jack Mar 20 '18 at 20:00
  • 1
    `.ToLookup()` is similar to a `.GroupBy()` - i.e its grouping each record by the `ParentId` property. The difference between the two is explained in the answers to [this question](https://stackoverflow.com/questions/10215428/why-are-tolookup-and-groupby-different). At this point we have a group with `Key=null` containing one record (Documents) and a group with `Key=1` containing 2 records (Work and Home) etc –  Mar 21 '18 at 05:42
  • 1
    That is what we use later to assign the collection of `Children` to each menu. But now we need to get each record in order to assign the `Children`. We could call the data base again, but instead we use `.SelectMany()` which flattens the items back again into a single collection. –  Mar 21 '18 at 05:45
  • 1
    Finally, We only want the 'top level' item - the one with `ParentId=null` (because it now contains the populate `Children` property) so we use `groups[null]` - which looks up the the group with `Key=null. (note that if you had not changed `ParentId` to `int?`, and the 'top level' had `ParentId=0` as per you original code, then it would have been `groups[0]`) –  Mar 21 '18 at 05:50
  • Many many thanks Stephen... Your explanations are really like a tutorial and I learn many thinks from each words :) – Jack Mar 23 '18 at 08:15