We have a list bound to view as
#model List<DataModels.UseCase>
This view contains html form as
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count(); i++)
{
#Html.CheckBoxFor(m => Model[i].IsSelected)
//few other controls as
}
<input type="submit" value="Submit Selection" >
}
And In Controller, POST method is like below
[HttpPost]
public ActionResult payment([Bind(Include = "Id,IsSelected// few other properties")] List<UseCase> useCases)
{
// Few business logic
return View();
}
Please note- Just for example I have shown only check box control on form, there are other few controls as well.
Now, in this case for example view contains 10 records but out of 10 only 2 are selected then we need to pass only 2 selected records to POST method and not all 10. This is to reduce overload on POST method.
Can we achieve this type of scenario in any way?
Good question, I might implement this on my projects as well.
I could only think of one way-- using javascript, when form is submitted, delete the other form input fields first then resubmit the form.
First is we need to put the input fields inside a parent div with class input-container, so we could quickly delete all the fields by just deleting the entire div. I also added a class targetCheckbox to your input field so we could attach an event to it;
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count(); i++)
{
<div class="input-group">
#Html.CheckBoxFor(m => Model[i].IsSelected, new { #class="targetCheckbox" })
//few other controls as
<div class="input-group">
}
<input type="submit" value="Submit Selection" >
}
We'll need to bind an event to to your form. On form submit, we need to identify which targetCheckbox are not checked, then delete the div that contains them. We also need to replace the indexes of the input fields because ASP.NET MVC model binding must start with 0 and should not skip. After all that resubmit the form;
<script>
$(document).ready(function(){
$("form").submit(function(e){
e.preventDefault();
var index = 0;
// loop through all the checkbox
$(".targetCheckbox").each(function(){
if($(this).is(":checked")){
// get the parent
var parent = $(this).closest(".input-container");
// loop through all the input fields inside the parent
var inputFieldsInsideParent = $(parent).find(":input");
// change the index inside the name attribute
$(inputFieldsInsideParent).each(function(){
var name = $(this).attr("name");
var firstBracket = name.IndexOf("[");
var secondBracket = name.IndexOf("]");
if(firstBracket != null && secondBracket != null){
// check if this is a valid input field to replace
var newName = name.substring(0,firstBracket)+index+name.substring(secondBracket);
// result should be IntputFieldName[newIndex].Property
// assign the new name
$(this).attr("name",newName);
}
});
index++;
}else{
// empty the parent
$(this).closest(".input-container").html("");
}
});
// submit the form
$(this).submit();
});
});
</script>
Related
I have a view model which gets displayed in a cshtml view like this:
#model MyViewModel
<form action="...">
#foreach (var item in this.Model.MyList)
{
<input type="checkbox" name="item.Name"/>
}
</form>
Then I have a controller method on the backend:
[HttpPost]
public ActionResult SaveMyViewModel(MyViewModel viewModel)
{
...
When I inspect the viewModel in the controller method while POSTing, it has all null properties. I would expect it to have values in MyList and in there, bools for each item in MyList.
Is this possible without Ajax? I cannot use ajax here.
What must be done to the form in order to properly return an accurate representation of the viewModel back to the server?
I am assuming your button to submit the form is within the <form> element in HTML.
When you are POSTing a List (Array) of an Object from HTML to Controller, you need to add index of each item in the List to the HTML name property.
Additionally, the name property should also have same name as of the Property in the C# code.
Your code should look like below to work properly.
<form action="...">
#{
var i = 0;
}
#foreach (var item in this.Model.MyList)
{ var nameChecked = string.Empty;
if(item.Name != null) nameChecked = “checked”;
<input type="checkbox" name="viewModel[i].Name" #nameChecked />
i++;
}
</form>
NOTE: This is a basic code sample. You can always enhance to work more efficiently.
Hope this helps!
I'm trying to create paging inside html form.
How do I make each page button change the model's CurrentPage property?
I'm new to MVC, so bear with me please.
I have a list of reports that I need to display in a view named Search. I made a model for that (ReportViewModel), and another model that holds a list of those, named SearchViewModel. In SearchViewModel I have some other variables like SearchName, SearchDate, CurrentPage, PageCount, PageSize, etc. The goal is to do paging and searching in one action named Search in ReportsController.
Now, I would like to keep that action simple by giving that function only SearchViewModel instance as a parameter, so that inside I use all variables of that model. Doing that, I won't have to type parameter names twice (once in SearchViewModel and once as parameters in Search action). I will not need that search function elsewhere, so it's ok for it to not have any parameters other than SearchViewModel instance.
So, very loosely, it looks something like this:
ReportsController.cs
public class ReportsController : Controller
{
public ActionResult Search(SearchViewModel model)
{
if (!ModelState.IsValid)
return View(model);
// do search magic here
return View(model)
}
}
Search.cshtml
#model ISFinReports.Models.SearchViewModel
#{
ViewBag.Title = "List";
Layout = "~/Views/_masterLayout.cshtml";
}
<link href="~/CSS/Search.css" rel="stylesheet" />
<script src="~/Scripts/Search.js"></script>
#using (Html.BeginForm("Search", "Reports", FormMethod.Get))
{
<div id="divSearchBar">
// add #Html.DropDownListFor, #Html.TextBoxFor and submit button here
</div>
<div id="divReportsTable">
<table id="tblReports">
#if (Model.Reports != null)
{
foreach (var r in Model.Reports)
{
// add tr, td of all reports in report list
}
}
</table>
</div>
<div style="text-align:center">
#for (int i = 0; i < Model.PageCount; i++)
{
#Html.ActionLink(i.ToString(), "Search", "Reports", new { CurrentPage = i });
// or
// <input type="submit" value="#i" onClick="meybe something like this?" />
}
Current Page #Html.EditorFor(q => q.CurrentPage), of total #Model.PageCount pages
</div>
}
I know that I can simply type twice all the data I need to flow between controller and view, once in model and once as Search action parameters, but that's extactly what I'm trying not to do. Currently I have a textbox for editing the current page, and it works, because it's created via #Html.EditorFor, but I'd like to have multiple buttons for each page.
I could maybe create a hidden field in that form, and create a javascript function that'll find that hidden field by id, and change it's value, and then call whatever the submit button onClick function is. BUT, is there a better/shorter/more-MVC way to implement that?
I would like to bind html data-* attribute to separate property in my model. How to do that?
As you can see here my button is binding to Operation property and I would like to bind data-* to property Data_RemoveAt.
public enum LinkListOperation
{
AddOne,
RemoveOne,
RemoveAll,
Submit,
RemoveAt
}
public class StepThree_Notification_TemplateEmailViewModel
{
public LinkListOperation Operation { get; set; }
[DisplayName("data-removeat")]
public int Data_RemoveAt { get; set; }
}
#using (var form = Html.BeginForm("AcceptTask", "Task", FormMethod.Post))
{
<div>Linki:</div>
for(int i = 0; i < Model.Links.Count; ++i)
{
<div>
#Html.TextBoxFor(f => f.Links[i], new { Name = string.Format("Model.Links[{0}]", i) })
<button value="RemoveAt" type="submit" name="Model.LinkOperation" data-removeat="#i">Remove</button>
</div>
}
<button value="AddOne" type="submit" name="Model.LinkOperation">MORE</button>
<button value="RemoveOne" type="submit" name="Model.LinkOperation">LESS</button>
<button value="RemoveAll" type="submit" name="Model.LinkOperation">REMOVE ALL</button>
<button value="Submit" type="submit" name="Model.Operation">OK</button>
}
If you are not using ajax, what you can do is to wrap your submit button inside a form tag and set the values you want to sent as form fields. You may use hidden fields for that.
for(int i = 0; i < Model.Links.Count; ++i)
{
<div>
#Html.TextBoxFor(f => f.Links[i],
new { Name = string.Format("Model.Links[{0}]", i) })
#using(Html.BeginForm("Remove","Home"))
{
<input type="hidden" name="Operation" value="#Model.Operation" />
<input type="hidden" name="RemoveIndex" value="#i" />
<button value="RemoveAt" type="submit" name="Model.Operation">Remove</button>
}
</div>
}
Assuming your action method looks like this
public ActionResult Remove(string Operation,int RemoveIndex)
{
// to do : return something with the passed in values.
}
If you already have an outer form tag, This approach won't be ideal as nested forms are not a good idea. You may consider using javascript-ajax code to read the data attribute value and send it to server.
You do not need to add the form tag like what i mentioned above. Keep your markup as it is except, we will add a css class to the submit button which we will use later as the jQuery selector.
<button value="RemoveAt" class='removeBtn' data-removeat="#i"
type="submit" name="Model.Operation">Remove</button>
And add this javscript to listen to the click event on the button and read the data attribute value and then make an ajax post call. Assuming you have jQuery library loaded to your page,
$(function(){
$(".removeBtn").click(function(e){
e.preventDefault();
var _this=$(this);
var removeIndex=_this.data("removeat");
var op=_this.attr("name");
var url = "#Url.Action("Remove","Home")";
url=url+"?Operation="+op+"&RemoveIndex="+removeIndex;
$.post(url,function(res){
//do something when a response comes back, may be update the UI/reload the page ?
//window.location.href=window.location.href;
});
});
});
I am trying to retrieve checkboxes value which are dynamically added. Here is what i have done
View:
foreach (var item in ViewBag.List as List<m_valuation>)
{
<table>
<tr>
<td>Print</td>
<td>#Html.TextBox("Txt")</td>
<td>Codes With</td>
<td>#Html.CheckBox("Check")</td>
<td>#Html.Label("Codes",item.point_valuation)</td>
</tr>
</table>
}
<input type="submit" value="GenerateCodes" />
Controller :
public ActionResult PrintPointCodes(int DealNo)
{
List<m_valuation> model = db.m_valuation.ToList();
ViewBag.List = model;
return View();
}
[HttpPost]
public ActionResult PrintPointCodes(List<string> Txt,List<bool> Check)
{
List<bool> CheckedItems = new List<bool>();
for (int i = 0; i < Check.Count; i++)
{
CheckedItems[i] = Check[i].Equals(false);
}
return View();
}
The returned values comes with true and false . Not sure Where i am going wrong
Please Help me!
Thanks in advance
#Html.CheckBox (and#Html.CheckBoxFor) methods render 2 controls, <input type="checkbox" ..> and <input type="hidden" value="false">. The reason is that unchecked checkboxes to not post back so the second hidden input ensure that false is returned is the associated (real) checkbox is unchecked. Handling checkboxes this way (as an array of boolean values) will not work. If you were to render the html manually then you would only get true values and you would not be able to tell which ones were checked on the client.
To correctly bind your properties, render you controls in a for loop or use a custom EditorTemplate for class m_valuation
GET method
public ActionResult PrintPointCodes(int DealNo)
{
List<m_valuation> model = db.m_valuation.ToList();
return View(model);
}
View
#model List<m_valuation>
...
for (int i = 0; i < Model.Count; i++)
{
#Html.TextBoxFor(m => m[i].Txt)
#Html.CheckBoxFor(m => m[i].Check)
}
POST method
[HttpPost]
public ActionResult PrintPointCodes(List<m_valuation> model)
{
//model will be correctly bound
I'm pretty much a jquery newb...I've almost got this working I think, let me know if i can clarify anything.
I have a screen displaying a list..let's call them affiliates. To create a new affiliate, a modal style pop up dialogue is used.
When the dialogue "Create" button is clicked, the form data must be saved (creating a new affiliate), the dialogue disappears, and the affiliate list updates without reloading the page
The jquery file at the bottom shows how I'm trying to do it now: trying to detect a click on the "confirm" button, get the form to submit using the data target property, and using the form's target property to know what container to update.
What is happening is: nothing. The cancel button works, create button does absolutely nothing.
Also note that the "Create" button, which will act as the form's submit, is not located within the <form> tags.
I'm pretty sure I'm doing modals wrong but let's ignore that for the moment and focus on the async post and list update. I've included my relevant code below to support my post.
--AffiliateListPartial
#model IPagedList<Acme.Business.DomainModel.Affiliates.Affiliate>
<div class="items-list" id="affiliate-list-view">
#foreach (var item in Model)
{
<a href="#Url.Action("AffiliateDetails", "Vendor", new { id = item.AffiliateId })">
//basic spans and razor display list in here..nothing notable
</a>
}
</div>
The above partial view is contained within a full view, lets call it AffiliateList. Nothing particularly relevant in there except that it is controlled by the VendorController/Affiliatelist method.
The VendorController.AffiliateList looks like:
public ActionResult AffiliateList(string searchTerm = null, int page = 1)
{
var userId = WebSecurity.GetUserId(User.Identity.Name);
var model = (from a in db.Affiliates.ToList()
where a.VendorId == userId
select a).ToPagedList(page, 15);
if(Request.IsAjaxRequest()) return PartialView("_AffiliateListPartial", model);
return View(model);
}
The modal style dialoque for creating a new affiliate (I'll just include the lines that I think are relevant):
_Modal.AffiliateCreate.Partial
<form id="affiliate-create-form" class="form" method="post" action="#Url.Action("AffiliateCreate")" data-acme-ajax="true" data-acme-target="#affiliate-list-view">
// multiple input elements
</form>
<div class="modal-footer">
<button name="close_modal"><span>Cancel</span></button>
<button name="confirm" data-acme-target="#affiliate-create-form"><span>Create</span></button>
</div>
And the VendorController.AffiliateCreate method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AffiliateCreate(Affiliate affiliate)
{
if (!ModelState.IsValid) return View(affiliate);
db.Affiliates.Add(affiliate);
db.SaveChanges();
return RedirectToAction("AffiliateList");
}
And the .js file's relevant parts:
$("button[name='confirm']").on("click", function() {
var $form = $(this).attr("data-acme-target");
var options = {
url: $form.attr("action"),
type: $form.attr("type"),
data: $form.serialize()
};
$.ajax(options).done(function (data) {
var $target = $($form.attr("data-acme-target"));
var $newHtml = $(data);
$target.replaceWith(data);
$newHtml.effect("highlight");
});
$(".modal_overlay").css("opacity", "0");
$(".modal_container").css("display", "none");
return false;
});
$("button[name='close_modal']").on("click", function(event) {
$(".modal_overlay").css("opacity", "0");
$(".modal_container").css("display", "none");
return false;
});
var $form = $(this).attr("data-acme-target"); is getting the attribute named 'data-acme-target' of the button, rather than the form it's associated with. So then when you're using $form.attr('action'), you aren't getting anything back.
Since data-acme-target is an ID to another control that is the form you want to submit, use $($(this).attr("data-acme-target")); to get it.