set value submit from collection submit mvc - c#

I want to get in controller and Model and specific value from collection which equal line on which I press button
<table id="Products" class="Products">
<tr>
<th>ProductId</th>
<th>Productname</th>
<th>Quantity</th>
<th>UnitPrice</th>
</tr>
<% for(int i=0; i < Model.NorthOrderDetails.Count; i++)
{ %>
<tr>
<td><%: Html.Label(Model.NorthOrderDetails[i].ProductID.ToString()) %></td>
<td><%: Html.Label(Model.NorthOrderDetails[i].ProductName) %> </td>
<td><%: Html.TextBoxFor(m => m.NorthOrderDetails[i].Quantity) %></td>
<td><%: Html.TextBoxFor(m => m.NorthOrderDetails[i].UnitPrice) %></td>
<td><%: #Html.ActionLink("Go to second view", "ViewTwo", "Order", Model, null)%></td>
<input type="submit" title="ads" value =<%: Model.NorthOrderDetails[i].ProductID.ToString()%> name=ssad />
<tr>
<% } %>
</table>
Can I set value in submit from collection, for example
<input type="submit" title="ads" value =<%: Model.NorthOrderDetails[i].ProductID.ToString()%> name=ssad />
And this value will equal 17, for example in controller. This work, but how I can change of text in button from value in collection to any text?
UPDATE
I use code of Stephen Muecke, but I edit table because I use aspx page
<td><button type="button" class="delete" data-id="<%:Model.NorthOrderDetails[i].ProductID %>">Delete</button><td>
<td><input type="hidden" name="<%:Model.NorthOrderDetails[i].ProductName %>" value="<%:i %>" /><td>
And, unfortunately the script doesn't call controller

Rather than doing a full post and regenerating the view each time you want to delete an item, you can use ajax to post the items ID value to a controller method that deletes the item in the database and then remove that item from the DOM. This will greatly improve performance and means you can probably avoid using Session.
Change the view to (sorry, but this is Razor syntax)
#for (int i = 0; i < Model.NorthOrderDetails.Count; i++)
{
<tr>
<td>#Html.LabelFor(Model.NorthOrderDetails[i].ProductID)</td> // ToString not required
<td>#Html.Label(Model.NorthOrderDetails[i].ProductName)</td>
<td>#Html.TextBoxFor(m => m.NorthOrderDetails[i].Quantity)></td>
<td>#Html.TextBoxFor(m => m.NorthOrderDetails[i].UnitPrice)</td>
<td>#Html.ActionLink("Go to second view", "ViewTwo", "Order", Model, null)</td> // This wont work
<td>
<button type="button" class="delete" data-id="#Model.NorthOrderDetails[i].ProductID">Delete</button><td> // change this
<input type="hidden" name="#Model.NorthOrderDetails.Index" value="#i" /> // add this
</tr>
}
</table>
<input type="submit" value="Save" /> // add this
Notes:
Your action link will not work (your cannot pass a collection to a
GET method) I suspect you mean #Html.ActionLink("Go to second view", "ViewTwo", "Order", new { ID = Model.NorthOrderDetails[i].ProductID }, null) so you can pass the productID to the ViewTwo() method
Change the submit button in each row to a normal button, and add one submit button at the end (to save all changes to your textboxes in one post)
Add the special hidden input for an Index property. This is used
by the DefaultModelBinder to match up collections where the
indexers are non-consecutive (which they will be if you delete items
in the middle of the collection)
You don't render any input for the ProductID which means you wont
be able to identify the products on post back. You will need to add
a hidden input for it
Then add the following script
var url = '#Url.Action("Delete", "YourControllerName")';
$('.delete').click(function() {
var id = $(this).data('id'); // Get the product ID
var row = $(this).closest('tr') // Get the table row
$.post(url, { ID: id }, function(data) {
if(data) {
row.remove(); // remove the row from the table
} else {
// oops!
}
});
});
And the controller
public ActionResult View(IEnumerable<YourModel> model)
{
// Save your collection and redirect
}
[HttpPost]
public JsonResult Delete(int ID)
{
// Delete the product in the database based on the ID
return Json(true);
}
Note: If deleting an item could throw and exception of fail in some way, then you should return Json(null); so it can be checked in the ajax method.

