Backbone collection sort Material datatable component - sorting

I am building an app using MarionetteJS with Material Lite.
I have an issue when sorting the collection in a datatable component.
ListView js is a Marionette.CompositeView instance
Here is the code:
listView.js
events: {
'click .sort': 'onSort'
},
onSort: function(e) {
e.preventDefault();
var dataTable = this.$('.mdl-data-table');
var element = this.$(e.currentTarget);
this.collection.sortField = element.data('field');
this.collection.sortDir = element.hasClass('mdl-data-table__header--sorted-descending') ? -1 : 1;
//sort collection
this.collection.sort();
}
schema
comparator: function(m1) {
var field = this.sortField;
var dir = this.sortDir;
return (dir == -1) ? -m1.get(field) : m1.get(field);
}
template.html
<table class="mdl-data-table mdl-js-data-table mdl-data-table--selectable">
<thead>
<tr>
<th class="mdl-data-table__cell--non-numeric">Category</th>
<th class="mdl-data-table__cell--non-numeric mdl-data-table__header--sorted-ascending sort" data-field="entry_date">Date</th>
<th class="mdl-data-table__cell mdl-data-table__header--sorted-ascending sort" data-field="amount">Amount</th>
<th class="mdl-data-table__cell--non-numeric">Kind</th>
<th class="mdl-data-table__cell"></th>
</tr>
</thead>
<tbody class="records-items"></tbody>
</table>
so when the collection is sorted, i have to change the classes in the element like this:
//update UI
if(this.collection.sortDir == 1) {
element.addClass('mdl-data-table__header--sorted-descending');
element.removeClass('mdl-data-table__header--sorted-ascending');
this.collection.sortDir = -1;
} else {
if(this.collection.sortDir == -1) {
element.addClass('mdl-data-table__header--sorted-ascending');
element.removeClass('mdl-data-table__header--sorted-descending');
this.collection.sortDir = 1;
}
}
but when call onRender, which calls component.UpgradeDom() internally the changes are not applied to the element (th), because the datatable is rendered from scratch..
listView.js
onRender: function() {
componentHandler.upgradeDom();
}
my question is, when to update the element css classes?
thanks in advance :)

Try something like:
templateHelpers: function() {
return {
sortDir: this.collection.sortDir,
sortAmount: this.collection.sortField === 'amount',
sortEntryDate: this.collection.sortField === 'entry_date'
};
}
and then use those in your template to render the class based on your stored state.
Personally I would also not use hasClass use the data you've stored to determine what class to display.

Related

Ajax to MVC POST request: Redirection issue, "Page not working"

