ASP.Net MVC: Recursive approach not showing nested child - asp.net-mvc

I am trying to show nested data in ul/li, but nested children are not showing. See my code and please tell me what is wrong there.
Controller:
public ActionResult Index()
{
List<MenuItem> allMenu = new List<MenuItem>
{
new MenuItem {Id=1,Name="Parent 1", ParentId=0},
new MenuItem {Id=2,Name="child 1", ParentId=1},
new MenuItem {Id=3,Name="child 2", ParentId=1},
new MenuItem {Id=4,Name="child 3", ParentId=1},
new MenuItem {Id=5,Name="Parent 2", ParentId=0},
new MenuItem {Id=6,Name="child 4", ParentId=4}
};
List<MenuItem> mi = allMenu
.Where(e => e.ParentId == 0) /* grab only the root parent nodes */
.Select(e => new MenuItem
{
Id = e.Id,
Name = e.Name,
ParentId = e.ParentId,
Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
}).ToList();
ViewBag.menusList = mi;
return View();
}
POCO class:
public class MenuItem
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public virtual List<MenuItem> Children { get; set; }
}
View:
#helper ShowTree(List<Scaffolding.Controllers.MenuItem> menusList)
{
<ul>
#foreach (var item in menusList)
{
<li>
<span>#item.Name</span>
#if (item.Children != null && item.Children.Any())
{
#ShowTree(item.Children)
}
</li>
}
</ul>
}
#{
var menuList = ViewBag.menusList as List<Scaffolding.Controllers.MenuItem>;
#ShowTree(menuList);
}
If you run the code then you will see child 4 is not showing which is a child of child 3. Please advise what I need to change in my code. Thanks

Your query gets the top level elements (ParentId == 0) only and then populate just their direct child elements.
Your query needs to be changed to populate all child elements for all levels. Note that your MeuItem does not need the ParentId property.
// Group the items by parentId and project to MenuItem
var groups = allMenu.ToLookup(x => x.ParentId, x => new MenuItem
{
Id = x.Id,
Name = x.Name,
});
// Assign the child menus to all items
foreach (var item in allMenu)
{
item.children = groups[item.Id].ToList();
}
// Return just the top level items
ViewBag.menusList = groups[0].ToList();
As a side note, do not use ViewBag. Pass the model to the view instead
return View(groups[0].ToList());
and in the view
#model List<MenuItem>
....
#ShowTree(Model);

Now i could fix my problem. the problem was in logic of razor code and also i comment this line //.Where(e => e.ParentId == 0) here i am adding working code.
#helper ShowTree(List<NestedChild.Controllers.MenuItem> menu, int? parentid = 0, int level = 0)
{
var items = menu.Where(m => m.ParentId == parentid);
if (items.Any())
{
if (items.First().ParentId > 0)
{
level++;
}
<ul>
#foreach (var item in items)
{
<li>
#item.Name
</li>
#ShowTree(menu, item.Id, level);
}
</ul>
}
}
#{
var menuList = ViewBag.menusList as List<NestedChild.Controllers.MenuItem>;
#ShowTree(menuList);
}
Action
public ActionResult Index()
{
List<MenuItem> allMenu = new List<MenuItem>
{
new MenuItem {Id=1,Name="Parent 1", ParentId=0},
new MenuItem {Id=2,Name="child 1", ParentId=1},
new MenuItem {Id=3,Name="child 2", ParentId=1},
new MenuItem {Id=4,Name="child 3", ParentId=1},
new MenuItem {Id=5,Name="Parent 2", ParentId=0},
new MenuItem {Id=6,Name="child 4", ParentId=4}
};
List<MenuItem> mi = allMenu
//.Where(e => e.ParentId == 0) /* grab only the root parent nodes */
.Select(e => new MenuItem
{
Id = e.Id,
Name = e.Name,
ParentId = e.ParentId,
Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
}).ToList();
ViewBag.menusList = mi;
return View();
}

Related

Placeholder on a #Html.Dropdownlist that is not selectable