Related

How can I bind a dynamic-length List<> in a Razor/.NET Core PageModel, without relying on JavaScript to re-index each input?

I have a form in which a user can supply an arbitrary-length list of <DateTime, int> pairs. It is represented like so:
List<ItemsPerDay> ItemsPerDayList = new List<ItemsPerDay>();
public class ItemsPerDay {
public DateTime Date { get; set; }
public int Amount { get; set; }
}
<tbody>
#{ var i = 0; }
#foreach (var _ in Model.ItemsPerDayList) {
<tr>
<td><input asp-for="ItemsPerDayList[i].Date" type="date" /></td>
<td><input asp-for="ItemsPerDayList[i].Amount" /></td>
<td><a class="remove">Remove</a></td>
</tr>
i++;
}
</tbody>
The issue:
The user is able to add/remove rows as they need. However, the property binding relies on the pairs being properly indexed. If, for example, you remove the first item, the list now begins at [1] and the property binding does not work; ItemsPerDayList is posted as null.
My current workaround:
I've had to use some JavaScript to make sure the indexes always remain correct. This works but isn't optimal.
function reIndexItemRows() {
$("table > tbody > tr").each(function(idx) {
$(this).find("input[type=date]").attr({
"data-val": true,
"data-val-required": "The Date field is required.",
id: `ItemsPerDayList_${idx}__Date`,
name: `ItemsPerDayList[${idx}].Date`
});
$(this).find("input[type=number]").attr({
"data-val": true,
"data-val-required": "The Amount field is required.",
id: `ItemsPerDayList_${idx}__Amount`,
name: `ItemsPerDayList[${idx}].Amount`
});
});
}
The question:
What is the appropriate way to represent this model on the front-end, such that I don't have to rely on JavaScript to groom the form each time a row is added or removed?
NOTE: I am not doing any updates, therefore the indexes are not necessary. Upon submission, any existing pairs are deleted, and the form-submitted pairs are inserted.
JavaScript is necessary for adjusting index. You can add events to adjust the index when submitting the form.
Add a event on Remove. Here is the form.
<form method="post" id="myform">
<table>
<tbody>
#{ var i = 0; }
#foreach (var _ in Model.ItemsPerDayList)
{
<tr>
<td><input asp-for="ItemsPerDayList[i].Date" type="date" /></td>
<td><input asp-for="ItemsPerDayList[i].Amount" /></td>
<td><a class="remove" onclick="remove(this)" >Remove</a></td>
</tr>
i++;
}
</tbody>
</table>
<input type="submit" name="name" value="submit" />
</form>
<button id="add" onclick="add()" class="btn-primary">add</button>
Before submitting the form, javascript iterates each row and modify the index.
#section Scripts{
<script>
$('#myform').submit(function () {
var i = 0;
$("tbody> tr ").each(function () {
$(this).find("td input[name$='Date']").attr("name", "ItemsPerDayList[" + i + "].Date");
$(this).find("td input[name$='Amount']").attr("name", "ItemsPerDayList[" + i + "].Amount");
i++
})
// ...
return true; // return false to cancel form action
});
function remove(e) {
$(e).parent().parent().remove()
}
function add() {
$('tbody').append('<tr><td> <input name="ItemsPerDayList[i].Date" type="date" /></td ><td><input name="ItemsPerDayList[i].Amount" /><td><a class="remove" onclick="remove(this)">Remove</a></td></tr>');
}
</script>
}
Then, I can get the all data from front-end.

get values in controller class from html form elements with the same name

