I have a partial view that is bound to an object Cart. Cart has a collection of CartLines. My view is below:
<tbody>
<% foreach (var line in Model.Lines) { %>
<tr>
<td align="center"><%=Html.CatalogImage(line.Product.DefaultImage, 80) %></td>
<td align="left">
<%=Html.ActionLink(line.Product.Name, "Product", "Catalog",
new { productId = line.Product.Id }, new { title = "View " + line.Product.Name })%>
</td>
<td align="right"><%= line.Product.Price.ToString("c")%></td>
<td align="center">
<%=Html.Hidden("lines[" + i + "].key", line.Product.Id) %>
<%=Html.TextBox("lines[" + i + "].value", line.Quantity, new { #class = "quantity" })%>
</td>
<td align="right"><%= (line.LineTotal).ToString("c")%></td>
<td>
<%using (Ajax.BeginForm("RemoveFromCart", "Cart",
new {ProductId = line.Product.Id, returnUrl = ViewData["returnUrl"]},
new AjaxOptions { UpdateTargetId="cart", LoadingElementId="loading" }))
{%>
<input type="image" src="<%=AppHelper.ImageUrl("delete.gif")%>" value="Remove item" />
<%} %>
</td>
</tr>
<% i++; } %>
</tbody>
There are two things to note. The first is that I am using a form per line for removing items.
The second is that I had attempted to allow users to change the quantity of line items and then click an update button to pass all the changes to the controller action:
// POST: /Cart/Update
[HttpPost]
public ActionResult Update(Cart cart, IDictionary<int,int> lines, string returnUrl)
{
foreach (var line in lines) {
Product p = _catalogService.GetProduct(line.Key);
cart.UpdateItem(p, line.Value);
}
if (Request.IsAjaxRequest())
return PartialView("Cart", cart);
else
return RedirectToAction("Index", new { returnUrl });
}
Note that I am using a dictionary since I am only concerned about the product and quantity. I don't really like the fact that I am having to retrieve the product again before calling cart.UpdateItem but I couldn't figure out how to pass the Product from the model to my action instead of the id.
The main problem however, is rather stupidly I wrapped the entire cart in a form so that I could post back the values and then spent a good hour wondering why things were not working correctly in IE - doh! nested forms
So I am stuck on how to get round this. I want the ability to remove items individually but allow a user to change item quantities and then pass all changes at once to the controller. I can't use links for my remove action as I would need to use javascript to force a post and everything must work without javascript enabled.
[Update]
Would a better solution be to allow updates on my custom model binder? This way I could make changes inside my view and post the cart object back to the controller - although I'm not sure whether this is possible with child collections (Cart.CartItems).
I've had a look on sites like Amazon and it would appear they wrap the entire cart in a form and both global update buttons and indidivual remove item buttons post back to the same action when javascript is disabled.
Any help would be appreciated.
Thanks,
Ben
There is only one way here and thats the ugly way. Have 1 form around everything.
Then in the action you have to check which button was pressed (you get the name of the button in the request).
It gets even more ugly with differences in firefox and ie. If you have a button pressed ie or firefox (Dont remember which one) not only sends the name of the pressed button, but also the location where the button was pressed.
You have more options if your solution can rely on JS enabled browsers. But thats another story.
Related
I have found the answer to the question "how to execute POST method using hyperlink (actionlink) instead of button" in this topic.
I have used form's submit method in this way - looks straightforward:
#foreach (var item in Model)
{
using (Html.BeginForm("Delete", "Area", new { id = item.id }, FormMethod.Post))
{
<tr>
...
<td>
Delete
<input id="sbmt" type="submit" style="visibility: hidden" />
</td>
</tr>
}
}
Typical declaration without button declaration
Delete
will not work.
The questions are:
1/ Is it possible to do the same operation but without hiding the button? I mean I don't want to use (and hide) button in my code at all.
2/ What are pros and cons of the solution listed above?
It is possible without button.One of the way is Just simply giving id to your form like
using (Html.BeginForm("Delete", "Area", new { id = item.id }, FormMethod.Post,new {id="frm"}))
{
}
and then
and if not jquery then
What is the problem with button I don't know. I can not see any cons except dirty coding(bad way) to submit a form.
I have a problem with calls to action with POST attribute exclusively from Grid.MVC tool.
I set up a column within the Grid.MVC tool, setting the property RenderValueAs follows:
#Html.Grid(Model).Columns(columns =>
{
// other lines...
columns.Add().Encoded(false)
.Sanitized(false)
.RenderValueAs(item => Html.ActionLink("Delete", "Delete", new { item.ID }));
}).WithPaging(3).Sortable(true)
In my controller I have set the following code which refers to the Delete action:
[HttpPost]
public ActionResult Delete(int id)
{
// anything here
return RedirectToAction("Index");
}
The best practices tell you that the action method should support only POST requests, because deleting objects is not an idempotent operation. This because browsers and caches are free to make GET requests without the user’s explicit consent, and so I must be careful to avoid making changes as a consequence of GET requests.
But, when I try to apply this configuration I have the next error:
Server Error in application '/'
Can not find the resource.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly
But if I remove the [HttpPost] attribute the code works. Is this behavior correct?
Thanks
UPDATE:
Bayu, you have reason, now I explain the problem from another scenario: I had my View as follows:
<table>
<tr>
<th>ID</th>
#*another columns*#
<th>Name</th>
<th>Actions</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.ID</td>
#*another columns*#
<td>#item.Name</td>
<td>
#using (Html.BeginForm("Delete", "Admin"))
{
#Html.Hidden("ID", item.ID)
<input type="submit"
value="Delete row" />
}
</td>
</tr>
}
</table>
That is, I used HTML tables, and therefore I could use an input to build a button that allows me to delete a row from the table.
Now, I want to use the tool Grid.View (because among other benefits allows me effortlessly filter columns), but I can not use an input for this purpose.
You have any idea how?
Change new { item.ID }) to new {id = item.ID })
Suppose I have a simple search form with a textbox. And upon submitting the form I send the contents of the textbox to a stored procedure which returns to me the results. I want the results to be displayed on the same page the form was, except just below it.
Right now I'm doing the following but it's not working out exactly the way I want:
"Index" View of my SearchController
#using (Html.BeginForm("SearchResults", "Search", FormMethod.Post, new { #class = "searchform" }))`{
<fieldset>
<legend>Name</legend>
<div class="editor-label">
#Html.Label("Search")
</div>
<div class="editor-field">
#Html.TextBox("Name")
</div>
<input type="submit" value="Search" class="formbutton" />
</fieldset>
#{ Html.RenderPartial("SearchResults", null);
This is my "SearchResults" View:
#model IEnumerable<MyProject.Models.spSearchName_Result>
<table>
#foreach (var item in Model)
{
<tr>
<td>
#item.Name
</td>
</tr>
}
</table>
This is my Controller:
// GET: /Search/SearchResult
[HttpPost]
public ActionResult SearchResult(FormCollection collection)
{
var result = myentity.spSearchName(collection["Name"]);
return PartialView("SearchResults", result);
}
I can only seem to get the results to display on an entirely new page (not being embedded as a partial view), or I get an error when I load the search page because there are no results (since I hadn't searched yet).
Is there any better way to achieve what I'm trying to do? I feel like I'm going against some of the best practices in MVC.
You could return the results in a ViewData object then only show on the view it if is not null.
Very similar to this question MVC 3 form post and persisting model data
For this instance it looks like you are not passing the results to your partial view. Try this?
#{ Html.RenderPartial("SearchResults", Model.Results);
Since you are not persisting your search information using a model, the search information will be lost when the search form is posted.
Given your design and stated goal, it would be best to convert the form in your Index view into an Ajax form, then your controller can send your partial view back to populate a div below your Ajax form.
counsellorben
I'm starting out with MVC but not sure it's the best option.
I need to create a form that is based upon a collection. Eg it might look like this:
product Price
Item 1 [textbox]
Item 2 [textbox]
[submit button]
where "item" is pulled from the database and textbox allows users to update the price.
essentially this is a type of datagrid but i don't want webforms style update each row one at a time i need to update the entire set of text boxes in one post.
Ideally I don't want a javascript based solution as it has to work without javascript.
Is this possible in MVC or should I stick to webforms (where I could do this in a repeater by iterating through he repeater items on postback)
I wrote blog entry about it: ASP.NET MVC - Binding model to a list
To summarize:
For every row you have to generate inputs with proper prefixes. Sample:
<% foreach (var item in Model)
{ %>
<tr>
<td>
<%= Html.Hidden("contacts[" + i + "].ID", item.ID)%>
<%= Html.TextBox("contacts[" + i + "].Name", item.Name)%>
</td>
<td>
<%= Html.TextBox("contacts[" + i + "].Surname",item.Surname)%>
</td>
<td>
<%= Html.TextBox("contacts[" + i + "].Phone",item.Phone)%>
</td>
</tr>
<%
i++;
} %>
And then in controller:
[HttpPost]
public ActionResult List(IEnumerable<Contact> contacts)
{
//Here we have populated contact list, contacs parameter is filled with data from form. You save it here to your repository.
return RedirectToAction("List");
}
How should I initiate a delete action from my view?
Creating a new form-tag for each entity just doesn't seem right :-)
<% foreach (var subscriber in group.Subscribers) { %>
<tr>
<td><%= subscriber.Email %></td>
<td><%= Html.ActionLink("[edit]", "edit", "subscriber", new {id=subscriber.SubscriberId}, null) %></td>
<td>
<form id="delete-subscriber-form" method="post" action="<%= Url.Action( "delete", "subscriber", new { #subscriberId = subscriber.SubscriberId }) %>">
<input type="submit" value="Delete" />
</form>
</td>
</tr>
<% } %>
How would you do it?
I normally use checkboxes on the side of the items. Then I can have action links (buttons, whatever) that apply an action to the selected items (such as delete).
you can use CSS and Javascript , add 'forDel' css class for all the elments you want to delete , if you are going to use jquery you can do it like this:
$(".element").each(function(){
$(this).data("id","the id of the element in db")
});
$(".element").toggle(function(){
$(this).addClass("forDel");
},function(){
$(this).removeClass("forDel");
});
and then on pressing the delete button:
var idsForDel;
$(".forDel").each(function(){
idsForDel = $(this).data("id"); + ";";
})
you can pass the idsForDel to the Controller ... and split it in the server side.
It depends on the situation, if you are doing CRUD operations you would normally go with one <form> tag per operation (delete,edit,new). If, however, you are displaying a list and you want to be able to 'multiple delete' items with one click then you will have to approach it from a different angle as you need to encapsulate all the required information into one form.
EDIT
Having had another look at your post above I notice you are providing a 'Delete Button' against each element in the list. For atomic actions like this (i.e. the user expects something to happen straight after they have clicked a button) I would definitely use one form per item.
What I wrote above still applies...