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.
Related
I have a set of images and I tried to add them into a table (ImageGalleries) using EF Core. The insertion goes well. After the insertion I need IDs (there is an identity column in the table) of inserted records. I tried below post in StackOverflow. But I couldn't get reach.
get ids of inserted rows
public ActionResult Save(IEnumerable<IFormFile> images,RoomGalleryVM vm)
{
if (images!= null)
{
string fileName = "";
foreach (var image in images)
{
var fileContent = ContentDispositionHeaderValue.Parse(image.ContentDisposition);
fileName = Path.GetFileName(fileContent.FileName.ToString().Trim('"'));
var gallery = new RoomGallery()
{
RoomId = vm.RoomId,
Description=fileName,
Status="A"
};
Context.RoomGalleries.Add(gallery);
}
Context.SaveChanges();
//var Ids = vm.Select(c => c.Id).ToList();
}
return View("RoomGalleryIndex");
}
RoomGalleryVM
public class RoomGallery
{
public int Id { get; set; }
public int RoomId { get; set; }
public Room Room { get; set; }
[StringLength(100)] public string Description { get; set; }
[StringLength(10)] public string Status { get; set; }
}
Index page
<form method="post" asp-action="Save" asp-controller="RoomGallery">
<h6 style="margin-top: 2em;">Room:</h6>
#(Html.Kendo().DropDownList()
.Name("RoomId")
.HtmlAttributes(new { style = "width:100%" })
.OptionLabel("Select room...")
.DataTextField("RoomName")
.DataValueField("Id")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetRooms", "Reservation");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
)
<br />
<div class="demo-section">
#(Html.Kendo().Upload()
.Name("images")
.HtmlAttributes(new { aria_label = "files" })
)
<p style="padding-top: 1em; text-align: right">
<button>Submit</button>
</p>
</div>
</form>
Just remember all the gallery you make, and retrieve their Id after you save:
var newRGs = new List<RoomGallery>();
foreach (var image in images)
{
var fileContent = ContentDispositionHeaderValue.Parse(image.ContentDisposition);
fileName = Path.GetFileName(fileContent.FileName.ToString().Trim('"'));
var gallery = new RoomGallery()
{
RoomId = vm.RoomId,
Description=fileName,
Status="A"
};
Context.RoomGalleries.Add(gallery);
newRGs.Add(gallery);
}
Then you can get the IDs for whatever you need after you have saved (the value the db calculated will be patched back into the local object)
var ids = newRGs.Select(rg => rg.Id).ToList();
If you want to use them in your view, don't forget to patch them back into your view model (what you want to use them for is unclear to me; the main point of this answer is "if you remember which objects you add to a context before you save then you can enumerate them for any db calculated values after you have saved")
Update your code to below,
public ActionResult Save(IEnumerable<IFormFile> images,RoomGalleryVM vm)
{
List<int> Ids=new List<int>();
if (images!= null)
{
string fileName = "";
foreach (var image in images)
{
var fileContent = ContentDispositionHeaderValue.Parse(image.ContentDisposition);
fileName = Path.GetFileName(fileContent.FileName.ToString().Trim('"'));
var gallery = new RoomGallery()
{
RoomId = vm.RoomId,
Description=fileName,
Status="A"
};
Context.RoomGalleries.Add(gallery);
Context.SaveChanges();
Ids.Add(gallery.Id)
}
}
var data = Ids; // here you will get list of ids
return View("RoomGalleryIndex");
}
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>
}
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];
}
}
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)
}
}
})
});
I have a ViewModel and a DropDownList with some values on my page:
public class MyViewModel
{
public string SelectedItemDrop1 { get; set; }
public string SelectedItemDrop2 { get; set; }
public string SelectedItemDrop3 { get; set; }
public List<OptionViewModel> Options { get; set; }
}
public class OptionViewModel
{
public string Number { get; set; }
public string Option { get; set; }
}
And, into my View:
#using (Html.BeginForm("Save", "Controller", FormMethod.Post))
{
<ul id="cursos">
<li>
#Html.DropDownListFor(c => c.SelectedItemDrop1,
new SelectList(Model.Options, "Number", "Option", Model.SelectedItemDrop1))
Choose 1
</li>
<li>
#Html.DropDownListFor(c => c.SelectedItemDrop2,
new SelectList(Model.Options, "Number", "Option", Model.SelectedItemDrop2))
Choose 2
</li>
<li>
#Html.DropDownListFor(c => c.SelectedItemDrop3,
new SelectList(Model.Options, "Number", "Option", Model.SelectedItemDrop3))
Choose 3
</li>
</ul>
}
When I use Javascript to change options from these select elements, my return is null. What's the problem? Thank you so much!!!
EDIT:
My javascript code:
$("#cursos li select").each(function (i, item) {
$(this).change(function () {
updateCursos($(this), 7);
});
$(this).blur(function () {
if ($(this).val() != -1) {
$(this).attr('disabled', 'disabled');
}
});
});
function updateCursos(select, avaiableCourses) {
var selecteds = '';
$("#cursos li select").each(function (i, item) {
var selected = $(item).val();
if (selected != -1)
selecteds += selected + ',';
});
var arr = selecteds.split(',');
$("#cursos li select").each(function (i, item) {
if ($(item).val() != select.val()) {
var oldValue = $(item).val();
if ($(item).val() == -1) {
var options = "<option value='-1'></option>";
for (i = 1; i <= avaiableCourses; ++i) {
if (select.val() != i && !contains(i, selecteds)) {
options += "<option value='" + i + "'>" + i + "ª option</option>";
}
}
options += "<option value='0'>Don't want it</option>";
$(item).children("option").remove();
$(item).html(options);
$(item).val(oldValue);
}
}
});
}
This way I'm sure that works, not even being javascript.
If the values are not being filled are coming from the database, with fixed values example: active, inactive, do the following
Create an enumerated type with these values
After the controller transforms this type enumerated in list using an internal class, turn it into a enumerable and go to the dropdownlist
example of usage:
# Html.DropDownListFor (model => model.chamados.UrgenciaId, new SelectList (ViewBag.urgencia, "Id", "Name"))