I have an ASP MVC 3 page that will allow users to create a drop down list and dynamically add items to it. For example, this is how the page looks when it first loads.
The information on the left is used to specify what page the drop down list will be located (Navbar Item) and what name we are going to give the drop down list (obviously Drop Down List Name).
The information on the right (Allowed Value and Display Value) will be the specific drop down list items. When the user clicks on the Add Another link, an Ajax call goes to the controller, returns a partial view, which is then appended to the Drop Down Items <fieldset> like so:
The problem is, once the user hit's the Submit button, none of the items make it to the controller.
I'm still relatively new to ASP MVC, so I'm wondering if I'm even going about this the right way. Is this the proper way to dynamically add items to a list?
Here is how this view is originally created in the controller
public ActionResult NewList()
{
List<DropDownValues> drop = new List<DropDownValues>();
drop.Add(new DropDownValues());
return View(drop);
}
A list of type DropDownValues is created and sent to the view, which has this tag at the top
#model IEnumerable<Monet.Models.DropDownValues>
The Ajax call below
<script>
$(document).ready(function () {
$("#addItem").click(function () {
if ($('#Field').text() != "" && $('#DisplayPage').text() != "") {
$.ajax({
url: '#Url.Action("BlankDropDownItem", "DropDownValues")',
data: { field: $('#Field').val(), displayPage: $('#DisplayPage').val() },
dataType: 'html',
cache: false,
success: function (html) {
$("#items").append(html);
}
});
} else {
alert("Please enter a Drop Down List Name and Navbar Item first!");
}
return false;
});
});
</script>
calls this controller method
public ActionResult BlankDropDownItem(string field, string displayPage)
{
DropDownValues partial = new DropDownValues();
partial.Field = field;
partial.DisplayPage = displayPage;
return PartialView("DropDownItemPartial", partial);
}
which then appends this Partial View to the main page
#model Monet.Models.DropDownValues
#Html.HiddenFor(model => model.Field)
#Html.HiddenFor(model => model.DisplayPage)
<div class="editor-label">
#Html.LabelFor(model => model.AllowedValue)
</div>
<div class="label-field">
#Html.EditorFor(model => model.AllowedValue)
#Html.ValidationMessageFor(model => model.AllowedValue)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DisplayValue)
</div>
<div class="label-field">
#Html.EditorFor(model => model.DisplayValue)
#Html.ValidationMessageFor(model => model.DisplayValue)
</div>
I'm trying to use the hidden fields in order to make sure all the new items are stored in the database with the correct Field and Navbar Item values (again, not sure if this is the proper way to go about this).
Any advice/suggestions would be greatly appreciated. Thx!
You could use Steven Sanderson's BeginCollectionItem helper to help you out: http://nuget.org/packages/BeginCollectionItem/
His blog post explains the principle using the WebForms view engine but it's just as applicable to Razor: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
If you want to read about the nuts and bolts involved in submitting collections of input fields with the same name, then have a read of Haack's blog post here: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Hope this gives you some pointers!
Related
In my web app I have a grid list. I select a row and then click the edit button to show a partial update view (which I use to add new data too) in a popup window. The view shows, but I don't have any values in the textboxes. I use devextreme components, but I think, my issue has nothing to do with it (maybe I'm wrong).
This is the onClick code:
function editrow_onClick() {
var key = $("#grid").dxDataGrid("instance").getKeyByRowIndex(selectedRowIndex);
$.ajax({
url: '/MasterData/Sender/UpdateSender/'+key,
}).done(function (response) {
var popup = $("#sender-popup").dxPopup("instance");
popup.option("contentTemplate", function (content) {
content.append(response);
});
popup.show();
});
}
If I click the edit button, I get the right url like /MasterData/Sender/UpdateSender/3.
The corresponding controller action looks like this:
[Route("{id}")]
public IActionResult UpdateSender(long SenderId)
{
return PartialView("NewSender", SenderRepository.GetSender(SenderId));
}
On top of the controller class I have the corresponging attribute: [Route("MasterData/[controller]/[action]")]
I testet id, the action is reached, but the SenderId is 0. I would expect f.e. 3. This should be causing the empty view, I think. Why is SenderId 0 (the default value)?
I post the update view too, maybe this is the source of the problem (don't bother the AddSender action, I plan to change it conditionally, if I get the update data working):
#model Sender
<form asp-action="AddSender" asp-controller="Sender" method="post">
#using(Html.DevExtreme().ValidationGroup()) {
#(Html.DevExtreme().Form<Sender>()
.ID("form")
.ColCount(1)
.Items(items => {
items.AddSimpleFor(m => Model.Name);
items.AddSimpleFor(m => Model.Address);
items.AddSimpleFor(m => Model.ContactPerson);
items.AddSimpleFor(m => Model.ContactEmail);
items.AddGroup().Items(groupItem => groupItem.AddSimple().Template(
#<text>
<div style="text-align: right">
#(Html.DevExtreme().Button().ID("save").Text("Mentés").Width(100).Type(ButtonType.Success).UseSubmitBehavior(true))
#(Html.DevExtreme().Button().ID("cancel").Text("Mégsem").Width(100).Type(ButtonType.Normal).OnClick("close_onClick"))
</div>
</text>));
})
.LabelLocation(FormLabelLocation.Top)
.FormData(Model)
)
}
</form>
<script>
function close_onClick() {
$("#sender-popup").dxPopup("hide");
}
</script>
[Route("{SenderId}")] public IActionResult UpdateSender(long SenderId) { return PartialView("NewSender", SenderRepository.GetSender(SenderId)); }
Try replacing id with SenderId.
Then action method will hit with the desired value.
I have a form and a partial view on my razor page, the idea being that if I change the dropdownlist, the Controller does some work and sets a ViewBag.ShowAlert (bool) that triggers the partial view to be displayed.
While this works, instead of just showing the code within the partial view, the partial view shows as a new view rather than on the same view.
Any idea why?
The view looks like this
#using (Html.BeginForm("AlterVote", "ChangeVoteType"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h1>New voting preference</h1>
<hr />
<p>Please select the type of vote you wish to change to #Html.DropDownListFor(model=>model.SelectedType, ViewBag.myList as SelectList, "Voting type", new { onchange = "this.form.submit();"})</p>
<div id="partialDiv">
#if (ViewBag.ShowAlert)
{
#Html.Partial("VotingChange")
}
</div>
</div>
}
The controller handling the HttpPost is this
[HttpPost]
public PartialViewResult AlterVote(Dropdown dropType)
{
ChangeBoilerPlate(dropType.SelectedType);
dropType.CurrentType = VoteTypeNames[(int)HomeController.VoterModel.CurrentVoteType];
return PartialView("VotingChange", dropType);
}
I'm guessing that this is down to the initial view being a form, so the partial gets confused as to where to insert the view.
If I understand correctly, by the partial view shows as a new view you mean it comes with a html tag, body and the full layout again. To solve this, you need to set up the layout to null inside your partial view, like so:
#model YourNamespace.Dropdown
#{
Layout = null;
}
<!-- partial view html below -->
<div>
</div>
The div tag is just to illustrate.
While this might solve your problem, you might want to load the partial view without reloading the whole page again. This is possible using ajax, like so:
Main View
#using (Html.BeginForm("AlterVote", "ChangeVoteType"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h1>New voting preference</h1>
<hr />
<p>Please select the type of vote you wish to change to #Html.DropDownListFor(model=>model.SelectedType, ViewBag.myList as SelectList, "Voting type", new { id = "vote"})</p>
<div id="partialDiv">
</div>
</div>
}
<script type="text/javascript">
$(document).ready(function () {
$('#vote').change(function() {
var selectedType = $(this).val();
$.post('yourserver/YourController/AlterVote', { "SelectedType": selectedType })
.done(function (data) {
$('#partialDiv').html(data);
})
.fail(function () {
console.log('Whoops, something went wrong!!!');
});
});
});
</script>
So I just added a javascript to listen to that same change event on your dropdrown, but instead of submitting the form, I just use ajax to load the partial view html without reloading the entire page.
Just fix the URL and remember to set up layout to null in your partial view. Also, you might want this javascript in a separate file, thus loading it with bundles.
I have a form in a view (Edit view), and a partial view inside that form on the Edit view. The partial view has its own form which performs a lookup. The lookup in the partial view is successfully returning the results to the Edit view. However, the POST from the partial view is then hitting the controller a second time (trying to submit the form in the Edit view). How do I stop the POST from hitting the controller a second time?
Here is where the partial view is called in the Edit view:
<div class="form-group" id="search-pac">
#Html.Action("PacSearch", "ItemRequest");
</div>
<div class="form-group" id="search-pac-results">
</div>
Here is where the controller gets the partial view:
[HttpGet]
public ActionResult PacSearch()
{
return PartialView("_PacSearchFormPartial");
}
Here is the form in the partial view:
#using (Ajax.BeginForm("PacSearch", "ItemRequest", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "search-pac-results"
}))
{
<div>
#Html.TextBox("pacupc")
<input type="submit" value="Find PAC" />
</div>
}
Which then hits the controller here:
[HttpPost]
public ActionResult PacSearch(string pacupc)
{
//do lookup stuff, and call a partial view to display the results
}
Once the results are displayed on the Edit view, POST then hits the controller here (which I don't want unless the submit button in the Edit view is clicked):
[HttpPost]
public ActionResult Edit(ItemRequest itemRequest, HttpPostedFileBase upcImage, Comment comment, String FinalApproval)
{
//handle form submission from Edit View
}
How do I keep the POST from the partial view from hitting the HttpPost for Edit view in the controller?
UPDATE:
Upon the suggestion to use a direct AJAX call, I ditched the partial views and changed my Edit view to:
<div class="form-group" id="search-pac">
#Html.TextBox("pacupc")
<input type="button" id="btn-pacupc" value="Find PAC" />
#* #Html.Action("PacSearch", "ItemRequest");*#
</div>
<div class="form-group" id="search-pac-results">
</div>
And AJAX call:
<script type="text/javascript">
$(document).ready(function () {
$(document).on('click', '#btn-pacupc', function () {
var pacupc = $("#pacupc").val();
$.ajax({
type: "POST",
url: "#Url.Action("PacSearch")",
data: { pacupc: pacupc },
success: function (result) { $('#search-pac-results').html(result); }
});
});
});
</script>
There's no support for forms within forms in HTML. A submission inside the innermost form will also submit any parent form. The solution then, is to not rely on Ajax.BeginForm, which will print a form element to the page, and instead, wire your AJAX manually. This is a prime example of why I encourage everyone to not use the Ajax family of helpers. They simply do too much, hidden to the developer, and often lead to confusion when things don't work as expected, which happens far more often than not.
I have 3 DropDownLists; when I select an item in #1, I want the items in #2 to be filled according the value of #1. And when I select an item in #2, the items in #3 should be filled according the selection in #2. They all are located in a form named GetItemsForLeve1. I've started by using onchange of the drop-down.
<% using (Html.BeginForm("GetItemsForLeve1", "Index"))
{ %>
Main Group: <%:Html.DropDownList("Level1", (SelectList)ViewBag.Level1, "Select One", new { onchange = "$this.form.submit()" })%>
<%:Html.DropDownList("Level2", (SelectList)ViewBag.Level2, "-", new { onchange = "$this.form.submit()" })%>
<%:Html.DropDownList("Level3", (SelectList)ViewBag.Level3, "-", new { onchange = "$this.form.submit()" })%>
<input type="submit" value="Filter" />
<%}%>
Is it possible to fill the level 2 and level 3 drop-down lists without sending the page back to the server?
How can I tell which drop-down list has been clicked in the GetItemsForLevel action?
I am completely new to MVC, so I appreciate telling me in a simple way?
Thank you
As far as I know, there's not really a component that does this for you. But you can use the Ajax.BeginForm helper to build it.
the first ajax form should contain the first select list, and post back to an action that returns a partial view
the partial view in 1. should return the second ajax form with a second select list
and so forth
So the main Razor view should contain something like this:
#using (Ajax.BeginForm("SecondAjaxForm", new AjaxOptions() { UpdateTargetId = "secondFormDiv" })
{
#Html.DropDownList("selectList1", Model.FirstSelectList, new { onchange = "this.form.submit()")
}
<!-- the second AJAX form + drop-down list will get populated here
<div id="secondFormDiv"></div>
And the SecondAjaxForm action:
public ActionResult SecondAjaxForm(string selectList1)
{
SelectList secondSelectList;
// populate the second select list here
return PartialView("SecondAjaxForm", secondSelectList);
}
The partial view SecondAjaxForm should be basically the same as the first form above.
#model SelectList
#using (Ajax.BeginForm("ThirdAjaxForm", new AjaxOptions() { UpdateTargetId = "thirdFormDiv" })
{
#Html.DropDownList("selectList2", Model, new { onchange = "this.form.submit()")
}
<!-- the third AJAX form + drop-down list (if any) will get populated here
<div id="thirdFormDiv"></div>
I have a list of employment records, you can also add an employment record from the same page using a partial view.
Heres employment.cshtml that has a partial view for the records list and a partial view to add a new record which appears in a modal pop up.
<h2>Employment Records</h2>
#{Html.RenderPartial("_employmentlist", Model);}
<p>
Add New Record
</p>
<div style="display:none">
<div id="regModal">
#{Html.RenderPartial("_AddEmployment", new ViewModelEmploymentRecord());}
</div>
</div>
Heres the partial view _AddEmployment.cshtml
#using (Html.BeginForm("AddEmployment, Application"))
{
#Html.ValidationSummary(true)
<div class="formEl_a">
<fieldset>
<legend></legend>
<div class="sepH_b">
<div class="editor-label">
#Html.LabelFor(model => model.employerName)
</div>
etc....etc....
</fieldset>
</div>
<p>
<input type="submit" class="btn btn_d" value="Add New Record" />
</p>
}
and heres my Application controller:
[HttpPost]
public ActionResult AddEmployment(ViewModelEmploymentRecord model)
{
try
{
if (ModelState.IsValid)
{
Add Data.....
}
}
catch
{
}
return View(model);
}
When compiling the following html is generated for the form:
<form action="/Application/Employment?Length=26" method="post">
It brings in a length string? and is invoking the Employment controller instead?
Hope all is clear....
QUESTION ONE: when I click the submit button from within the partial view it does not go to the controller specified to add the data. Can anyone see where im going wrong?
QUESTION TWO: When I get this working I would like to update the employment list with the new record....am I going about this the correct way? Any tips appreciated.
Answer 1: First try this and let me know if that hits your controller.
#using (Html.BeginForm("AddEmployment", "Application", FormMethod.Post))
Answer 2: To update the employment list, I would assume you would want to save the model to your database then have your employment list displayed on the same page or a different page calling the data from the DB into the the list or table to be displayed.
Edit:
It looks as though your form attributes are not being applied.
For your employment.cshtml, I personally don't use { } around my #Html statements.
You must not be doing what I stated above because your error occurs only when I write it as
#using (Html.BeginForm("AddEmployment, Application", FormMethod.Post))
missing those closing quotes is what is causing your problem.
jQuery code:
window.jQuery(document).ready(function () {
$('#btnsave').click(function () {
var frm = $("form");
var data = new FormData($("form")[0]);
debugger;
$.ajax({
url: '/Home/Update',
type: "POST",
processData: false,
data: data,
dataType: 'json',
contentType: false,
success: function (response) {
alert(response);
},
error: function (er) { }
});
return false;
});
});
Controller Code
[HttpPost]
public JsonResult Update(Generation obj)
{
if (ModelState.IsValid)
{
return Json("done");
}
else
{
return Json("error create");
}
}
Using those code you can post form using jquery and get response in jsonresult
I know this is very old Question
the reason it didn't work for you because your syntax
Here is your code
#using (Html.BeginForm("AddEmployment, Application"))
the fix
#using (Html.BeginForm("AddEmployment", "Application"))
Regards
you have put #using (Html.BeginForm("AddEmployment, Application")) what this is trying to do is invoke a action called "AddEmployment, Application" i think you meant #using (Html.BeginForm("AddEmployment", "Application"))