Recursive List to nicely formatted table - c#

I have hit a slightly complicated problem that I am finding it hard to wrap my head around.
I have a class:
public class Location
{
public string Name { get; set; }
public List<Location> ChildLocations { get; set; }
}
It again has reference to Child Location objects. So this is a recursive List and I want to output this list in a nicely formatted HTML table. This is the code:
using System.Collections.Generic;
using System.Web.Mvc;
namespace WebApplication1.Controllers
{
public class Location
{
public string Name { get; set; }
public List<Location> ChildLocations { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
List<Location> locations = new List<Location>() {
new Location() {
Name = "Item 1",
ChildLocations = new List<Location>() {
new Location()
{
Name="Female",
ChildLocations = new List<Location>()
{
new Location() { Name = "Female 1" },
new Location() { Name = "Female 2" }
}
},
new Location()
{
Name="Male",
ChildLocations = new List<Location>()
{
new Location() { Name = "Male 1" },
new Location() { Name = "Male 2" }
}
}
}
},
new Location() {
Name = "Item 2",
ChildLocations = new List<Location>() {
new Location()
{
Name="Female",
ChildLocations = new List<Location>()
{
new Location() { Name = "M1" },
new Location() { Name = "M2" },
new Location() { Name = "M3" }
}
},
new Location()
{
Name="Male",
ChildLocations = new List<Location>()
},
new Location()
{
Name="Unknown",
ChildLocations = new List<Location>()
}
}
}
};
return View(locations);
}
}
}
and I want to make the output look something like this:
<table class="table table-border">
<tr>
<td rowspan="4">
Item 1
</td>
<td rowspan="2">
Female
</td>
<td>
Female 1
</td>
</tr>
<tr>
<td>Female 2</td>
</tr>
<tr>
<td rowspan="2">Male</td>
<td>Male 1</td>
</tr>
<tr>
<td>Male 2</td>
</tr>
<tr>
<td rowspan="5">
Item 2
</td>
<td rowspan="3">Female</td>
<td>M1</td>
</tr>
<tr>
<td>M2</td>
</tr>
<tr>
<td>
M3
</td>
</tr>
<tr>
<td>Male</td>
</tr>
<tr>
<td>Unknown</td>
</tr>
</table>
and if somebody wants to view the HTML I have the jsfiddle for it:
https://jsfiddle.net/4pk0necp/
I want to render this type of table using the data that I have pro grammatically in my cshtml:
#using WebApplication1.Controllers;
#model List<Location>
#{
ViewBag.Title = "Home Page";
}
I understand that a function to calculate the rowspan would be needed so I created this tiny function which correctly returns the depth:
public static int GetDepth(this Location location)
{
int noOfchildren = 0;
bool counted = false;
foreach (Location item in location.ChildLocations)
{
if (item.ChildLocations.Count <= 0)
{
if (!counted)
{
noOfchildren += location.ChildLocations.Where(i => i.ChildLocations.Count <= 0).Count();
counted = true;
}
}
else
noOfchildren += GetDepth(item);
}
return noOfchildren;
}
The give list of Tree about is just a sample and there can be many levels of depth in the tree. Any help is appreciated.
Edit: I tweaked my GetDepth function since we only need leaf level nodes count.

Try using the following class. Only barely tested:
public class TreeDrawer {
private readonly Dictionary<Location, int> _depthMap;
public TreeDrawer() {
_depthMap = new Dictionary<Location, int>();
}
public string Draw(IEnumerable<Location> locations) {
var sb = new StringBuilder("<table>");
bool first = true;
foreach (var l in locations) {
Draw(l, sb, true, first);
first = false;
}
sb.Append("</table>");
return sb.ToString();
}
private void Draw(Location l, StringBuilder sb, bool fromRoot, bool first) {
int depth = GetDepth(l);
bool openedRow = false;
if (fromRoot || !first) {
sb.Append("<tr>");
openedRow = true;
}
sb.Append("<td");
if (depth > 1) {
sb.Append(" rowspan=\"");
sb.Append(depth);
sb.Append("\"");
}
sb.Append(">");
sb.Append(l.Name);
sb.Append("</td>");
bool isFirstChild = true;
if (l.ChildLocations != null) {
foreach (var child in l.ChildLocations) {
Draw(child, sb, false, isFirstChild);
isFirstChild = false;
}
}
if (openedRow) {
sb.Append("</tr>");
}
}
private int GetDepth(Location l) {
if (!_depthMap.ContainsKey(l)) {
_depthMap.Add(l, Math.Max(1, l.ChildLocations?.Sum(GetDepth) ?? 0));
}
return _depthMap[l];
}
}