How do i put a placeholder on my #Html.Dropdownlist that is not selectable.
I implemented chosen-jquery on my dropdown.
My code is as per below:
#Html.DropDownList("Id", new SelectList(new Ahib.Membership.UserFacade().GetAllUsers(), "Id", "Username"), "-select a system owner-", new { #class="chosen1-select" })
Currently my dropdownlist has a placeholder but the "-select a system owner-" becomes a selectable value which i dont want. How do solve this issue?
You can convert your list to the selectitem list and apply your disabled attribute from the controller itself.
like this
assuming your model is like this
class user
{
public int Id { get; set; }
public string Username { get; set; }
}
Convert your list to List<SelectListItem> like this, note here I am disabling the select a system owner entry.
public ActionResult Index()
{
List<user> users = new List<user>();
users.Add(new user { Id = 1, Username = "test1" });
users.Add(new user { Id = 2, Username = "test2" });
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem { Text = "-select a system owner-", Value = "0", Disabled = true });
foreach (var item in users)
{
list.Add(new SelectListItem
{
Text = item.Username,
Value = item.Id.ToString()
});
}
ViewBag.listItems = list;
return View();
}
and in view use it like this
#Html.DropDownList("test", ViewBag.listItems as IEnumerable<SelectListItem>);
Result:

How to save changes of courses in EmployeeCourse table

What i Need
I need to save changes in courses in EmployeeCourse table
by remove all course for employee from EmployeeCourse table
then add courses after changes
Problem
when add changes in courses after remove i get error in save changes() function
{"An error occurred while updating the entries.
The INSERT statement conflicted with the FOREIGN KEY constraint
\"FK_EmployeeCourse_Course\". The conflict occurred in database \"mycourse\",
table \"dbo.Course\", column 'Id'.\r\nThe statement has been terminated."}
under submit button edit [httppost]
public ActionResult Edit(EditEmployeeVm model)
{
var emp = db.Employees.FirstOrDefault(f => f.Id == model.Id);
// remove all courses
foreach (EmployeeCourse eec in emp.EmployeeCourses.ToList())
{
var ec = db.EmployeeCourses.Find(eec.Id);
db.EmployeeCourses.Remove(ec);
}
// add courses again
foreach (var couseid in model.CourseIds)
{
db.EmployeeCourses.Add(new EmployeeCourse { CourseId = couseid, EmployeeId = emp.Id });
}
db.SaveChanges();
}
Debug result
debug add as image below
Details
Relation between two tables one to many
EmployeeCourse
Id(primary key) CourseId(forign key) EmployeeId(forign key)
Course table in database
Id(pk) CourseName
I using following view model for edit to get CourseIds
#model StudentCourses.Models.EditEmployeeVm
$(function () {
$(document).on("click", ".remove", function (e) {
e.preventDefault();
$(this).closest(".course-item").remove();
});
$('#AvailableCourses').change(function () {
var val = $(this).val();
var text = $("#AvailableCourses option:selected").text();
var existingCourses = $("input[name='CourseIds']")
.map(function () { return this.value; }).get();
if (existingCourses.indexOf(val) === -1) {
var newItem = $("<div/>").addClass("course-item")
.append(text + ' Remove ');
newItem.append('<input type="text" name="CourseIds" value="' + val + '" />');
$("#items").append(newItem);
}
});
});
</script>
to retrieve courses from database in edit view i used code below
<div>
#using (Html.BeginForm())
{
#Html.HiddenFor(g => g.Id)
#Html.LabelFor(f => f.Name)
#Html.DropDownList("AvailableCourses", Model.Courses, "Select")
<h4>Existing courses</h4>
<div id="items"></div>
foreach (var c in Model.ExistingCourses)
{
<div class="course-item">
#c.Name Remove
<input type="text" name="CourseIds" value="#c.Id" />
</div>
}
}
</div>
model used for that as following
public class EditEmployeeVm
{
public int Id { set; get; }
public List<SelectListItem> Courses { get; set; }
public int[] CourseIds { set; get; }
public List<CourseVm> ExistingCourses { set; get; }
}
public class CourseVm
{
public int Id { set; get; }
public string Name { set; get; }
}
}
update
public ActionResult Edit(int id)
{
var vm = new EditEmployeeVm { Id = id };
var emp = db.Employees.FirstOrDefault(f => f.Id == id);
vm.Name = emp.Name;
vm.ExistingCourses = db.EmployeeCourses
.Where(g => g.EmployeeId == id)
.Select(f => new CourseVm
{
Id = f.Id,
Name = f.Course.CourseName
}).ToList();
vm.CourseIds = vm.ExistingCourses.Select(g => g.Id).ToArray();
vm.Courses = db.Courses.Select(f => new SelectListItem
{
Value = f.Id.ToString(),
Text = f.CourseName
}).ToList();
return View(vm);
}
In your EDIT Get action, You are using the wrong id for CourseVm. It should be the Course Id, but you are setting the EmployeeCourse record/entity Id.
This should fix it.
vm.ExistingCourses = db.EmployeeCourses.Where(g => g.EmployeeId == id)
.Select(f => new CourseVm
{
Id = f.Course.Id,
Name = f.Course.CourseName
}).ToList();