In my MVC project, I created a table from a database I made, and added Edit/Save and Delete buttons to interact with the DB accordingly.
I created the code in the cshtml file with razor-notation for loop, which means that my input elements all have the same name (to be called in the controller).
I'm trying to figure out how to single out a specific row and get a specific element, even though there are multiple elements with the same name (number of rows in my table).
snippet of my view:
#{
int i=0;
foreach(link l in Model.links)
{
<tr>
<td><input class="idField" name="link.Id" type="text" value="#l.Id" disabled="disabled" /></td>
<td><input class="linkField" name="link.link" type="text" value="#l.link" disabled="disabled" /></td>
<td><input class="timeField" name="link.Time" type="text" value="#l.Id" disabled="disabled" /></td>
<td>
<input class="edit" type="button" value="Edit" formaction="Update" onclick="enableField(#i)" />
</td>
</tr>
i++;
}
}
Update action in my controller:
public ActionResut Update(){
LinkDal dal = new LinkDal(); /*LinkDal uses Entity framework to connect to my DB*/
List<Link> links = dal.links.ToList<Link>();
Link toUpdate = dal.links.Find(int.Parse(Request.Form["link.Id"].ToString()));
toUpdate.link = Request.Form["link.link"].ToString();
toUpdate.Time = int.Parse(Request.Form["link.link"].ToString());
dal.SaveChanges();
VMLinks vml = new VMLinks();
vml.link = toUpdate;
vml.links = links;
return View("MyView", vml);
}
instead of gettin the specific "link" object, "toUpdate" becomes null because the Request.Form doesn't return anything.
I tried typing an ID that I know it exists and the code works (the DB is updated).
My problem is that I'm not being able to access the text inside the input element because there are multiple elements with the same name (for example - "link.Id").

Bind dynamically created element to complex mvc model in view