I am having a tiny issue after my Post request (from AJAX to the controller). Basically, the post request takes place, it executes the function in the controller, however, after it executes the ajax call, I get the following page:
I don't know why that is happening, and I would appreciate some help. I haven't worked with this kind of stuff before.
Here are some code snippets that can help:
EDITED .js file:
function Export() {
var donations = new Array();
$("#Donations tbody tr").each(function () {
var row = $(this);
var donation = {};
donation.Name = row.find("td").eq(0)[0].innerText;
donation.DOB = row.find("td").eq(1)[0].innerText;
donation.DOD = row.find("td").eq(2)[0].innerText;
donation.COD = row.find("td").eq(3)[0].innerText;
donation.CaseNumber = row.find("td").eq(4)[0].innerText;
donations.push(donation);
});
$.ajax({
type: "POST",
url: "/Donation/Export",
data: JSON.stringify(donations),
dataType: "json",
success: function (data) {
console.log("file saved: ", data);
}
}).done(function () {
window.location.href = '#Url.Action("Download", "DonationController", new { csv = data }))';
});;
};
EDITED Index.cshtml:
#using (Html.BeginForm())
{
<p>
<input type="submit" class="btn btn-outline-primary btn-sm" value="Export" onclick="Export()" />
</p>
<table id="Donations" class="table">
<thead>
<tr>
<th>Full Name</th>
<th>#Html.DisplayNameFor(model => model.Person.DateOfBirth)</th>
<th>#Html.DisplayNameFor(model => model.Donation.DateOfDeath)</th>
<th>#Html.DisplayNameFor(model => model.Donation.CauseOfDeath)</th>
<th>#Html.DisplayNameFor(model => model.Donation.CaseNumber)</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Donations)
{
<tr>
<td><a asp-action="Details" asp-controller="Person" asp-route-id="#item.PersonId">#Html.DisplayFor(modelItem => item.Person.Title) #Html.DisplayFor(modelItem => item.Person.Forenames) #Html.DisplayFor(modelItem => item.Person.Surname)</a></td>
<td>#Html.DisplayFor(modelItem => item.Person.DateOfBirth)</td>
<td>#Html.DisplayFor(modelItem => item.DateOfDeath)</td>
<td>#Html.DisplayFor(modelItem => item.CauseOfDeath)</td>
<td><a asp-action="Details" asp-controller="Donation" asp-route-id="#item.PersonId">#Html.DisplayFor(modelItem => item.CaseNumber)</a></td>
</tr>
}
</tbody>
</table>
}
EDITED DonationController.cs:
[HttpPost]
public string Export()
{
var resolveRequest = HttpContext.Request;
string[] columnNames = { "Name", "DOB","DateOfDeath", "CauseOfDeath", "CaseNumber" };
//Build the CSV file data as a Comma separated string.
string csv = string.Empty;
foreach (string columnName in columnNames)
{
//Add the Header row for CSV file.
csv += columnName + ',';
}
//Add new line.
csv += "\r\n";
foreach (string k in resolveRequest.Form.Keys)
{
using JsonDocument doc = JsonDocument.Parse(k);
JsonElement root = doc.RootElement;;
var users = root.EnumerateArray();
while (users.MoveNext())
{
var user = users.Current;
var props = user.EnumerateObject();
while (props.MoveNext())
{
var prop = props.Current;
csv += String.IsNullOrEmpty(prop.Value.ToString()) ? "," : prop.Value.ToString().Replace(",", ";") + ',';
//Console.WriteLine($"{prop.Name}: {prop.Value}");
}
csv += "\r\n";
}
}
return (csv);
}
public FileContentResult Download(string csv)
{
//Download the CSV file.
byte[] bytes = Encoding.ASCII.GetBytes(csv);
return File(bytes, "application/text", "Donations.csv");
}
File cannot be passed as a querystring, which will cause the payload format is in an unsupported format. This will result in a 415 error.
In your Export method(IActionResult,return a Jsonresult):
[HttpPost]
public IActionResult Export([FromBody] List<ExportedValues> values)
{
//...
return new JsonResult (new {csv = csv });
}
Then in your Download method:
public FileContentResult Download(string csv)
{
return File(//Convert to your file)
}
In your ajax:
$.ajax({
type: "POST",
url: "/Donation/Export",
data: JSON.stringify(donations),
dataType: "json",
success: function (data) {
console.log("file saved: ", data);
window.location = '/Donation/Download?csv=' + data.csv;
}
});

View not rendering after foreach loop MVC

Goal: I am attempting to populate a table based on a dropdown list using Razor syntax to populate my table.
Summary: I am passing the model into my View and looping thru each object within my model. I am able to see the objects populated within the model when debugging the View, however, when the page actually displays, There is nothing in the table and the only thing that is displaying is the dropdown.
Question: What may be the problem with the page rendering?
My view is as follows:
#model IEnumerable<FantasySportsMVC.Models.PlayerDetails>
#{
ViewBag.Title = "Position";
}
<h2>Position</h2>
<body>
<div>
#Html.DropDownList("ddlTournaments",(IEnumerable<SelectListItem>)ViewBag.Tournaments, new { id="ddlTournament", name="ddlTournament"})
</div>
<div>
<input type="button" id="btnGetData" value="Show me some stuff, Yo!" />
</div>
<div id="results">
</div>
<table id="tbDetails">
#if(Model != null)
{
<tbody>
#foreach (var player in Model)
{
<tr>
<td>#player.LastName</td>
<td>#player.FirstName</td>
<td>#player.Position</td>
</tr>
}
</tbody>
}
</table>
</body>
<script type="text/javascript">
function SendTournamentId() {
var data = JSON.stringify({ id : $("#ddlTournament option:selected").val()});
$.ajax({
url: '/Leaderboard/Position',
type: 'POST',
dataType: 'json',
data: data,
contentType: 'application/json; charset=utf-8',
success: function (result) {
//return JSON.stringify($("#ddlTournament option:selected").val());
$("#ddlTournament option:selected").val(result.d.id);
}
});
}
$(function () {
$('#btnGetData').click(SendTournamentId);
});
</script>
My Controller is as follows:
public class LeaderboardController : Controller
{
public ActionResult Position()
{
ViewBag.Tournaments = GetTournamentDetailsSelectList();
return View();
}
[HttpPost]
public ActionResult Position(string id)
{
ViewBag.Tournaments = GetTournamentDetailsSelectList();
var tournamentId = id;
var url = ConstructLeaderboardUrl(tournamentId);
var xmlToJsonUrl = ConvertXmltoJson(url);
List<PlayerDetails> details = BindDataTablePlayerDetails(xmlToJsonUrl);
return View(details);
}
}
private static List<PlayerDetails> BindDataTablePlayerDetails(string url)
{
dtAttributeList = new DataTable();
var details = new List<PlayerDetails>();
try
{
//ConvertXmltoJson(url);
// Construct Datatable
dtAttributeList.Columns.Add("Last Name", typeof(string));
dtAttributeList.Columns.Add("First Name", typeof(string));
dtAttributeList.Columns.Add("Position", typeof(string));
// Add rows to Datatable from Json
for (int i = 0; i < doc.GetElementsByTagName("player").Count; i++)
{
dtAttributeList.Rows.Add(
doc.GetElementsByTagName("player").Item(i).Attributes["last_name"].Value,
doc.GetElementsByTagName("player").Item(i).Attributes["first_name"].Value,
doc.GetElementsByTagName("player").Item(i).Attributes["position"].Value);
}
// Add rows from Datatable to PlayerDetails
foreach (DataRow row in dtAttributeList.Rows)
{
var player = new PlayerDetails();
player.LastName = row["Last Name"].ToString();
player.FirstName = row["First Name"].ToString();
player.Position = row["Position"].ToString();
details.Add(player);
}
}
catch (Exception e)
{
throw new Exception();
}
return details;
}

