Imagine a webshop with several steps (views) during checkout. When the customer presses the 'next' button, the corresponding 'Action' method is called. In that method some validation is done, checking if the customer is free to proceed, or if there's something wrong with the order. Depending of the validation result, I'd like one of two things to happen:
A) The next view is generated and displayed.
B) The customer stays in the current page, but a modal dialog pops up, containing an error message.
I've tried several approaches found around the web, but nothing has worked so far. The last thing I tried was using AJAX, but I've actually got no clue if this is the right approach.
public ActionResult SomeAction()
{
if (ModelState.IsValid)
{
return View();
}
// If not valid
return errorMessage
}
$(document).ready(function () {
$("#btn").click(function () {
$.ajax({
url: "/Home/SomeAction",
async: true,
success: function (result) {
if (!result.success) {
alert(result.error);
}
// Else display view
}
});
Do you want to display a partial view in a modal pop up? If so, you could use the JQuery AJAX method as you've already done but combine it with a JQuery pop up/lightbox plugin to display the resulting HTML.
So, in your code where "/Home/SomeAction" is the PartialView for your pop up return a "PartialView" from your Controller and when you receive the HTML back in the JQuery "success:" event, maybe use (for example) "Jquery Colorbox" to display it (see here: http://www.jacklmoore.com/colorbox/)
It's hard to say how do you want to achieve such workflow but one thing that comes to my mind is returning values with ie. error in TempData store and doing proper redirection to next step or if validation fails to the same step.
For simple model like this:
public class Cart
{
[Required]
public string Item { get; set; }
[Required]
public string Quantity { get; set; }
}
Some code example for controller could look like this:
public class CartController : Controller
{
//
// GET: /Cart/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Cart model)
{
if (ModelState.IsValid)
return RedirectToAction("Step1");
TempData["Error"] = "Validation failed";
return View();
}
public ActionResult Step1()
{
// add more logic, steps etc
return View();
}
}
On view side:
#using (Html.BeginForm())
{
<fieldset>
<p>
<label for="Id">Item:</label>
#Html.TextBox("Item")
#Html.ValidationMessage("Item", "*")
#Html.Editor("Quantity")
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#if (TempData["Error"] != null)
{
<p>Please fix all errors!</p>
}
I would probably embeed this into view master page ie. only for those steps.
Edit: Changed some code added more real-world example :-)
Btw. keep in mind using such mechanism you'll get validation from MVC framework for free so you'll get your fields highlighted if needed. You can as well pass this model later on to other steps if needed. Overall here's nice starting guide for validation: http://www.asp.net/mvc/overview/older-versions/getting-started-with-aspnet-mvc4/adding-validation-to-the-model
If you would like to go AJAX way then you could use something like this:
$.post("#Url.Action("Index")", values, function(data) {
// add logic for loading next page or error
});
where values object you'll have to construct yourself with proper structure.
Good luck!
No need for ajax to check a form, why not just a few lines of javascript? Like "On click, if the 'e-mail' input field is empty, throw an alert" etc. There are many form validation plug-ins out there, like jQuery inline form validation, just google it.
See : http://www.w3schools.com/js/js_validation.asp
Related
Is it possible to submit simultaneously 2 forms to different actions? In the actions I want to set TempData and than redirect to third action.
I tried method described here Submit two forms with one button Let's suggest that each form have one input field. First form with "name" input, second with "lastname" input
submitForms = function(){
document.forms["formForFirstAction"].submit();
document.forms["formForSecondAction"].submit();
window.location("/Home/ThirdAction")
}
My Actions looks like...
[HttpPost]
public void FirstAction(string name) {
TempData["name"] = name;
}
[HttpPost]
public void SecondAction(string lastname) {
TempData["lastname"]=lastname;
}
public ActionResult ThirdAction () {
string fullname;
fullname = string.Format((string)TempData["name"] +
(string)TempData["lastname"]);
return View(fullname);
}
But it doesn't work. In most cases I got only name or last name in fullname string. Looks like it's something with async or other things I don't know much now. Can you please help me?
TempData is available only the next immediate request. That is the reason you are getting only one item (the last one set from one of those 2 action methods) from the temp data dictionary. You should consider using another persistence mechanism like Session /database
Also, Instead of submitting the form, you oshould consider ajax
submitForms = function(){
$.post("#Url.Action("FirstAction")",$("#formForFirstAction").serialize());
$.post("#Url.Action("SecondAction")",$("#formForSecondAction").serialize());
window.location("/Home/ThirdAction")
}
and here is an example using Session to persist data
[HttpPost]
public void SecondAction(string lastname) {
Session["lastname"]=lastname;
}
public ActionResult ThirdAction () {
string fullname;
fullname = string.Format((string)Session["name"] +
(string)Session["lastname"]);
return View(fullname);
}
You may also consider using jquery when() or q.all to make sure that you are redirecting only after the ajax call's are done ( to avoid the callback hell)
Solution 1 : Use AJAX Form submissions.
Solution 2: Create All in one form in hidden and submit.You can implement this static. In every submit button click you have to read data from
form1 and form2 and update those to form hidden and submit it.
Solution 3: Dynamic Hidden form creation and submission. Integrate everything in to dynamically created form and submit on the fly.
Dynamic for creation examples
How to create a form dynamically using JavaScript?
Plain JS approach
my_form=document.createElement('FORM');
my_form.name='myForm';
my_form.method='POST';
my_form.action='http://www.another_page.com/index.htm';
my_tb=document.createElement('INPUT');
my_tb.type='TEXT';
my_tb.name='myInput';
my_tb.value='Values of my Input';
my_form.appendChild(my_tb);
my_tb=document.createElement('INPUT');
my_tb.type='HIDDEN';
my_tb.name='hidden1';
my_tb.value='Values of my hidden1';
my_form.appendChild(my_tb);
document.body.appendChild(my_form);
my_form.submit();
JQuery Approach
Dynamically create and submit form
$(document).ready(function(){
$('<form action="form2.html"></form>').appendTo('body').submit();
});
I am using the following viewModel on my view:
public class CourseViewModel
{
public IEnumerable<Course_page> Course_page { get; set; }
public IEnumerable<course_section> Course_section { get; set; }
public IEnumerable<course_subsection> Course_subsection { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
In my view I have the following ajax form:
#using (Ajax.BeginForm("updateprogress", "User", new { EnrollmentID = Model.Enrollments.Single().EnrollmentID }, new AjaxOptions
{
HttpMethod = "POST",
}, new { id = "ajaxForm" }))
{
#Html.HiddenFor(Model => Model.Enrollments.Single().progress1, new { id = "scenecheck" })
}
I have an animation generated by a program called Hype, within the animation are scenes and the current scene number is called using a javascript function with the value populating the form above in the 'progress1' field:
function updateprogress(hypeDocument, element, event) {
var scenenumber = hypeDocument.currentSceneName();
hypeDocument.getElementById('scenecheck').value = scenenumber;
$('form#ajaxForm').submit();
}
I have some other javascript which listens for the scene change and calls the function above whenever it does change.
When I change HiddenFor to TexboxFor on the page I can see that everything is working correctly within the view, the scene number is present in the textbox.
My controller action when this form submits is as follows:
[Authorize]
[HttpPost]
public ActionResult updateprogress(int EnrollmentID, CourseViewModel model)
{
Enrollment enrollment = db.enrollment.Find(EnrollmentID);
Mapper.Map(model, enrollment);
db.SaveChanges();
return null;
}
I'm using Automapper to map the change to the viewmodel back to the actual model, only a single field is to change in the viewmodel. This might not be the ideal way to do it, I'm still trying to work out how I should approach this.
My two issues are:
1 - The form does not appear to pass anything back to the controller when it submits. So the 'model' object is empty, null. This leads me to believe that what I thought I understood about what I was doing was wrong. Why is the updated field not being passed back to the controller?
2 - Is this the best method for updating the value of a single field in the viewmodel?
Thanks
You have a very complex model, the internal model binder is not going to know what to do with it because you have an IEnumerable of an object and you're editing(or hiding in this case) progress1 of the first Enrollment. You'll have to create a custom model binder.
What's happening on the view is it's storing this input tag of progress1 but when the controller gets it, it's not going to have any idea which Enrollment object it belongs to.
Try just passing in the first Enrollment to your view for editing.
This question already has an answer here:
Submit same Partial View called multiple times data to controller?
(1 answer)
Closed 6 years ago.
Background
I'm working on client's website project and I've used MVC to create the site. I've got EF working with a SQLDB to store data.
My WebApp is fitted with a back-office area for managing/adding/updating Items etc.
A particular page in question allows the user to add a new Item to the database by entering data and clicking 'Save' on the form.
The Item has some properties which all get added the DB nicely in a new row.
Item also contains a List<Appointment>s declared against it (I will serialize these and save them in the appropriate column).
The objects look like this (I have removed some properties for brevity):
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public List<Appointment> Appointments { get; set; }
// Plus some other properties
}
public class Appointment
{
public string Day { get; set; }
public string From { get; set; }
public string To { get; set; }
}
I've set out a ViewModel to use and I'm practising all that usual good-practice stuff. It looks like this:
public class AddItemViewModel
{
[Required(ErrorMessage = "Please enter an item name")]
[Display(Name="Item Name")]
public string Name { get; set; }
[Display(Name="Appointments")]
public List<Appointment> Appointments { get; set; }
...
// Other properties in here
...
}
I am using the Razor view engine.
I have everything working fine using the #Html.EditorFor(x => x.Property) helper; for all (but one - tell you in a moment) elements on the root object's (Item's) properties.
I can post the ViewModel to the controller and save a new Item, and all that jazz - so all is good there.
Problem
I have a List<Appointment> declared in my ViewModel.
How can I allow the user to add new Appointments to this List<Appointment> from within the page?
I want to assign these to the Item object to be created when the ViewModel is posted - I'll do this in my controller/services etc.
How can I achieve this?
Desired view
I need the user to be able to add Appointments from within the page - up to however many they like, really.
I would like there to be a list of the "created" Appointments in a list (ul or anything similar etc.) in an area on the page.
Underneath that list, I would like some textboxes that are relevant to the Day, From and To properties for the Appointment - with an 'Add' button, which appends it to the list (and clears the textboxes, ideally).
As a bonus, I would also like some sort of "×"-esque "delete" button next to each item, to remove it from the List<Appointment> and the view.
Here is a sample (drawn in MSPaint - old-school tools ftw!) of what I imagine the view to look like:
Things I have tried
I have been digging far and wide, but nothing has come to my rescue, as yet.
I have looked at many solutions to do with adding the objects in
JavaScript to the page - which does work. Only problem is I can't
pick them up in the object that gets posted to the controller.
I have also been pointed in the direct of using Ajax to create an Appointment object and insert it into the page, but this didn't seem to get picked up, either.
Your expertise, knowledge and wisdom is much appreciated.
To be able to add new Appointments input controls on the UI dynamically, on click of Add button, you can
[code below is from the top of my head, and more like pseudo-code]
a) use jQuery/js to clone a hidden div that you have pre-rendered on the view which contains all the relevant appointment controls.
e.g. this would be a pre-rendered div which will serve as a template
<div style="display:none;" id="appointment-template">
<input type=text>
</div>
And then the jQuery clone function on click of Add would be something like
var addNewAppointmentRowFromTemplate = function(){
$("#appointments-list-container").append($("#appointment-template").clone());
}
OR
b) Get the partial view from the server and append it to the form (again using jQuery $.get / js xhr)
$.get( "controller/templatDivActionMethodUrl", function( data ) {
$("#appointments-list-container").append( data );
});
To be able to send the model with List in Item that is accepted in ActionMethod's Item parameter, you can
a) Dynamically create a json object using jquery/javascript that has the same structure as the server side model (maintainability will be an issue when model is changed).
var appointmentsJson = [];
//foreach loop is a pseudo code, can be replaced with $.each()
foreach (var appointment in $("#appointments-list-container").children()){
appointmentsJson.push(new {'day' : $(appointment).children("#day").val(),'from' : $(appointment).children("#from").val(),'to' : $(appointment).children("#to").val()}) ;}
//and then post this json to actionMethod
$.post("serverController/Actionmethod",{appointments: appointmentsJson});
OR
b) Use the ModelBinder feature that comes with ASP.Net MVC. There's good amount of documentation/tutorials on this our there, a starting point:
https://docs.asp.net/en/latest/mvc/models/model-binding.html
Problem Statement:View not able to load on ajax success method.
Description:
I'm having couple of dropdowns as cascaded(second binding based on the value of first).
On change of the first dropdown I'm binding second dropdown and again on change of the second dropdown,now on right side I want to display a list of record based on the two dropdown values.
For this,I'm using the following JS code in onchange event for the dropdown 2:
function ShowDocsList() {
var teamId = $('#TeamID').val();
var projectId = $("#ProjectID").val();
var Url = "#Url.Content("~/DocsHome/DocsList")";
$.ajax({
url: Url,
type:'POST',
dataType: 'html',
data: { TeamID: teamId ,ProjectID : projectId},
success: function (data) {
return data;
$('.docs-detail').html(data);
}
});
Here,in DocsHome Controller,DocsList method is getting hit on change of second dropdown which is project dropdown.But the view is not getting rendered .Following is my Controller Code:
public ActionResult DocsList(int teamId, int projectId)
{
List<CustomerViewModel> customerViewsModels = SmartAdminHelper.GetCustomers(db1);
if (Request.IsAjaxRequest())
return PartialView("DocsList");
else
return View("DocsList");
}
Again ,I'm getting record in List but while debugging it does not pass to the DocsList view which according to me is correct.
Here DocsList is the view I want to render as a partial view on change of the second dropdown.
According to my knowledge,while debugging it comes to the point return PartialView("DocsList") but then again it goes back to the ajax success method and finally I find that there I'm doing something wrong.
Earlier I have Json to get data but here I'm calling actionmethod from ajax. So, not sure that also might be a problem as I'm new to this.
What and where exactly I'm doing wrong?
Saroj, I see that this is an old question and that you needed to get it done quickly, but if you happend to come back to this, I'll add my two cents. You need to remove the return statement that David and Ehsan mention above. The rest of the callback function does what it should. In your action method it doesn't look like you're doing anything with the parameters you pass in. I'm assuming that you are going to figure that out after you get the view down to the client. So, lets get that view down to the client.
I like to pass the rendered partial view back to the client as a string of HTML. I do this using a method that I keep in a controller base class that each of my controllers inherit from. To use the method you will need to reference System.Web.Mvc and System.IO namespaces.
The method looks like this:
private string RenderViewToString( string viewName, object model ) {
ViewData.Model = model;
using ( var sw = new StringWriter() ) {
var viewResult = ViewEngines.Engines.FindPartialView( ControllerContext, viewName );
var viewContext = new ViewContext( ControllerContext, viewResult.View, ViewData, TempData, sw );
viewResult.View.Render( viewContext, sw );
viewResult.ViewEngine.ReleaseView( ControllerContext, viewResult.View );
return sw.GetStringBuilder().ToString();
}
}
You pass your model and the name of the view to the method and it returns the rendered view HTML as a string which you can return to the client as a ContentResult.
Update your action method like so:
public ActionResult DocsList(int teamId, int projectId)
{
List<CustomerViewModel> customerViewsModels = SmartAdminHelper.GetCustomers(db1);
if (Request.IsAjaxRequest())
var viewContents = RenderViewToString("DocsList", customerViewsModels);
return Content(viewContents);
else
return View("DocsList");
}
Assuming that the element that you want the rendered view to appear in has the css class '.docs-detail' on it you'll be in business. Hope this helps!
In my controller, I send an object list into the view (index.cshtml)
return View(AdsPrevModel);
in my index.cshtml:
<div id ="ele">
<ul>
<li> name1<input id="a1" type="checkbox"/></li>
</ul>
</div>
when the user clicks the checkbox, I use jquery to know if the user checked the box or not:
My javascript file:
$('#ele :checkbox').click(function () {
if ($(this).is(':checked')) {
alert($(this).attr('id'));
} else {
alert('unchecked');
}
});
How can I get my AdsPrevModel into my js file?
I know I can do something like this:
In my html, add:
<input type="hidden" id="AdsPrevModel" value="#Model.AdsPrevModel" />
and in the js:
var adsPrevModel = JSON.parse(document.getElementById('AdsPrevModel').value);
Is there another option without adding a hidden input in my html?
Maybe something like the following in the js file:
var adsPrevModel = JSON.parse(Model.AdsPrevModel));
The best practise is
do an ajax call to that controller and that controller should return json results
return JSON( model ) ;
In the code you've shared there's nothing emitting the model to the client, so there's currently no direct way for the JavaScript code to access it.
Since you're binding the view to the model, the view can include it in various ways. It could be a series of hidden fields for the members of the model (not the model in its entirety, unless it can be represented as a string in its entirety). Something like this:
#Html.HiddenFor(x => x.SomeField)
#Html.HiddenFor(x => x.AnotherField)
This would create two hidden inputs for two fields on the model. Depending on how complex the model is, this could get cumbersome.
You might also emit the model to the JavaScript code directly in a similar fashion:
var someField = #Model.SomeField;
var anotherField = #Model.AnotherField;
Again, if the model is complex, this gets cumbersome quickly. Even if you try to build an actual JavaScript object from it:
var theModel = {
someField : #Model.SomeField,
anotherField : #Model.AnotherField
};
(Note also that I've seen Visual Studio get very confused when you mix razor syntax and JavaScript like this. Not so much in 2012 anymore, but a lot in 2010.)
You might use something like the JavaScriptSerializer to add a property on the model for a serialized version of itself. I've never done this before, but it should work. Something like this on the model:
public string SerializedCopy
{
get
{
return new JavaScriptSerializer().Serialize(this);
}
}
It might take some tweaking to get it to work, though.
Finally, a particularly clean option which only requires another request to the server would be to have another action which just returns the JSON version of that model. Something like this:
public ActionResult SomeActionName()
{
// get the model somehow, then...
return Json(AdsPrevModel);
}
Your JavaScript code would then just need to call this action to get the JSON object representing the whole model:
var theModel = {};
$.get('#Url.Action("SomeActionName", "SomeController")', function (data) {
// maybe do some error checking here?
theModel = data;
});
Then if your actual view isn't actually binding anything to the model then the action which returns that view doesn't need to fetch the model and supply it to the view. The JavaScript code would get the model by calling this other action which returns JSON data instead of a view.