I have a very huge form in my application with a lot of different inputs and a lot of lists in my model. So i will try to add/delete the lists without sending the complete model to the server.
I tried several ways now but i don´t find a clean way. You can imagine my model like:
public class EditSomething
{
public string name { get; set;}
public List<something> somethingList { get; set;}
// a lot other fields...
public EditSomething(EditSomethingFromDatabase editSomethingFromDatabase)
{
name = editSomethingFromDatabase.Name;
somethingList = new List<SomethingModel>();
foreach(var something in editSomethingFromDatabase.Something)
{
somethingList.Add(new SomethingModel(editSomethingFromDatabase.Something));
}
}
}
The other model looks similar but without lists.
In the view i have a table for the model:
<h2>Something</h2>
<div id="SomethingDiv">
<table id="SomethingTable">
<thead>
<tr>
<th>#Html.Label("SomethingName")</th>
<th>#Html.Label("SomethingID")</th>
<th></th>
</tr>
</thead>
<tbody id="SomethingTableBody">
#Html.EditorFor(x => x.somethingList)
</tbody>
</table>
<p>
<input type="button" name="addSomething" value="Add Something" id="AddSomething">
</p>
</div>
the jquery of the addSomething is:
$('#AddSomething').click(function () {
$.ajax({
url: '#Url.Action("AddSomething", "SomethingModels")',
data: { tableSize: $('#SomethingTable tr').length },
cache: false,
success: function (html) { $('#SomethingTable tr:last').after(html); }
});
The controller method AddSomething is:
public ActionResult AddSomething (int tableSize)
{
SomethingModel something= new SomethingModel(null, (-2) * (tableSize + 1));
return PartialView(""~/Views/EditorTemplates/EditSomethingModel.cshtml"", something);
}
And at least i have a editor template in EditorTemplates as for editorfor and partialview. This have the important informations i want to send to the server:
#model SomethingModel
<tr>#TextBoxFor(m=>m.SomethingName)<td>
#TextBoxFor(m=>m.SomethingID)
So the problem now is, that the submit of the first view only post the SomethingModel to the server who already existed while opening the view but the new SomethingModel from the AddMutation method aren´t in the post. Someone an idea to fix this?
Edit: Changed the path to the editor template so i only need one view for the EditorFor and PartialView.
Edit2: To solve the main problem i created a view as following and use it as partial view. Now the data is send to the server correctlly. Only the validation on client side is still not working:
#model SomethingModel
<tr>#TextBoxFor(m=>m.SomethingName, new{Name="somethingList["+ViewBag.ListId+"].SomethingName")<span class="field-validation-valid" data-valmsg-for="somethingList[#ViewBag.ListId].SomethingName" data-valmsg-replace="true"></span><td>
<tr>#TextBoxFor(m=>m.SomethingID, new{Name="somethingList["+ViewBag.ListId+"].SomethingID")<span class="field-validation-valid" data-valmsg-for="somethingList[#ViewBag.ListId].SomethingID" data-valmsg-replace="true"></span><td>
</tr>
In the AddSomething method i added the ViewBag.ListId with the id of the next element in the list.
It seems a reasonable enough approach, but You've not shown your EditorTemplate, so I'm going to assume its something like:
#model List<something>
#for(int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m[i].Id) #Html.HiddenFor(m => m[i].Id)</td>
<td>#Html.EditorFor(m => m[i].Name)</td>
</tr>
}
Your ajax method should return the HTML of a row - and this is important... the form fields need to be named 1 above the last one in the table.
So when you view the rendered source of your table (before adding any new fields it might look like:
...
<tbody>
<tr>
<td>1 <input type="hidden" name="something[0].Id" value="1"/></td>
<td><input type="text" name="something[0].Name" value="somename" /></td>
</tr>
</tbody>
You need to ensure the html returned by the ajax method for your new row is:
<tr>
<td>2 <input type="hidden" name="something[1].Id" value="2"/></td>
<td><input type="text" name="something[1].Name" value="somenewname" /></td>
</tr>
ie. the number inside the brackets is the next index for the items in something. If there is a gap in the indexes (or they overlap) then the new items will not get parsed.
EDIT - to get client side validation to work for the new fields alter your jquery ajax success callback as follows:
$('#AddSomething').click(function () {
$.ajax({
url: '#Url.Action("AddSomething", "SomethingModels")',
data: { tableSize: $('#SomethingTable tr').length },
cache: false,
success: function (html) {
$('#SomethingTable tr:last').after(html);
$.validator.unobtrusive.parse('#SomethingTable');
}
});

Check if a checkbox is checked in a list of items

I'm building an MVC app and right now my view generates a pack of items. The user needs to check a checkbox if he wants to send the data.
Here's my view and how it is builded:
<script type="text/javascript">
$(document).ready(function() {
//alert("The document is ready");
$("#selectAll").click(function() {
//alert("The case has been clicked");
var chkValue = $(this).is(":checked");
$(".divChckBox").prop("checked", chkValue);
});
});
</script>
<p>
#using (Html.BeginForm("SendObj", "Manager"))
{
<p>
Select / UnSelet All Items #Html.CheckBox("selectAll", true)
</p>
<table>
<tr>
<th>Card Name</th>
<th>Number In Stock</th>
(...)
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(x => x[i].m_OthObj.m_ObjName)</td>
<td>#Html.DisplayFor(x => x[i].m_NbInStock)#Html.HiddenFor(x => x[i].m_NbInStock)</td>
(...)
<td>
<input type="checkbox" name="itdoesnotmatter" class="divChckBox" checked="true"/>
</td>
</tr>
}
</table>
<input type="submit" value="Send"/>
}
</p>
So you understand why I cannot use "CheckboxFor". Now what I want to do is send only the items which checkbox status is "checked". I know how to do this via model binding (checkboxfor), but I'm clueless as to how to build this.
I need to return a list of items. So how could I do this? Thank you very much!
Your form will return the values based on name, so shoot whoever told you such a stupid name :)
Use
<input type="checkbox" name="InStock" class="divChckBox" checked="true" value="#Model[i].ID" />
Or something more representative. Note that it is CRITICAL that you supply a unique identifier as the value of your checkbox. The value is how you will identify what was checked!
In your controller, there's several ways you can capture it. I do it like this:
public ActionResult Create(List<int> InStock)
{
foreach(var inStockItem in InStock)
{
//do what you need to do
}
}
The important points:
List<int> InStock
This must match the NAME attribute on your checkbox. The actual values will be the Value of your checkboxes.
Here I just randomly selected Create for your Action, but you need to make it match whatever action you are in (Edit, Index, etc..)
Good Luck!
try using the attr method to change the property checked.
$(document).ready(function() {
$("#selectAll").click(function() {
var chkValue = $(this).is(":checked");
$(".divChckBox").attr("checked", chkValue);
});
});
View code:
<!-- note "x[i].m_id"; Use the entity's id property is here
...maybe this should be m_NbInStock? -->
<input type="checkbox" name="selectedItems" value="#x[i].m_id" class="divChckBox" checked="true"/>
Controller code:
public class Manager : Controller
{
/* ... */
[HttpPost]
public ActionResult SendObj(IList<Int32> selectedItems)
{
// Grab those items by their IDs found within `selectedItems` and perform
// any processing necessary
// ...
//return View();
}
/* ... */
}

How can I create a list of object based on checkboxfor created for the model

I have this view based on a list of a model where I create strongly-typed checkboxes for each items of the model based on a boolean.
Here's my view:
#using MyApp.Models
#model IList<MyApp.Models.ObjInfo>
#{
ViewBag.Title = "Obj Inventory";
}
<h2>Search Inventory</h2>
<p>
#using (Html.BeginForm())
{
(Many search filters which are non-relevant)
<p>
Send Items: #Html.ActionLink("Click Here", "SendItems")
</p>
}
<table>
<tr>
<th>
Obj Name
</th>
<th>
Number In Stock
</th>
(...)
<th>
Select Item
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(modelItem => item.m_IsSelected)
</td>
</tr>
}
</table>
The whole process works fine and I can actually generate a view with checkboxes for each item of my list of model.
Now my question is that I want to create a list which would regroup only the items in the list which are checked and send them to the controller. How could I do that? Can anyone help me or suggest me a way to work?
Thank you!
* EDIT *
Here is the HttpPost Method used to get the List of items as mentioned below:
//
// GET: /Inventory/SendItems
[HttpPost]
public ActionResult SendItems(IList<ObjInfo> listToSend)
{
m_ListObjToSend = new List<ObjInfo>();
foreach (var item in listToSend.Where(item => item.m_IsSelected))
{
m_ListObjToSend .Add(item);
}
return View(m_ListObjToSend );
}
However I have encountered many problems:
This method does NOT work if I put the [HttpPost] attribute (it will show as "Not Found");
The list I am supposed to receive is null;
Each hiddenfield linked with the checkbox has default value as false even if the checked value shows true;
I am using an actionlink because I do not want to use a button, there is already one that is doing another job.
I am open for any comments / help available, thank you!
If you use the CheckBoxFor helper to generate checkboxes you will notice that it generates an additional hidden field along with each checkbox. This means that all values will be sent to the controller and you will have to filter in your controller those that are checked.
Also I would recommend you using indexes to ensure proper model binding. You just need to use an IList<ObjInfo> or ObjInfo[] which is trivially easy achievable by calling .ToList() or .ToArray() extension methods on your view model before passing it to the view:
#using MyApp.Models
#model IList<ObjInfo>
...
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(x => x[i].m_IsSelected)
</td>
</tr>
}
...
And now your controller action could directly take the list of items:
[HttpPost]
public ActionResult SomeAction(IEnumerable<ObjInfo> model)
{
...
}
and if you wanted to find the selected values, you could simply get them through LINQ:
[HttpPost]
public ActionResult SomeAction(IEnumerable<ObjInfo> model)
{
var selectedItems = model.Where(x => x.m_IsSelected);
...
}
Remark: m_Name and m_IsSelected is a disastrously bad naming convention for a properties in C#.
UPDATE:
Another issue you have with your code is that your Html.BeginForm doesn't contain any input field. It has only a single ActionLink which obviously only does a GET request. If you want to submit the values you should wrap your entire table with the form and use a submit button and not some action links:
#using MyApp.Models
#model IList<ObjInfo>
#{
ViewBag.Title = "Obj Inventory";
}
<h2>Search Inventory</h2>
<p>
#using (Html.BeginForm("SendItems", null, FormMethod.Post))
{
(Many search filters which are non-relevant)
<table>
<tr>
<th>Obj Name</th>
<th>Number In Stock</th>
(...)
<th>Select Item</th>
</tr>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
<!--
This will not be sent to your controller because it's only a label.
You will need a corresponding hidden field if you want to get that value back
-->
#Html.DisplayFor(x => x[i].OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(x => x[i].m_IsSelected)
</td>
</tr>
}
</table>
<p>
Send Items: <button type="submit">Click Here</button>
</p>
}
</p>
So really, 2 things you should learn:
The naming convention that the default model binder expects when binding to a list
How to use a javascript debugging tool (such as FireBug and/or Chrome Developper Toolbar) which will allow you to inspect all the values that are sent to your server and immediately recognized whether you respected the convention you learned in 1.

Categories

Resources