asp.net mvc paging error

I have a pagination problem. I have a Product model, it has a string ProductCategory attribute. One page can take 4 products, whenever it exceeds 4, it points out page 2. The problem is that when I click "Car" category and click page 2, it takes every product, rather than taking only "Car" .
I got the book from the book, ASP.NET MVC 4, published by apress.
Here is my ProductListViewModel:
public class ProductsListViewModel
{
public List<Product> Products { get; set; }
public PagingInfo PagingInfo { get; set; }
public string CurrentCategory { get; set; }
}
Here is my ProductController's List Action: When I debug the application, at the click at page 2, category parameter is null.
public ViewResult List(string category, int page = 1)
{
ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products
.Where(p => category == null || p.ProductCategory.Equals(category))
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize).Take(PageSize).ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.ProductCategory == category).Count()
}
};
model.CurrentCategory = category;
return View(model);
}
Here is my List View:
#model SportsStore.WebUI.Models.ProductsListViewModel
#{
ViewBag.Title = "Products";
}
#foreach (var p in Model.Products)
{
<div class="item">
#Html.Partial("ProductSummary", p)
</div>
}
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x, ProductCategory = Model.CurrentCategory }))
ProductSummary is a partial view that views the product. Pagelinks is a extention methods:
public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo,
Func<int, string> pageUrl)
{
StringBuilder result = new StringBuilder();
for (int i = 1; i <= pagingInfo.TotalPages; i++)
{
TagBuilder tag = new TagBuilder("a"); // Construct an <a> tag
tag.MergeAttribute("href", pageUrl(i));
tag.InnerHtml = i.ToString();
if (i == pagingInfo.CurrentPage)
tag.AddCssClass("selected");
result.Append(tag.ToString());
}
return MvcHtmlString.Create(result.ToString());
}
As pictured above, when I click page 2, it gets every product, rather than car. How can I solve it?
Thanks in advance.
NOTE: Routes has been added below:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null,
"",
new
{
controller = "Product",
action = "List",
category = (string)null,
page = 1
}
);
/*
*http://localhost:56701/?page=2 olmasındansa http://localhost:56701/Page4 olmasını sağlayan kod parçacığı.
*
*/
routes.MapRoute(null,
"Page{page}",
new { controller = "Product", action = "List", category = (string)null },
new { page = #"\d+" }
);
routes.MapRoute(null,
"{category}",
new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(null,
"{category}/Page{page}",
new { controller = "Product", action = "List" },
new { page = #"\d+" }
);
routes.MapRoute(null, "{controller}/{action}");
}
}
NOTE 2:
Here is the result when I click page 2 of the Car category:
As you see below, at the page 2, every car items exist. (Blue rectangle). But I do no want to see page 3 and 4 at the bottom of the page (Red rectangle).
Thanks in advance.
The solution has implemented in the book, and it seems it has been escaped from my attention.
Two revisions should be made in the source code, one in List.cshtml :
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x, category = Model.CurrentCategory }))
Another is in the List action of the ProductController:
public ViewResult List(string category, int page = 1)
{
ProductsListViewModel viewModel = new ProductsListViewModel
{
Products = repository.Products
.Where(p => category == null || p.ProductCategory == category)
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize).ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
**TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.ProductCategory == category).Count()**
},
CurrentCategory = category
};
return View(viewModel);
}
TotalItems attribute shall be updated as mentioned above.