Ajax post method not returning value from function

My Ajax call works successfully inside my function but I am unable to return the the result outside the function. Does this have something to do with Ajax or how I am trying to return the result from the function?
HTML
<table>
<tr>
<th> Vote </th>
</tr>
<tbody>
<tr class="vote">
<td id="upvote">1</td>
<td id="downvote">-1</td>
</tr>
<tr>
<td id="newvote"></td>
</tr>
</tbody>
</table>
JQuery
$(document).ready(function(e) {
var myvote = allFunction4(); //returns undefined
alert(myvote);
})
function allFunction4() {
$('.vote').children().click(function() {
var vote = $(this).text();
var timestamp = 1369705456; //value I know exits in db
$.post('forumvote.php', {'timestamp': timestamp, 'vote': vote}, function(result, success) {
var newvotes = result;
alert(newvotes); //this works
return newvotes;
})
})
}
That is because I think you are returning value from a function within the $.post... the return statement is NOT returning from the allfunction4().
You may want to re-refer the JQuery documentation on JQuery.post at http://api.jquery.com/jQuery.post/
// Assign handlers immediately after making the request,
// and remember the jqxhr object for this request
var jqxhr = $.post("example.php", function() {
alert("success");
})
.done(function() { alert("second success"); })
.fail(function() { alert("error"); })
.always(function() { alert("finished"); });
// perform other work here ...
// Set another completion function for the request above
jqxhr.always(function(){ alert("second finished"); });

How can I access the values of my view's dropdown list in my controller?

