Should I be using Ajax forms in MVC partial views for adding new data in real time? - ajax

I want to know if there is a better way to accomplish what I am doing or if my current approach is ok. My current approach is giving me a little trouble so I need to see what I can do about it all.
In my MVC web application, I have dropdown lists which are searchable (handled by KendoUI Widgets) and I allow the user to add missing data to the database table which serves those dropdown lists.
Example of Widget
#(Html.Kendo().DropDownList()
.Name("Manufacturer")
.Filter("contains")
.OptionLabel("-- Select --")
.DataTextField("ManufacturerName")
.DataValueField("ManufacturerName")
.NoDataTemplate("No Data Add Manufacturer")
.DataSource(source => {
source.Read(read => {
read.Action("GetManufacturers", "Cars");
}).ServerFiltering(false);
}))
The data is added via an ajax form in a partial view which is displayed using a bootstrap 4.0 modal trigger by a button in the no data template (see above example).
These dropdown lists appear in both Create and Edit views, in the Create view they work fine and as expected but within the Edit views, they have dictionary errors.
Create View
#model MyProject.Models.Cars
#using (Ajax.BeginForm("Create", "Cars", new AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "NotifySuccess",
OnFailure = "NotifyFailure"
},
new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
...Form goes here with widgets etc
}
#Html.Partial("_Manufacturer")
Popup form partial view
#model MyProject.Models.Manufacturer
<div class="modal fade" id="modal_addManufacturer">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
#using (Ajax.BeginForm("AddManufacturer", "Cars", new AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "Success",
OnFailure = "Fail"
}, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(l => l.ManufacturerName, new { #Class = "form-label" })
#Html.TextBoxFor(i => i.ManufacturerName, new { #Class = "form-control" })
<input type="submit" value="Add" />
<input type="reset" value="Reset" />
</div>
}
</div>
</div>
</div>
</div>
These errors seem to stem from the fact that the Edit method in the controller passes an Id parameter. These leads me to the most obvious question, is this approach correct? Should I be using ajax forms in modals to create data on the fly like this especially when the models they use are causing problems when the Id parameter is used on views such as edit.
Can anyone offer some guidance on what I can do about this?

Related

Ajax form not submitting to controller action

Ajax form is not submitting to controller action. Here is the code
#using (Ajax.BeginForm("searchCustomers", "Transaction", new { phoneNumber = Model.CustomerMobile }, new AjaxOptions
{
UpdateTargetId = "custList",
InsertionMode = InsertionMode.Replace
}))
{
<div class="col-md-6">
<div class="form-group">
<label>Customer Mobile No:</label>
#Html.TextBoxFor(x => x.CustomerMobile, new { #class = "form-control", id = "custMobile" })
</div>
#*<div class="form-group">
<label>Customer Name</label>
#Html.TextBoxFor(x => x.CustomerName, new { #class = "form-control", id = "custName" })
</div>*#
<input type="submit" class="btn btn-default" value="Get Customer Details" >
</div>
}
Here is the controller action
public ActionResult searchCustomers(string phoneNumber)
{
if (phoneNumber==null)
{
return PartialView(new List<Models.Customer>());
}
var c = Database.Session.Query<Models.Customer>()
.Where(x => x.MobileNumber.Like(phoneNumber) )
.ToList();
return PartialView(c);
}
but the ajax form is not submitting. I've added the JavaScript files as bundles. I've another #Html.Action("searchCustomers", new { phoneNumber = Model.CustomerMobile }) this one calls the controller action.
Everything is fine in your code. There are two javascript files that are needed for Ajax.Beginform to work.
jquery-{Vaersion}.js
jquery.unobtrusive-ajax.js
Check whether you have included those files to your view or not. Or if your view has any LayOut if those javascript files are included in your LayOut or not.

Saving multiple partial views from one main page