.net mvc selectlist multiple sources

I have a list of users I am retrieving from Active Directory like so
PrincipalContext pc = new PrincipalContext(ContextType.Domain, "DOMAIN", "dc=domain,dc=org");
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, "Advisors");
ViewBag.EmployeeID = new SelectList(group.Members, "EmployeeID", "DisplayName");
I would like to add two additional items to this list but do not want to create user accounts for them in Active Directory as they are not real users.
Is it possible to do something like
var items1 = new[]
{
new { EmployeeID = "1", DisplayName = "Independent" },
new { EmployeeID = "2", DisplayName = "Own" }
};
var items = Concat(group.Members, items1);
{
ViewBag.EmployeeID = new SelectList(items, "EmployeeID", "DisplayName");
}
Well, You can have a ViewModel like this:
public class EmployeeViewModel
{
public string EmployeeId { get; set; }
public string DisplayName { get; set; }
}
and than you can cast the group.Members to a list of your ViewModel, add some custom itens on this list and set this list to the ViewBag.
var members = group.Members.Select(x => new EmployeeViewModel() { EmployeeId = x.EmployeeId, DisplayName = x.DisplayName }).ToList();
members.Add(new EmployeeViewModel() { EmployeeId = "1", DisplayName = "Independent" });
members.Add(new EmployeeViewModel() { EmployeeId = "2", DisplayName = "Own" });
ViewBag.EmployeeID = new SelectList(members, "EmployeeID", "DisplayName");

Will a DropDownList html helper work correctly with a IList<SelectListItem>?

I am having problems with getting a DropDownList to correctly select the right value and display it.
I am using the following:
#Html.DropDownListFor(x => Model.AdminSummaries[index].Status, AdminStatusReference.GetAdminStatusOptions(), new { id = string.Format("Status_{0}",index ) })
Is it okay that AdminStatusReference.GetAdminStatusOptions() returns a List or MUST it return an IEnumerable?
Model:
public class MyViewModel
{
public IList<AdminSummary> AdminSummaries { get; set; }
}
public class AdminSummary
{
public string Status { get; set; }
}
public static class AdminStatusReference
{
public static IEnumerable<SelectListItem> GetAdminStatusOptions()
{
return new[]
{
new SelectListItem { Value = "1", Text = "status 1" },
new SelectListItem { Value = "2", Text = "status 2" },
new SelectListItem { Value = "3", Text = "status 3" },
};
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
AdminSummaries = new[]
{
// preselect the first item
new AdminSummary { Status = "1" },
// preselect the second item
new AdminSummary { Status = "2" },
// nothing will be preselected because there is no xxx Value in the list
new AdminSummary { Status = "xxx" },
}.ToList()
};
return View(model);
}
}
View:
#model MyViewModel
#for (int index = 0; index < Model.AdminSummaries.Count; index++)
{
#Html.DropDownListFor(
x => x.AdminSummaries[index].Status,
new SelectList(
AdminStatusReference.GetAdminStatusOptions(),
"Value",
"Text",
Model.AdminSummaries[index].Status
)
)
}
It will be fine.
List<T> implements IEnumerable<T>
for using
#Html.DropDownListFor<>
in my projects I have always used a IEnumerable<SelectListItem> in my View and therefore before hand I have set which item has the propery "Selected" as true. This will then set the default item.
forgot I could edit my original answer :P
here is the html rendered on my project

Resources