How can I access the values of my view's dropdown list in my controller?
var typeList = from e in db.Rubrics where e.DepartmentID == 2 select e;
var selectedRubrics = typeList.Select(r => r.Category);
IList <String> rubricsList = selectedRubrics.ToList();
IList<SelectListItem> iliSLI = new List<SelectListItem>();
SelectListItem selectedrubrics = new SelectListItem();
selectedrubrics.Text = "Choose a category";
selectedrubrics.Value = "1";
selectedrubrics.Selected = true;
iliSLI.Add(selectedrubrics);
for(int i = 0;i<rubricsList.Count();++i)
{
iliSLI.Add(new SelectListItem() {
Text = rubricsList[i], Value = i.ToString(), Selected = false });
}
ViewData["categories"] = iliSLI;
In my view this works fine showing the dropdown values:
#Html.DropDownList("categories")
Then in my controller I am using FormCollection like this:
String[] AllGradeCategories = frmcol["categories"].Split(',');
When I put a breakpoint here, I get an array of 1’s in AllGradeCategories. What am I doing wrong?
MORE:
Here’s my begin form:
#using (Html.BeginForm("History", "Attendance",
new {courseID = HttpContext.Current.Session ["sCourseID"] },
FormMethod.Post, new { #id = "formName", #name = "formName" }))
{
<td>
#Html.TextBoxFor(modelItem => item.HomeworkGrade, new { Value = "7" })
#Html.ValidationMessageFor(modelItem => item.HomeworkGrade)
</td>
<td>
#Html.TextBoxFor(modelItem => item.attendanceCode, new { Value = "1" })
#Html.ValidationMessageFor(model => model.Enrollments.FirstOrDefault().attendanceCode)
</td>
<td>
#Html.EditorFor(modelItem => item.classDays)
</td>
<td>
#Html.DropDownList("categories")
</td>
}
My controller signature:
public ActionResult ClassAttendance(InstructorIndexData viewModel, int id, FormCollection frmcol, int rows, String sTeacher, DateTime? date = null)
EDITED 2
Tried this but although it seems to get posted, the I still don’t get the values of the list in the hidden field or the categories parameter.
#Html.Hidden("dropdownselected1");
#Html.DropDownList("categories",ViewBag.categories as SelectList, new { onchange = "changed" })
</td>
$(function () {
$("#dropdownselected1").val($("#categories").val());
});
If you are looking to access the selected value from the dropdown list, use a hidden field and repopulate that field on onChange() javascript event of the dropdown list.
but you can use normal
#Html.Hidden("dropdownselected1");
#Html.DropDownList("categories",new { onchange ="changed();"}
function changed()
{
$("#hiddenfieldid").val($("#dropdownlistid").val());
}
should do.
I ended up creating a ViewModel to handle just the drowdown list with an associated partial view. Then in the controller I accessed the values of the list using FormCollection().

MVC3 Custom HTMLHelper, partial view or other solution to apply DRY principle

I've got an MVC3 Read Only view that contains a table displaying properties for an Item.
For many of the properties of the Item, we track the changes a Vendor has made to the item. So, for example, a vendor may update a property named 'Color' from a value of 'Blue' to 'Red'. In this View a table lists each property tracked in a table row, with a column showing the 'Old Value' and the 'New Value'. The next column either shows the current change's status (Awaiting Approval, Approved, or Rejected). However, for Admin users, the column will contain Links ('Approve', 'Reject', or 'Reset to Awaiting Approval').
My markup and Razor code for this is very repetitive and getting out of hand. I'd like to create an HTMLHelper for this, or possibly a partial view that I can use to move all the code into and then use it for each Item Property.
Here is an example of the code used for one Property. This code is repeated for another 10 or so properties.
I'm using some jquery and ajax for the actions. For example, when an change is rejected, the user must enter a reason for rejecting the change.
<tr id="rowId-color">
<td>#Html.LabelFor(model => model.Color)</td>
<td>#Html.DisplayFor(model => model.Color)</td>
#if (Model.ChangeLog != null && Model.ChangeLog.Item("Color") != null) {
var change = Model.ChangeLog.Item("Color");
var changeStatus = (ItemEnumerations.ItemChangeStatuses)change.ItemChangeStatusID;
<td>#change.OldValueDisplay</td>
<td id="tdstatusId-#change.ItemChangeID">
#if (changeStatus == ItemEnumerations.ItemChangeStatuses.AwaitingApproval && User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Approve", "Approve", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Approve this change?", OnSuccess = "actionCompleted" })
#Html.Raw("|")
<a href="#dialog" name="reject" data-id="#change.ItemChangeID" >Reject</a>
}
else if ((changeStatus == ItemEnumerations.ItemChangeStatuses.Rejected || changeStatus == ItemEnumerations.ItemChangeStatuses.Approved) && User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Reset to Awaiting Approval", "Reset", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Reset this change to Awaiting Approval?", OnSuccess = "actionCompleted" })
}
else {
#changeStatus.ToDisplayString()
}
</td>
<td id="tdreasonId-#change.ItemChangeID">#Html.DisplayFor(m => m.ChangeLog.Item(change.ItemChangeID).RejectedReason)</td>
}
else {
<td colspan="3">No Change</td>
}
</tr>
This really sounds more like a DisplayTemplate for the ItemChangeModel type, that way you can just do:
<tr id="rowId-color">
<td>#Html.LabelFor(model => model.Color)</td>
<td>#Html.DisplayFor(model => model.Color)</td>
#Html.DisplayFor(m => m.ChangeLog.Item("Color"))
</tr>
For each ChangeLog cell and the display template then is like a mini-view with a typed model of ItemChangeModel. So your view file would like like this:
#model ItemChangeModel
#if(Model != null) {
<td>#Html.DisplayFor(m => m.OldValueDisplay)</td>
<td id="tdstatusId-#Model.ItemChangeID">
#switch((ItemEnumerations.ItemChangeStatuses) Model.ItemChangeStatusID) {
case ItemEnumerations.ItemChangeStatuses.AwaitingApproval:
if(User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Approve", "Approve", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Approve this change?", OnSuccess = "actionCompleted" })
#Html.Raw("|")
<a href="#dialog" name="reject" data-id="#change.ItemChangeID" >Reject</a>
}
break;
case ItemEnumerations.ItemChangeStatuses.Rejected:
case ItemEnumerations.ItemChangeStatuses.Approved:
if(User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Reset to Awaiting Approval", "Reset", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Reset this change to Awaiting Approval?", OnSuccess = "actionCompleted" })
} else {
#changeStatus.ToDisplayString()
}
#break;
}
</td>
<td id="tdreasonId-#change.ItemChangeID">#Html.DisplayFor(m => m.RejectedReason) </td>
} else {
<td colspan="3">No Change</td>
}
(Hard to code in editor box, this could use some cleanup, but I think you will get the idea)
You add this display template (with the file name ItemChangeModel.cshtml) to the Views\Shared\DisplayTemplates folder and it will get used whenever a DisplayFor call is made on that type.
Its been noted in comments that you can't use a method in DisplayFor, but you can change that to an indexed property:
public class ChangeLog
{
public ItemChangeModel this[string key] { get { return Item("Color"); } }
}
Then use:
#Html.DisplayFor(m => m.ChangeLog["Color"])
You haven't shown nor explained how your domain and view models look like but I suspect that what you are using here is not an appropriate view model for this specific requirement of the view. A better view model would have been one that has a list of properties to approve which would be shown in the table.
Anyway, one possible approach is to write a custom HTML helper so that your view looks like this:
<tr id="rowId-color">
#Html.DisplayFor(x => x.Color)
#Html.ChangeLogFor(x => x.Color)
</tr>
...
and the helper might be something along the line of:
public static class HtmlExtensions
{
public static IHtmlString ChangeLogFor<TProperty>(
this HtmlHelper<MyViewModel> html,
Expression<Func<MyViewModel, TProperty>> ex
)
{
var model = html.ViewData.Model;
var itemName = ((MemberExpression)ex.Body).Member.Name;
var change = model.ChangeLog.Item(itemName);
if (change == null)
{
return new HtmlString("<td colspan=\"3\">No Change</td>");
}
var isUserTVAPMgr = html.ViewContext.HttpContext.User.IsInRole("TVAPMgr");
var changeStatus = (ItemChangeStatuses)change.ItemChangeStatusID;
var sb = new StringBuilder();
sb.AppendFormat("<td>{0}</td>", html.Encode(change.OldValueDisplay));
sb.AppendFormat("<td id=\"tdstatusId-{0}\">", change.ItemChangeID);
var ajax = new AjaxHelper<MyViewModel>(html.ViewContext, html.ViewDataContainer);
if (changeStatus == ItemChangeStatuses.AwaitingApproval && isUserTVAPMgr)
{
sb.Append(
ajax.ActionLink(
"Approve",
"Approve",
new {
itemChangeID = change.ItemChangeID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Approve this change?",
OnSuccess = "actionCompleted"
}).ToHtmlString()
);
sb.Append("|");
sb.AppendFormat("Reject", change.ItemChangeID);
}
else if ((changeStatus == ItemChangeStatuses.Rejected || changeStatus == ItemChangeStatuses.Approved) && isUserTVAPMgr)
{
sb.Append(
ajax.ActionLink(
"Reset to Awaiting Approval",
"Reset",
new {
itemChangeID = change.ItemChangeID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Reset this change to Awaiting Approval?",
OnSuccess = "actionCompleted"
}
).ToHtmlString()
);
}
else
{
sb.Append(changeStatus.ToDisplayString());
}
sb.AppendLine("</td>");
sb.AppendFormat(
"<td id=\"tdreasonId-{0}\">{1}</td>",
change.ItemChangeID,
html.Encode(model.ChangeLog.Item(change.ItemChangeID).RejectedReason)
);
return new HtmlString(sb.ToString());
}
}
A better approach would be to re-adapt your view model to the requirements of this view and simply use display templates.

Resources