ARTICLE AD BOX
I have a SQL database with a product category and a products table, and I'm trying to create a page that displays a treeview on the left, populated from the product categories table, and a table on the right. This table should be populated from the products database table, using a query with the treeviews selected node id as the parameter, to display only the selected categories products.
I'm using Entity Framework Core, overriding the DbContext class in the usual manner to get my data. ASP.NET Core MVC doesn't have a treeview control, so I'm using the jQuery jsTree plugin, which is causing all of my problems.
This is the ProductFolder class for the product categories:
public class ProductFolder { [Key] public int ProductFolderId { get; set; } = 0; public int ParentFolderId { get; set; } = 0; public int ProductFolderLevel { get; set; } [Required] public string ProductFolderName { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public ProductFolder() { ProductFolderId = 0; ParentFolderId = 0; ProductFolderLevel = 0; ProductFolderName = string.Empty; } }Here is the Product class:
public class Product { [Key] public int ProductId { get; set; } = 0; public int ParentFolderId { get; set; } = 0; public int ProductLevel { get; set; } [Required] [Display(Name = "Product Name")] public string ProductName { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; [Display(Name = "Width [DX]")] public decimal DX { get; set; } = 0.0M; [Display(Name = "Height [DY]")] public decimal DY { get; set; } = 0.0M; [Display(Name = "Depth [DZ]")] public decimal DZ { get; set; } = 0.0M; public int CNCMachineId { get; set; } = 1; [Display(Name = "CNC Machine")] public string CNCMachineName { get; set; } = string.Empty; [NotMapped] ProductFaceCollection ProductFaceCollection; [NotMapped] ProductInteriorCollection ProductInteriorCollection; [NotMapped] public decimal materialThickness { get; set; } = 18.0M; public Product() { ProductId = 0; ParentFolderId = 0; ProductLevel = 1; ProductName = string.Empty; ProductFaceCollection = new ProductFaceCollection(); ProductFaceCollection.ProductFaceCells.Add(new ProductFaceCell()); ProductInteriorCollection = new ProductInteriorCollection(); ProductInteriorCollection.ProductInteriorCells.Add(new ProductInteriorCell()); } }And this is the JsTreeView class used to populate the treeview:
public class JsTreeModel { public string id { get; set; } public string text { get; set; } public int level { get; set; } public List<JsTreeModel> children { get; set; } public JsTreeModel() { id = "#"; text = string.Empty; level = 0; children = new List<JsTreeModel>(); } public JsTreeModel(int id, string text, int level) { this.id = id.ToString(); this.text = text; this.level = level; children = new List<JsTreeModel>(); } }This is the controller:
using System.Data; using Newtonsoft.Json; using Microsoft.AspNetCore.Mvc; using MyIMS_EFCore.Models; namespace MyIMS_EFCore.Controllers { public class ProductsController : Controller { private readonly AppDbContext _context; private readonly ILogger<ProductsController> _logger; public ProductsController(AppDbContext context, ILogger<ProductsController> logger) { _context = context; _logger = logger; } // GET: Products public IActionResult Index(int? id) { // Create jstree nodes from product categories. JsTreeModel ProductTreeModel = new JsTreeModel(); GetProductFolders(ref ProductTreeModel); // Save jstree nodes as Json in ViewBag. ViewBag.Json = JsonConvert.SerializeObject(ProductTreeModel); // Create product list by product category, represented by jstrees selected node id. List<Product> productList = new List<Product>(); productList = _context.Products.Where(x => x.ParentFolderId == id).ToList(); // The model for this controllers View is IEnumerable<Product> (to be used to populate the products table), so return the product list. return View(productList); } public void GetProductFolders(ref JsTreeModel treeModel) { List<ProductFolder> rootList = new List<ProductFolder>(); rootList = _context.ProductFolders.Where(x => x.ParentFolderId == 0).ToList(); PopulateTree(rootList[0].ProductFolderId, treeModel); } private void PopulateTree(int parentid, JsTreeModel treeModel) { List<ProductFolder> productFolders = new List<ProductFolder>(); productFolders = _context.ProductFolders.Where(x => x.ParentFolderId == parentid).ToList(); treeModel.children = new List<JsTreeModel>(); for (int i = 0; i < productFolders.Count; i++) { JsTreeModel childNode = new JsTreeModel(productFolders[i].ProductFolderId, productFolders[i].ProductFolderName, productFolders[i].ProductFolderLevel); treeModel.children.Add(childNode); PopulateTree(productFolders[i].ProductFolderId, childNode); } } private int StringToInt(string value) { if (String.IsNullOrEmpty(value)) { return -1; } int result = 0; bool b = int.TryParse(value, out result); if (b == false) { return -1; } return result; } [HttpGet] [Route("Products/Node/{id}")] public IActionResult Node(string id) { int nodeid = StringToInt(id); List<Product> productList = new List<Product>(); productList = _context.Products.Where(x => x.ParentFolderId == nodeid).ToList(); return View(productList); } } }And the view looks like this:
@model IEnumerable<Product> @{ ViewData["Title"] = "Products"; } <h2>Products</h2> <div style="height: 363px"> <div class="panel panel-default" style="width: 300px;height: 320px;float:left"> <div class="panel-body"> <div id="divProductTree"> <div id="jsProductTree"></div>@* Product categories *@ </div> </div> </div> <div style="width: 600px;height: 320px;float:left;margin-left: 10px"> <table class="table">@* Products *@ <thead> <tr> <th>ProductName</th> <th>DX</th> <th>DY</th> <th>DZ</th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td>@item.ProductName</td> <td>@item.DX</td> <td>@item.DY</td> <td>@item.DZ</td> </tr> } </tbody> </table> </div> </div> @section Scripts { <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" /> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script> <script type="text/javascript"> $(document).ready(function () { $('#jsProductTree').jstree({ 'core': { 'themes': { 'icons': false }, 'data': @Html.Raw(ViewBag.Json)/* Product categories data created in Controller*/ }, 'plugins': ["defaults","changed","json_data","ui"], }) $('#jsProductTree').on('changed.jstree', async function(e, data) { alert("(" + data.node.id + ") " + data.node.text); let url = `@Url.Action("Node", "Products")/${encodeURIComponent(data.node.id)}`; let response = await fetch(url); if (response.ok) { // parses the JSON response from the server let newTreeData = response.json(); // redraw the jstree } else { console.error('Failed to load jstree'); } }); }); </script> }What do I want to do?
I want to update the Products table on the right when the selected treeview node changes.
What works? Everything up to and including:
Populating the jstree.
The onchanged.jstree event, which calls the Node(string id) function. The alert call in the script shows the correct jstree node id and text, and the correct node id is passed to the int id variable in the Node() function. But, I have no idea what that function needs to return, or why, or how the jstree deals with it when it returns.
What doesn't work?
I think it's more a case of what I don't understand. I thought I could "just" get the id of the selected treeview node, pass that to the controller somehow, run my query with that id, create my product list, refresh the page and the table would then display the correct products.
It's obviously not that simple, and I don't know what I'm doing well enough to even know where to start. I think I need to maybe populate the jstree by calling the GetProductFolders() method from within the jstree, to disconnect that from the Index() method, which would then allow me to update just the table when refreshing the view.
My main problem is this; how do I refresh the page on a node change?
I understand I am asking a lot. I'm obviously drowning here, and maybe 10 years ago I would have taken the time to learn to swim, but I just don't have that time anymore, and besides, just like Homer Simpson, I'm afraid that if I push anything else into my head, something more important will get pushed out.
By the way, I've seen a lot of jstree examples, and there's a multitude of jstree-related questions on here, but they are all concerning using jstree alone; I haven't found a single question or example of using jstree together with another control, which seems odd. I suppose you can use just a lone treeview as a directory editing tool?
