I'm migrating a webforms project to MVC and I've come across a repeater control. The repeater control allows for inline editing (by this I mean each line that can be edited (because there is a criteria to allow this) has input controls on it where you can change the values and an edit button that takes those values and updates the db) as well as display of records displayed in a table.
With the repeater control it's easy to tell which record got updated since when that records edit button is clicked it calls the ItemCommand function passing in the row that was edited allowing me to just get that control name value to get the values. How is this done in MVC? I know we have display templates and edit templates but I need to combine them into 1, which I'm able to do with the below code, but if I have more than 1 row that can be edited how do I get the correct row of input controls to get the values?
#foreach (var item in Model.Data)
{
if(item.User == "PIRA" || DateTime.Today != item.EntryDate.Value.Date)
{
<tr style="height: 25px;">
<td hidden="hidden">
#Html.DisplayFor(modelItem => item.TransactionID)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.EntryDate)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.User)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.EffectiveDate)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.RecordType)
</td>
<td align="right">
#Html.DisplayFor(modelItem => item.Value)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.Comments)
</td>
<td align="center">
</td>
</tr>
}
else
{
<tr style="height: 25px;">
<td hidden="hidden">
#Html.DisplayFor(modelItem => item.TransactionID)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.EntryDate)
</td>
<td align="center">
#Html.DisplayFor(modelItem => item.User)
</td>
<td align="center">
#Html.EditorFor(modelItem => item.EffectiveDate)
</td>
<td align="center">
#Html.DropDownListFor(modelItem => item.RecordType, Model.RecordTypes)
</td>
<td align="center">
#Html.EditorFor(modelItem => item.Value, new { style = "text-align: right;" })
</td>
<td align="center">
#Html.EditorFor(modelItem => item.Comments, new { cols = 50, #rows = 3 })
</td>
<td align="center">
<div style="margin-bottom:10px;">
<input type="submit" value="Edit" name="action:Edit" />
</div>
<div>
#Html.ActionLinkWithList("Delete", "Delete", "Capacity", new { id = item.TransactionID }, null)
</div>
</td>
</tr>
}
}
I was able to get this by not using DisplayFor() but instead the control *For() function so I can set the id property (because DisplayFor() doesn't seem to allow this). I set the id to include that records transactionID so each input can be unique. Then each records edit link passes in the transactionID to a javascript function. Then inside there once I have the id I can build the id properties to get all the input values and make my controller call.
ie.
#Html.TextBoxFor(modelItem => item.EffectiveDate, new { id="effectiveDate_" + item.TransactionID, #class="datepicker", style="width: 150px;" })
#Html.ActionLink("Edit", "Edit", "Data", new { onclick = "EditRecord(" + item.TransactionID + ");" })
function EditRecord(id) {
// get the control values based off the passed in id
var effDate = $("#effectiveDate_" + id).val();
// make the call to our controller passing in the edited row data
$.post("/Data/Edit/", { effectiveDate: effDate });
}
Kind of a pain to do it manually like this but it works. In the web forms repeater control this is probably what it's doing behind the scenes so I wish MVC had something like this as well.
Related
I have an ASP.Net MVC website and I want to pass a textbox value from view to controller using URL Action.
Below is my code,
<table class="table table-striped table-bordered table-hover" id="products" width="100%">
<thead>
<tr>
<th>ProductId</th>
<th>Name</th>
<th>ShortName</th>
<th>ProductNamePrefix</th>
<th>Minimum Count</th>
<th>Add Product</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProductId)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.ShortName)
</td>
<td>
#Html.DisplayFor(modelItem => item.ProductNamePrefix)
</td>
<td>
#Html.TextBox("MinCount", item.MinimumCount, new {#class = "form-control" + " " + item.ProductId})
</td>
<td>
#if (item.IfExists == true)
{
<div class='isa_success'><i class='fa fa-check'></i></div>
}
#if (item.IfExists == false)
{
<div class='isa_info'><i class='fa fa-plus-circle'></i></div>
}
</td>
</tr>
}
</tbody>
</table>
I want to pass the textbox value in url action method to controller. I am creating a custom class based on the id for textbox but I am not able to retrieve it using javascript or jQuery.
but I am not able to retrieve it using javascript or jQuery
Why not? It's pretty simple.
In the absence of JavaScript, you can't do this with just a link. Instead, you'd need to use a form. So you'd have to wrap each table row in a form element and replace your link with an input type="submit". (And, of course, style it according to your needs.) This would post the values of the contained form elements (including the text box) to the form's action.
This does get a little ugly, since you can't wrap a form around a tr, so you'd have to nest your structure. Something like this:
<table>
<tr>
<td>
<form>
<table><!-- Your 1-row table goes here --></table>
</form>
</td>
</tr>
</table>
The outer tr is what gets repeated for each row of your "table", so each row is actually a single cell containing a nested table which itself has only one row. As I said, a bit ugly. But it gets the job done.
I am using Datatables version 1.10.6.
In jquery.dataTables.js i am getting above error.
0x800a138f - JavaScript runtime error: Unable to get property 'mData' of undefined or null reference.
$.each( _fnGetRowElements( oSettings, rowOne[0] ).cells, function (i, cell) {
var col = oSettings.aoColumns[i];
if ( col.mData === i ) {// this line gives error
var sort = a( cell, 'sort' ) || a( cell, 'order' );
var filter = a( cell, 'filter' ) || a( cell, 'search' );
if ( sort !== null || filter !== null ) {
col.mData = {
_: i+'.display',
sort: sort !== null ? i+'.#data-'+sort : undefined,
type: sort !== null ? i+'.#data-'+sort : undefined,
filter: filter !== null ? i+'.#data-'+filter : undefined
};
_fnColumnOptions( oSettings, i );
}
}
} );
}
In MVC i am using :
<script type="text/javascript">
$(document).ready(function () {
// Setup - add a text input to each footer cell
$('#example tfoot th').each(function () {
var title = $('#example thead th').eq($(this).index()).text();
$(this).html('<input type="text" placeholder="Search ' + title + '" />');
});
// DataTable
var table = $('#example').DataTable();
alert(table);
// Apply the search
table.columns().every(function () {
var that = this;
$('input', this.footer()).on('keyup change', function () {
that
.search(this.value)
.draw();
});
});
});
</script>
<table class="display" id="example" cellspacing="0">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Employee Id</th>
<th>Active</th>
<th>SFDC</th>
<th>System User</th>
</tr>
</thead>
<tfoot>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Employee Id</th>
<th>Active</th>
<th>SFDC</th>
<th>System User</th>
</tr>
</tfoot>
<tbody>
#foreach (var item in Model)
{
<tr>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.FirstName)
</td>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.LastName)
</td>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.EmailAddress)
</td>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.EmployeeId)
</td>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.IsActive, true)
</td>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.IsSFDC, true)
</td>
<td valign="middle" align="center">
#Html.DisplayFor(modelitem => item.IsSystemUser, true)
</td>
<td valign="middle" align="center">
#Html.ActionLink(" ", "Edit", new { id = item.UserId }, new { #class = "edit_btn", title = "Edit" })
#Html.ActionLink(" ", "Details", new { id = item.UserId }, new { #class = "details", title = "Details" })
#Html.ActionLink(" ", "Delete", new { id = item.UserId }, new { #class = "delete", title = "Delete" })
</td>
</tr>
}
</tbody>
</table>
The mismatch in the number of header columns cause this issue, there should be equal number of header columns and the row columns.
I have added an extra <th></th> eight column was missing
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Employee Id</th>
<th>Active</th>
<th>SFDC</th>
<th>System User</th>
<th></th>
When I got this error I tried the above solutions and after looking at the source code on the webpage, I saw that the entire inner contents of the table was in a <tbody> instead of separated into <thead> and <tbody>. So, make sure you have the following with an equal number of header columns as cells in each row like mentioned above:
<table>
<thead>
<tr>
<th></th>
...
<tr>
</thead>
<tbody>
<tr>
<td></td>
...
</tr>
</tbody>
</table>
After I did this, it worked!
I faced same situation. For my case, due to runat="server" attribute in table tag.
Once removed runat="server" from table declaration, it worked well.
This could also happen when an action attached to a JQuery selector cannot execute successfully for a particular document's element. Therefore, you either change the element's id or fix the action's JavaScript code.
I have a PersonViewModel that consists of several List<SubViewModel> (Name, Address, etc.)
I have an Html Form with tables for each SubViewModel, where the user can update,add,delete and set items to the primary entry for the person.
a simplified table for names:
#foreach (PersonNameViewModel name in Model.Names)
{
<tr>
<td>
#Html.DisplayFor(modelItem => name.FullName)
</td>
<td>
#Html.DisplayFor(modelItem => name.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => name.MiddleName)
</td>
<td>
#Html.DisplayFor(modelItem => name.LastName)
</td>
<td class="center">
#Html.RadioButtonFor(modelItem => name.IsPrimary, "")
</td>
<td class="center">
#Html.CheckBoxFor(modelItem => name.ToDelete)
</td>
</tr>
}
From here, I don't know how to get all the data back into the PersonViewModel when the user submits the form, to update the person record. If I simply accept a PersonViewModel in my POST controller, it's completely empty. I looked at FormCollection, but I'm not sure if that's right for this. Can someone with experience point me in the right direction for building my model back up for updating? Thankyou very much.
Everyone's answer is correct, But if you want to update people's information, you need to use Html Helper Editor NOT Display. Also, you still can use foreach in your view, just change it like below:
#foreach (var name in Model.Names.Select((value, i) => new { i, value }))
{
<tr>
<td>
#Html.EditorFor(m => m.Names[#name.i].FullName)
</td>
<td>
#Html.EditorFor(m => m.Names[#name.i].FirstName)
</td>
<td>
#Html.EditorFor(m => m.Names[#name.i].MiddleName)
</td>
<td>
#Html.EditorFor(m => m.Names[#name.i].LastName)
</td>
<td class="center">
#Html.RadioButtonFor(m => m.Names[#name.i].IsPrimary,"")
</td>
<td class="center">
#Html.CheckBoxFor(m => m.Names[#name.i].ToDelete)
</td>
</tr>
}
You should use sth like this (good article about model binding of collections and complex data here):
#for(var i = 0;i < Model.Names.Count;i++)
{
<tr>
<td>
#Html.DisplayFor(m => Model.Names[i].FullName)
</td>
<td>
#Html.DisplayFor(m => Model.Names[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => Model.Names[i].MiddleName)
</td>
<td>
#Html.DisplayFor(m => Model.Names[i].LastName)
</td>
<td class="center">
#Html.RadioButtonFor(m => Model.Names[i].IsPrimary, "")
</td>
<td class="center">
#Html.CheckBoxFor(m => Model.Names[i].ToDelete)
</td>
</tr>
}
Try replacing the foreach with a for loop:
#for (int i = 0; i < Model.Names.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.Names[i].FullName)
</td>
<td>
#Html.DisplayFor(m => m.Names[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => m.Names[i].MiddleName)
</td>
<td>
#Html.DisplayFor(m => m.Names[i].LastName)
</td>
<td class="center">
#Html.RadioButtonFor(m => m.Names[i].IsPrimary, "")
</td>
<td class="center">
#Html.CheckBoxFor(m => m.Names[i].ToDelete)
</td>
</tr>
}
The expression parsing/model binding does a better job with indexes than with foreach temp variables.
This is a problem I have been working on for a very long time to no avail.
Basically I have two Models: Units and Trades, what i want is when I am on the View for Units, which returns a paged list of the units. What I want is an additional column to the table which returns a count of how many entries in Trades have Trade.Name = model.Name for each Unit.
The first problem I am having is accessing two models from one view. I have tried tons of things based on searching, but can't seem to be able to make anything work.
The second problem is how to actually do the count. Is it possible to use Linq directly from the View? It hasn't been working for me so far.
Thanks in advance or any help!
The important part of the Units view:
#model PagedList.IPagedList<FTv2.Models.Unit>
<table style="border-width: 1px; border-color:#000000; border-style: solid; border-top: 2px; border-top-color:#000000; border-top-style: solid;">
<tr>
<th></th>
<th>Name</th>
<th>Type</th>
<th>Skill</th>
<th>Rating</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#{ ViewBag.ImgUrl = item.Name + ".png";}
<a href="/Images/#ViewBag.ImgUrl" data-lightzap="" ><img src="/Images/#ViewBag.ImgUrl" HEIGHT="66" WIDTH="50" ></a>
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Type)
</td>
<td>
#Html.DisplayFor(modelItem => item.Skill)
</td>
<td>
#Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<!-- this is where I would want the count to go. -->
</td>
</tr>
}
</table>
Last issue, not showing any results in the View.
Here's the relevant parts of the controller:
var units = db.Units;
var students = db.Units.Select(u => new UnitViewModel()
{
Unit = u,
TradeCount =
db.Movies.Where(t => t.Name == u.Name).Count()
});
return View(students.ToPagedList(pageNumber, pageSize));
Get everything you need server side and pass it to the view. You can do the count in the controller GET action first, and pass it using ViewBag or add a property in your view model to hold the counts.
[HttpGet]
public ActionResult MyView()
{
var units = _context.Units.Where(//whatever);
var viewModels = units.Select(u => new UnitViewModel()
{
Unit=u,
TradeCount =
context.Trades.Where(t => t.name == u.name).Count()
});
return View(viewModels);
}
EDIT:
I would write a view model class for your view. So instead of the view using a model of List<Unit>, now it uses List<UnitViewModel>.
public class UnitViewModel
{
public Unit Unit {get;set;}
public int TradeCount {get;set;}
}
EDIT VIEW:
#model PagedList.IPagedList<FTv2.Models.UnitViewModel>
<table style="border-width: 1px; border-color:#000000; border-style: solid; border-top: 2px; border-top-color:#000000; border-top-style: solid;">
<tr>
<th></th>
<th>Name</th>
<th>Type</th>
<th>Skill</th>
<th>Rating</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#{ ViewBag.ImgUrl = item.Name + ".png";}
<a href="/Images/#ViewBag.ImgUrl" data-lightzap="" ><img src="/Images/#ViewBag.ImgUrl" HEIGHT="66" WIDTH="50" ></a>
</td>
<td>
#Html.DisplayFor(modelItem => item.Unit.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Unit.Type)
</td>
<td>
#Html.DisplayFor(modelItem => item.Unit.Skill)
</td>
<td>
#Html.DisplayFor(modelItem => item.Unit.Rating)
</td>
<td>
#Html.DisplayFor(modelItem => item.TradeCount)
</td>
</tr>
}
</table>
I got the following Razor view that display a list of HDD so an user and add to a cart. When the user press the button next to each row of HDD, it will pass the quantity and HDD's identity to a controller. However, while each HDD's ID does display properly, "hddId" is always 1 and "quantity" is correctly 5 when I inspect the Controller's parameters.
#model IEnumerable<TestStore.Models.Hdd>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
#using(Html.BeginForm("AddToCart","Cart")){
<table>
<tr>
<th>
Ident
</th>
<th>
Brand
</th>
<th>
Name
</th>
<th>
Model
</th>
<th>
Speed
</th>
<th>
Capacity
</th>
<th>
Cache
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.hddId)
</td>
<td>
#Html.Hidden("hddId", item.hddId)
#Html.Hidden("quantity", 5)
#Html.DisplayFor(modelItem => item.brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.model)
</td>
<td>
#Html.DisplayFor(modelItem => item.speed)
</td>
<td>
#Html.DisplayFor(modelItem => item.capacity)
</td>
<td>
#Html.DisplayFor(modelItem => item.cache)
</td>
<td>
<input type="submit" value="Add to Cart" />
</td>
</tr>
}
</table>
}
Codingbiz is correct. The submit button wasn't referring to any particular row but the entire table. I suspect the reason why "hddId" was always "1" is because that's the first reference of "hddId" it see when the form is submitted. I changed it so #using(Html.BeginForm(...)) is inside the #foreach loop so each row has its own form and submit button:
#foreach (var item in Model) {
<tr>
#using(Html.BeginForm("AddToCart","Cart")){
<td>
#Html.DisplayFor(modelItem => item.hddId)
</td>
<td>
#Html.Hidden("hddId", item.hddId)
#Html.Hidden("quantity", 5)
#Html.DisplayFor(modelItem => item.brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.model)
</td>
<td>
#Html.DisplayFor(modelItem => item.speed)
</td>
<td>
#Html.DisplayFor(modelItem => item.capacity)
</td>
<td>
#Html.DisplayFor(modelItem => item.cache)
</td>
<td>
<input type="submit" value="Add to Cart" />
</td>
}
</tr>
}
As others have said, this doesn't work because when you submit, you're submitting every row in the table, not just the one clicked.
One option is to do what they suggest in this article:
http://federmanscripts.com/2010/01/12/form-and-table-row-nesting-workaround/
That is, using javascript, you copy the form values on button press to a hidden row and submit that.
I got around the validation problem by using button type="submit" instead of input type="submit". It still works correctly by passing the id number to the controller.
#using(Html.BeginForm("AddToCart","Cart")){
<table>
<tr>
//header stuff here
</tr>
#foreach (var item in Model) {
//row data goes here
//remove hidden field reference to #item.hddId
<tr>
<td>
<button type="submit" value="#item.hddId" name="hddId">Add to Cart</button>
</td>
</tr>
</table>
}