Here is my requirement :
I am designing a page to add a vehicle to the database :
Normal vehicle information [Model - Inventory]
Some other features [Model - IList]
Here is my index.cshtml page
#model Model.ViewModel.VehicleViewModel
<div>
<div class='col-md-12'>
<div class="form-group">
<input id="mainFormSubmit" type="button" value="Save" class="btn btn-default" />
</div>
</div>
#{Html.RenderPartial("~/Views/Shared/_InventoryPartial.cshtml", Model.InventoryVM);}
#{Html.RenderPartial("~/Views/Shared/_StandardFeaturePartial.cshtml", Model.StandardFeatures);}
</div>
<script type="text/javascript">
$('#mainFormSubmit').click(function () {
$('#InventoryForm').submit();
$("#StandardFeatureForm").submit();
});
</script>
This is my view model class
public class VehicleViewModel
{
public InventoryViewModel InventoryVM { get; set; }
public IList<StandardFeature> StandardFeatures { get; set; }
}
The Inventory partial view [_InventoryPartial.cshtml]
#model Model.ViewModel.InventoryViewModel
#{
var options = new AjaxOptions() { HttpMethod = "Post" };
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Ajax.BeginForm("InventorySave", "AddVehicle", options, new { id = "InventoryForm" }))
{
<fieldset>
<legend>Inventory Info</legend>
<div class='col-md-6'>
<!-- VIN input-->
<div class="form-group">
#Html.LabelFor(x => x.VIN, new { #class = "col-md-4 control-label" })
<div class="col-md-7">
#Html.TextBoxFor(x => x.VIN, new { #class = "form-control", #placeholder = "VIN" })
</div>
</div>
</div>
</fieldset>
}
The standard feature partial view [_StandardFeaturePartial.cshtml]
==
#model IEnumerable<Model.DomainModel.StandardFeature>
#{
var options = new AjaxOptions() { HttpMethod = "Post" };
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Ajax.BeginForm("StandardFeatureSave", "AddVehicle", options, new { id = "StandardFeatureForm" }))
{
When I am clicking on index page SAVE button, only
$('#InventoryForm').submit();
$("#StandardFeatureForm").submit();
last one(StandardFeatureForm) is executing.
Please let me know if this process is correct, and what could be the reason of this issue.
You should not call the submit method twice. Depending of the browser you can face different issues :
the form submission causes the browser to navigate to the form action and the submission
of the first may prevent the submission of the second
The browser could detected there are two requests and discards the
first submit.
In your case it will be easier to wrap your two partial views inside a unique form.
#using (Ajax.BeginForm("InventorySave", "AddVehicle", FormMethod.Post, new { id = "InventoryForm" }))
{
#{Html.RenderPartial("~/Views/Shared/_InventoryPartial.cshtml", Model.InventoryVM);}
#{Html.RenderPartial("~/Views/Shared/_StandardFeaturePartial.cshtml", Model.StandardFeatures);}
}
However when the partial views render they are not generating the correct name attributes for the larger modelModel.ViewModel.VehicleViewModel you want to use :
public void InventorySave(VehicleViewModel vehicleViewModel) {}
In this case you should use EditorTempmlate instead of partial views. It's simple to do from your partial views and this post should help you :Post a form with multiple partial views
Basically, drag your partials to the folder ~/Shared/EditorTemplates/
and rename them to match the model name they are the editor templates
for.
Finally something like :
#model Model.ViewModel.VehicleViewModel
#using (Html.BeginForm("InventorySave", "AddVehicle", FormMethod.Post, new { id = "InventoryForm" }))
{
#Html.EditorFor(m => m.InventoryVM);
#Html.EditorFor(m => m.StandardFeatures});
}
The Ajax.BeginForm helper already has a submit event associated to it which creates an Ajax POST request. When you are manually submitting your form using $('#InventoryForm').submit();, you're calling both and the submit events which can have strange side effects.
There are a few ways around this. Here is one solution
Change your forms to a regular HTML form using the Html.BeingForm helper.
Amend your script to create ajax requests and use the form data
$('#InventoryForm').submit(function(e) {
e.preventDefault();
$.post($(this).attr("action"), $(this).serialize(), function(r) {
//Do something
});
});
$('#StandardFeatureForm').submit(function(e) {
e.preventDefault();
$.post($(this).attr("action"), $(this).serialize(), function(r) {
//Do something
});
});
Hope this helps

Why does Unobtrusive Ajax require a layout page (returning a partial view doesn't work)?

While creating a custom plugin, that Ajaxifies any forms that are not already Ajaxified by unobtrusive Ajax, I noticed that my partial/layout code failed to display on post-back with a normal Unobtrusive Ajax form:
For my other partial-update plugins, I conditionally render pages as partial (for ajax requests) or full with layout (for normal requests) like this:
public ActionResult AjaxForm()
{
if (!Request.IsAjaxRequest())
{
ViewBag.Layout = "_Layout.cshtml";
}
return PartialView();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AjaxForm(AjaxFormModel model)
{
if (!Request.IsAjaxRequest())
{
ViewBag.Layout = "_Layout.cshtml";
}
if (ModelState.IsValid)
{
return PartialView("Success", model);
}
return PartialView(model);
}
When it came to testing a normal unobtrusive Ajax form, I used this test view, which updates just its own element UpdateTargetId="FormPanel":
#using (Ajax.BeginForm("AjaxForm", null, new AjaxOptions() { UpdateTargetId = "FormPanel" }, new { id = "FormPanel" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AddressLine, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AddressLine, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
If I remove the test if (!Request.IsAjaxRequest()), and return the full page with master page/layout, it renders the result just fine where the form was.
Fiddler shows the "success" view is returned in either case, but it does not do anything without the layout present.
Q. What is it in a masterpage/layout that unobtrusive Ajax requires in order to extract content and replace the specified element
Update:
To make things worse it actually works correctly the first time, but not if I ajax load the same form again.
A: Forms must have unique IDs!
There are loads of issues about unique IDs on SO, which browsers generally cope with, but the one you cannot ever break is having two forms loaded with the same id. Only one is visible to JavaScript/jQuery!
My problem was caused by a duplicate form ID. You should not insert the result page back in to the form itself (leaving the form element intact).
When I reloaded the original form, dynamically in a slideshow-style control, I wound up with duplicate form ID's.
This meant Unobtrusive Ajax could only see the first form in the DOM!
Solution (do not set Ajax forms to update themselves):
Set UpdateTargetId to an element above the form, never the form itself if you load panels dynamically.
Fix to my example view (surround form with a target panel):
<div id="FormPanel">
#using (Ajax.BeginForm("AjaxForm", new AjaxOptions() { UpdateTargetId = "FormPanel" }))

MVC - view variable?

Using an MVC pattern say I have a view and a controller:
Controller
User
View
User
login
logout
dashboard
The user controller has the following actions:
loginAction
logoutAction
dashboardAction
The login view has a simple login form, now my question:
Where should the form get its action url from?
Should this be hardcoded in the view such as:
<form action="/post.php" method="post">
It doesnt seem correct that the controller should tell the view, so where else could this go?
The controller tells the view everything it needs, and the view displays it. The view has no knowledge of the controller.
#using (Html.BeginForm("Login", "Account", FormMethod.Post, new { #class = "form-signin", ReturnUrl = ViewBag.ReturnUrl }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<h2 class="form-signin-heading">Please sign in</h2>
<br />
#Html.TextBoxFor(m => m.UserName, new { #class = "input", type = "text", placeholder = "Username" })
<br />
#Html.PasswordFor(m => m.Password, new { #class = "input", type = "password", placeholder = "Password" });
<br /><br />
<button type="submit">Sign in</button>
}
In that login form, the link to the method on the controller is ("Login", "Account") where 'Login' is the method name and 'Account' is the controller name.

How i can submit multiple objects inside the form post values to be added

i have a create view to add answers to a question, currently the user can only add one answer at the same time when he clicks on the submit button, instead of this i want the user to be able to insert multiple answers objects into the same view and then the system to add all these new answer objects to the database after the user click on the submit button, my current view looks as the follow:-
#model Elearning.Models.Answer
#{
ViewBag.Title = "Create";
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<div id = "remove">
#using (Ajax.BeginForm("Create", "Answer", new AjaxOptions
{
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "remove"
}))
{
<div id = "returnedquestion">
#Html.ValidationSummary(true)
<fieldset>
<legend>Answer</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</fieldset>
<input type= "hidden" name = "questionid" value = #ViewBag.questionid>
<input type= "hidden" name = "assessmentid" value = #ViewBag.assessmentid>
<input type="submit" value="Add answer" />
</div>
}
</div>
and the action methods look as the follow:-
public ActionResult Create(int questionid)//, int assessmentid)
{
ViewBag.questionid = questionid;
Answer answer = new Answer();
return PartialView("_answer",answer);
}
//
// POST: /Answer/Create
[HttpPost]
public ActionResult Create(int questionid, Answer a)
{
if (ModelState.IsValid)
{
repository.AddAnswer(a);
repository.Save();
return PartialView("_details",a);
}
return View(a);}
so how i can modify the above code to be able to insert multiple answer objects at the same view and then submit these answers objects at the same time when the user click on the submit button?
Try Post a List
Add input by javascript when user click "Add Answer".
And when submit the form ,it will post all answer data to binding to List
<script>
$(document).ready(function () {
var anwserCount = 1;
$("#addbutton").click(function () {
$("#AnwsersDiv")
.append("<input type='text' name='Anwsers[" + anwserCount + "]'/>");
anwserCount += 1;
});
});
</script>
#using (Html.BeginForm())
{
<div id="AnwsersDiv">
<input type="text" name="Anwsers[0]" />
</div>
<input id="addbutton" type="button" value="Add answer" />
<input type="submit" value="submit" />
}
Model
public class Answer
{
public List<String> Anwsers { get; set; }
}
When submit the form
I think this is what you are looking for
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Conclusion: you should make the post action with ICollection<Answer> Parameter, then it will be easy to get them when you post your main form, and create the appropriate QUESTION object, then save them all with only one submit.

Resources