Related

Converting Knockout.js databinding for id attributes to Blazor

What's the Blazor equivalent of this Knockout.js data-binding?
I can easily create a foreach loop through a C# List<T> object and bind to the object's properties however I'm struggling with this as the binding is for the id attributes.
HTML code:
<div data-bind="foreach : combinedArr">
<div data-bind="with: $data.recordCo">
<table id="tableFull">
<tbody>
<tr>
<td class="fixed-width iconCol" data-bind="with: $parent.targetCo">
<button data-bind="attr: { 'data-target': $data[0].tableTargetID, id: $data[0].buttonID }" data-toggle="collapse" type="button" onclick="glyphChanger(this.id)" class="btn btn-default iconButton glyphicon glyphicon-chevron-right" aria-label="Left Align" aria-hidden="true"></button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="tbAdj panel panel-collapse collapse" data-bind="attr: { id: $data.countCo }">
<table class="table table-condensed tableSize tableSpacer" id="tableFull">
<tbody data-bind="foreach: $data.jsonCo">
....
</tbody>
</table>
</div>
</div>
Javascript code:
self.combinedArr = ko.observableArray();
self.post = function () {
voidNotification = false;
self.combinedArr.removeAll();
$.post(this.api + "/tabular", {
value: this.logSource()
}).success(
function (d) {
var counter = 0;
$.each(d, function (i, o) {
var objArr = JSON.parse(o);
//Parse a second time to access the objects individually
var recordParsed = JSON.parse(objArr[0]);
var jsonParsed = JSON.parse(objArr[1]);
//Data to populate the expandable table
jsArr = [];
for (var x in jsonParsed) {
jsArr.push({ jfieldName: jsonParsed[x].Name, jfieldValue: jsonParsed[x].Value });
}
//ID for the button that expands and collapses the table
var buttonID = "btUniqID" + counter;
//The id of the table - needs to be in the same array as the buttonID
var tableTargetID = "#jsonTable" + counter;
var idArr = [];
idArr.push({ tableTargetID: tableTargetID, buttonID: buttonID });
//The id for that table that needs to be on its own
var uniqueTableID = "jsonTable" + counter;
//Combine all the data and push to the combinedArr
self.combinedArr.push({ recordCo: recordParsed, jsonCo: jsArr, countCo: uniqueTableID, targetCo: idArr });
//Incremented for unique button ids
counter++;
});
if (counter === 0) {
self.FaultFound(true);
self.FaultText("Data could not be parsed");
$("#recordTable").css("display", "none");
} else {
self.FaultFound(false);
//Check if transaction is void
VoidHandler();
//Hides the loading animation
self.ShowDetailsLoading(false);
//Show the recordsTable
$("#recordTable").css("display", "block");
}
})
.error(
function (d) {
alert('failed ' + d);
}
);
}
I've created a C# class equivalent to self.combinedArr with the rows variable below:
public List<CombinedRow> rows = new List<CombinedRow>();
private async Task OnParseClicked()
{
try
{
var response = await Http.PostAsJsonAsync("api/TLogParser/Records", new TLogMessageRequestDto(logMessage: inputMessage));
parsedMessage = await response.Content.ReadFromJsonAsync<IEnumerable<RecordItem>>();
var jsArray = new List<Record>();
foreach (var m in parsedMessage)
{
jsArray.Add(new Record { jfieldName = m.MessageId, jfieldValue = m.RecordBody });
}
var counter = 0;
foreach (var m in parsedMessage)
{
//ID for the button that expands and collapses the table
var buttonID = "btUniqID" + counter;
//The id of the table - needs to be in the same array as the buttonID
var tableTargetID = "#jsonTable" + counter;
var row = new Row() { ButtonId = buttonID, TableTargetId = tableTargetID };
//The id for that table that needs to be on its own
var uniqueTableID = "jsonTable" + counter;
var combinedRow = new CombinedRow { recordCo = m, JsonCo = jsArray, CountCo = uniqueTableID, TargetCo = row};
rows.Add(combinedRow);
counter++;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
public class CombinedRow
{
public RecordItem recordCo { get; set; }
public Row TargetCo { get; set; }
public string CountCo { get; set; }
public List<Record> JsonCo { get; set; }
}
public class Row
{
public string ButtonId { get; set; }
public string TableTargetId { get; set; }
}
public class Record
{
public string jfieldName { get; set; }
public string jfieldValue { get; set; }
}
However, I'm not sure on the HTML/Blazor binding part of how to bind to the id attributes.

Sum up columns in MVC C# Razor View

I am wondering if I can sum up decimal in all the columns that are dynamically generated in razor view without having to change this in controller and viewModel. I will be happy to try jQuery option as well.
ViewModel
public class LedgerViewModel
{
public LedgerViewModel(int PayCategoryCount)
{
PayCollection = new List<decimal>(new decimal[PayCategoryCount]);
}
public DateTime PDate { get; set; }
public class MonthlyPaymentsVM
{ public int PayCategoryId { get; set; }
[DisplayFormat(DataFormatString = "{0:MMMM yyyy}")]
public DateTime Date { get; set; }
public IEnumerable<string> PaymentCategories { get; set; }
public List<LedgerViewModel> Payments { get; set; }
public List<LedgerViewModel> Member { get; set; }
}
controller
public ActionResult Report(int year, int no, string nom, Guid gcode)
{
DateTime startdate = (new DateTime(DateTime.Now.Year - 1, 7, 1));
DateTime enddate = (new DateTime(DateTime.Now.Year, 7, 1));
DateTime date = DateTime.Now;
//get all payments
var a = from of in db.OfflinePayMents.Include(i => i.Customer).Include(i => i.PaymentCategory).ToList().Where(x => x.PDate >= startdate && x.PDate < enddate && x.POK && x.CustomerGuid=gcode) select new { of.Customer, of.PaymentCategory, of.CustomerID, of.PaymentCategoryID, of.PDate, of.TxnId, of.Pay, of.PType };
var grouped = a.GroupBy(x => new { customer = x.CustomerID, PaymentCategory = x.PaymentCategory.PaymentCategoryID, txn = x.TxnId, pd = x.PDate, x.PType }).Select(x => new
{
Name = x.First().Customer.Name,
Customer = x.First().Customer,
PaymentCategory = x.First().PaymentCategory,
Txn = x.First().TxnId,
Pd = x.First().PDate,
PType = x.First().PType,
cid = x.First().PaymentCategoryID,
Pay= x.Sum(y => y.Pay)
});
var data = grouped.GroupBy(x => x.Txn);
var PaymentCategories = db.PaymentCategories.OrderBy(z => z.Order);
var PayCategoryCount = PaymentCategories.Count();
var PaymentCategoryIDs = PaymentCategories.Select(x => x.PaymentCategoryID).ToList();
var model = new MonthlyPaymentsVM()
{
//Member = members,
Date = date,
PaymentCategories = PaymentCategories.Select(z => z.PaymentCategoryTitle),
Payments = new List<LedgerViewModel>()
};
foreach (var group in data)
{
LedgerViewModel payment = new LedgerViewModel(PaymentCategoryCount);
var pd = group.First().Pd;
payment.PDate = pd;
foreach (var item in group)
{
int index = PaymentCategoryIDs.IndexOf(item.PaymentCategory.PaymentCategoryID);
if (index < 0)
{
payment.PayCollection[index + 1] = item.Pay;
}
else
{
payment.PayCollection[index] = item.Pay;
}
payment.Total += item.Pay;
}
model.Payments.Add(payment);
}
return View(model);
}
Razor View
<table class="doubleborder" width="99%" border="0" align="center" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>Date</th>
#foreach (var payname in Model.PaymentCategories)
{
<th>#payname</th>
}
</tr>
</thead>
<tbody>
#foreach (var item in Model.Payments)
{
<tr>
<td>#item.PayDate.ToString("dd/MM/yyyy")</td>
#foreach (var amount in item.PayCollection)
{
<td>#amount.ToString("c")</td>
}
</tr>
}
<tr class="doubleborder">
<td>Total:</td>
#foreach (var item in Model.PaymentCategories)
{
<td>
looking at getting sum totals of each column here
</td>
}
</tr>
</tbody>
</table>
So this groups by customer. The payments are grouped by transaction Date and are iterated in the foreach loop in the view.
Thanks if you are able to help.
If I understand your model correctly, this is what you are looking for:
#Model.Payments.Sum(i => i.Paycollection.Sum(x => x.amount));
Update, based on your comment: (and assuming that all the columns have value for all the PaymentCollections)
#for(int i = 0 ; i < PaymentCategories.Count(); i++)
{
<td>
Model.Payments.Sum(x => x.PayCollection[i]); //this will throw an exception if the value at index is not defined
</td>
}

looping through dictionary inside dictionary in jquery

I'm trying to set values to table containing textboxes in each cell. For instance a table having a first column containing names, and rest of the columns containing textboxes to add marks of the given assignment for individual students. For that I used dictionary to hold given marks by the instructor and when the page is load, the marks are set to the relevant textboxes. however my iteration over Dictionary in jquery doesn't seem to be really working. I'm missing somewhere in loop. Below is my code.
public class StudentAssignments
{
public string Id { get; set; }
public Models.Assignment assignment { get; set; }
public Models.User user { get; set; }
public DateTime? SubmissionDate { get; set; }
public string Result { get; set; }
public Models.File assignmentfiles {get;set;}
}
public class Assignment
{
public string Id { get; set; }
public string batchid { get; set; }
public List<SelectListItem> batchnumber { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[DataType(DataType.Date)]
public DateTime? DueDate { get; set; }
public Models.File file { get; set; }
}
public class File
{
public string strId { get; set; }
public string strName { get; set; }
public string strPath { get; set; }
public string strSize { get; set; }
}
InstructorController.cs
[HttpPost]
public ActionResult AssignedStudents(string id)
{
//Get Student Assignments
Models.StudentAssignments studentAssignment = new Models.StudentAssignments();
Context.Assignment contAssignment = new Context.Assignment();
CreateUser contUser = new CreateUser();
Dictionary<string, Dictionary<string, List<Models.StudentAssignments>>> dict =
new Dictionary<string, Dictionary<string, List<Models.StudentAssignments>>>();
var studentslist = contStudent.getAllStudentBatchList().Where(p => p.batchId == id).Select(p => p.studentId).ToList();
var assignmentid = contAssignment.lstAssignment().Where(p => p.batchid == id).Select(p=>p.Id).ToList();
foreach(var items in studentslist)
{
dict.Add(items.ToString(), new Dictionary<string, List<Models.StudentAssignments>>());
foreach (var item1 in assignmentid)
{
dict[items].Add(item1.ToString(), GetStudentFiles(items, item1));
}
}
ViewBag.StudentAssignment = dict;
}
private List<Models.StudentAssignments> GetStudentFiles(string stdid, string assgnid)
{
Context.Assignment contAssignment = new Context.Assignment();
CreateUser contuser = new CreateUser();
List<Models.StudentAssignments> lstassgn = new List<Models.StudentAssignments>();
var details = (from t1 in contAssignment.GetStudentAssignments()
join
t2 in contAssignment.GetStudentAssignmentsFiles() on
t1.Id equals t2.assignment.Id
join t3 in contuser.GetAllUsers() on t1.user.Id equals t3.Id
where t1.assignment.Id == assgnid && t1.user.Id == stdid
select new { t1,t2,t3 }).ToList();
foreach (var item in details)
{
Models.StudentAssignments assignment = new Models.StudentAssignments();
assignment.assignment = new Models.Assignment();
assignment.Id = item.t1.Id;
assignment.Result = item.t1.Result;
assignment.assignmentfiles = new Models.File();
assignment.assignmentfiles.strId = item.t2.Id;
assignment.assignmentfiles.strPath = item.t2.assignmentfiles.strPath;
assignment.user = new Models.User();
assignment.user.Id = item.t3.Id;
assignment.user.FirstName = item.t3.FirstName;
assignment.user.LastName = item.t3.LastName;
lstassgn.Add(assignment);
}
return lstassgn;
}
AssignedStudents.cshtml
<div class="col-md-12">
<h2>Submitted Assignments</h2>
<table class="table table-bordered table-responsive table-hover" id="tbl">
<tr>
<th>
Name
</th>
<th>
Assignment 1
</th>
</tr>
#foreach (var item in ViewBag.StudentAssignment)
{
bool flag = true;
<tr class="item">
#foreach (var item1 in item.Value)
{
foreach (var item2 in item1.Value)
{
if (flag == true)
{
<td>
#item2.user.FirstName
</td>
flag = false;
}
break;
}
<td>
#foreach (var item2 in item1.Value)
{
<img src="#" />
}
#foreach (var item2 in item1.Value)
{
if (item2.Result != null && item2.Result != "Pending")
{
<div><input type="text" value="#item2.Result" class="txtbox" id=#Guid.NewGuid().ToString() disabled="disabled"/></div>
}
else
{
<div><input type="text" class="txtbox" id=#Guid.NewGuid().ToString() /></div>
}
<input type="hidden" id="stdassgnid" value="#item2.Id" />
break;
}
</td>
}
</tr>
}
</table>
<input type="button" id="saveResult" value="Save Result" class="btn btn-success" />
</div>
Edited:-
Since I've managed to pass the results in the required textboxes. I'm here surrounded by another issue which is the next assignment submission. The first column contains the submitted marks already, and this time instructor would carry out the marking of the next assignment. Therefore, The system would look for the next column as the previous column already contains marks.
$("#saveResult").click(function () {
$("#tbl tr.item").each(function () {
$(this).find("input.txtbox").attr("disabled", "disabled");
var Mkrs = $(this).find("input.txtbox").val();
var Id = $(this).find("#stdassgnid").val();
alert(Mkrs)
$.ajax({
type: "POST",
url: '#Url.Action("UpdateResult", "Assignment")',
data: { Marks: Mkrs, studentAssgnId: Id},
dataType: "json",
success: function (data) {
if(data.message!=null)
{
alert(data.message)
}
}
})
});

ASP.NET MVC PresentationModel Binding to ComboBoxes stays empty

Hello I'm learning to program MVC style in ASP.NET. I'm trying to populate a combobox with my PresentationModel, somehow it stays empty.
I'm using the ASP tag-helpers:
View (Index.cshtml)
#model Week3_oef2_ITPro.PresentationModel.PMRegistration
<h2>New Registration</h2>
<h4>Registration</h4>
<form asp-controller="Register" asp-action="" method="post">
<table>
<tr>
<td>Organization</td>
<td class="form-group">
<select asp-for="OrgId.Id" asp-items="#Model.Org" class="form-group" />
</td>
</tr>
<tr><td><input type="submit" /></td></tr>
</table>
</form>
PresentationModel (PMRegistration.cs)
public class PMRegistration
{
public Organization OrgId { get; set; }
public List<SelectListItem> Org { get; set; }
}
Model (Organization.cs)
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
}
Data (Where all the objects are initialized)
public class Data
{
private static List<Session> sessions = new List<Session>();
private static List<Organization> organizations = new List<Organization>();
private static List<Device> devices = new List<Device>();
static Data()
{
organizations.Add(new Organization() { Id = 1, Name = "Howest" });
organizations.Add(new Organization() { Id = 2, Name = "Vives" });
organizations.Add(new Organization() { Id = 3, Name = "HoGent" });
organizations.Add(new Organization() { Id = 4, Name = "HoLimburg" });
organizations.Add(new Organization() { Id = 4, Name = "De blauwe smurfen" });
devices.Add(new Device() { Id = 1, Name = "Laptop" });
devices.Add(new Device() { Id = 2, Name = "Tablet" });
devices.Add(new Device() { Id = 3, Name = "Apple Watch" });
}
public static List<Device> GetDevices()
{
return devices;
}
public static List<Organization> GetOrganizations()
{
return organizations;
}
}
Controller (RegisterController.cs)
public class RegisterController : Controller
{
// GET: /<controller>/
[HttpGet]
public IActionResult Index()
{
PMRegistration pm = new PMRegistration();
pm.OrgId = new Organization();
pm.Org = ConverToListItems(Data.GetOrganizations());
return View(pm);
}
#region methodes
private List<SelectListItem> ConverToListItems(List<Organization> data)
{
List<SelectListItem> items = new List<SelectListItem>();
foreach (var item in data)
{
items.Add(new SelectListItem() { Text = item.Name, Value = item.Id.ToString() });
}
return items;
}
#endregion
}
Your HTML markup for the SELECT element is wrong. The SELECT element requires a closing tag.
It should be
<select asp-for="OrgId.Id" asp-items="#Model.Org" class="form-group"></select>

Html table not populating from ViewModel

I am trying to populate an HTML table with data from a table in my database. The issue is simply that the HTML table is not getting populated with any data.
Here is the ViewModel:
public class TestViewModel
{
public string MatchedId { get; set; }
public string UnmatchedId { get; set; }
public string Auth { get; set; }
public DateTime CreditDate { get; set; }
public string CreditNumber { get; set; }
public decimal CreditAmount { get; set; }
public DateTime DeniedDate { get; set; }
public int DeniedReasonId { get; set; }
public string DeniedNotes { get; set; }
}
Controller Action:
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new Entities();
//model here is the .csv, doesn't have anything to do with this issue
foreach (var item in model)
{
var tc = new TemporaryCsvUpload
{
Id = item.Id,
CreditAmount = item.CreditAmount,
CreditDate = item.CreditDate,
CreditNumber = item.CreditNumber,
DeniedDate = item.DeniedDate,
DeniedReasonId = item.DeniedReasonId,
DeniedNotes = item.DeniedNotes
};
entity.TemporaryCsvUploads.Add(tc);
}
entity.SaveChanges();
System.IO.File.Delete(filePath);
//This is where the database table is getting filled
entity.Database.ExecuteSqlCommand("Insert into CsvReport Select p.Id as MatchedId, case when p.Id is null then t.Id end as UnmatchedId, p.Auth,p.CreditDate, p.CreditNumber,p.CreditAmount, p.DeniedDate,p.DeniedReasonId, p.DeniedNotes from TemporaryCsvUpload t left join PermanentTable p on p.Id = t.Id;");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
View:
#model IEnumerable<TestProject.TestViewModel>
#if (Model != null)
{
foreach (var item in Model.Where(x => x.IdMatched != null))
{
<tr>
<td>
#item.MatchedId
</td>
<td>
#item.Auth
</td>
<td>
#item.CreditDate
</td>
<td>
#item.CreditNumber
</td>
<td>
#item.CreditAmount
</td>
<td>
#item.DeniedDate
</td>
<td>
#item.DeniedReasonId
</td>
<td>
#item.DeniedNotes
</td>
</tr>
}
}
It's a little weird because I am populating the database with an SQL command. What am I missing here? Do I need to try and pass it through the controller action? Let me know if you need more information. Thanks!
Edit
I tried to pass the instance through, but I may still be doing it incorrectly:
var testModel = new TestViewModel();
return View("Upload", testModel);
Here is what its padding through:
public class TestViewModel
{
public IEnumerable<Test> Test { get; set; }
}
Made an answer so essentially the view doesn't know what to render you need to pass an actual filled model (in your case an IEnumerable to the view). This can be done using the method:
View("Upload", viewModelList);
Controller.View docs on MSDN
It looks like you are not adding any data to your view model.
If your view model is a collection of Test objects, you need to add some
Test objects to the collection.
var model = new TestViewModel()
{
Test = new List<Test>() { new Test(), new Test(), ... }
}
return View("Upload", model);

Categories

Resources