I am new to Ajax and Jquery so i need you to forgive me if i am trying to do something stupid,
i am working with MVC 5 and Ajax.Beginform,
and what i am trying to do is i have a ajax form i need to validate it with jquery unobtrusive,if i am getting it right the Jquery validation work with ModelState and will return the view back again if is a validation error found, in this case i need to update my form so the validation message appears to the user browser ,for example here is my controller :
[HttpPost]
public ActionResult Index(AddProduct model)
{
if (ModelState.IsValid)
{
// connect to the database save data etc...
return PartialView("~/Views/Shared/_MyModal.cshtml");
}
else
{
return View(model);
}
}
if the ModelState.IsValid i should save the data and return partial view (Bootstrap Modal) indicating that the data has been saved successfully,
else it will return the whole view to display the validation messages and to do that i have to put the TargetId of the ajax form to wrap up the whole ajax form to updated,
and here the ajax form :
<div id="result">
#using (Ajax.BeginForm("Index", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "result",
HttpMethod = "POST",
OnBegin = "onBegin();",
OnComplete = "onCompleated();",
OnSuccess = "onSuccess()",
OnFailure = "onFailure()"
}))
{
#Html.ValidationSummary(true)
<div id="form1" class="form-horizontal">
<div class="row">
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Name, String.Empty, new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Price, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Price, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Price, String.Empty, new { #class = "form-help form-help-msg text-red" })
</div>
</div>
<div class="form-group">
<button class="btn btn-default col-md-2 col-md-offset-2" type="submit">Save </button>
</div>
</div>
</div>
}
</div>
else i will return a partial view and display it as Bootstrap Modal and i need in this case not to update the ajax form targetId i need to keep it intact and just Displaying the Modal, but the problem is in both situation ModelState.IsValid or Is not Valid all response fires onSuccess Method in ajax form i didn't know these is normal or not, and here is the javascript onsuccess() Method :
function onSuccess() {
$('#myModal').modal('hide')
$("#resultModal").modal({
backdrop: 'static',
keyboard: false
});
$('#resultModal').on('hidden.bs.modal', function (e) {
window.location = "/product";
});
}
$(#'myModal').modal('hide') is a progress Modal i hide it after the posting has finish,and the next step is i show result Modal which i had return it if the Posting execute fine without validation error,
the Problem is :
i get to update the form using the UpdatetargetId in the ajax form just fine if validation Error happen to be Exist, but that's happen any way if i am returning Validation Error Or partial view that caring the Modal for me,in both situation the form disappear,and that's something i don't wont to do,
i need to keep the form in case the return Content was partial view and updated in case ModelStat validation Error, maybe i miss understand everything but if i am, i need some explanation to put me back on track .
I did an ugly code and messy and i didn't like in onSuccess() Mehtod as mentioned above, i used event fires when i close my result Modal redirect me again to Index so the form Display again but its not clean and i don't like it, i need something professional if exist.
Thank you in Advance,
I perfectly understand your problem and I am ready to show you the method that I am using to handle this case.
So because onSuccess method is called every time not depending if ModelState is valid or not , you will need to handle the response a little bit different.
So your[HttpPost] method should look like this:
[HttpPost]
public ActionResult Index(AddProduct model)
{
if (ModelState.IsValid)
{
return Json(new {isValid = true, data = this.RenderPartialViewToString("ViewWhenModelStasteIsValid",model,false)});
}
else
{
return Json(new { isValid = false, data = this.RenderPartialViewToString("ViewWhenModelStasteIsNotValid", model, false) });
}
}
public static string RenderPartialViewToString(this Controller controller, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
{
viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
}
controller.ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
In the view you will need to take off UpdateTargetId from the form and handle it manually on success function.
So your onSuccess function should look like this
function onSuccess(result, ref) {
if (result.isValid) {
jQuery("#result).html(result.data);
} else {
jQuery("#form1").html(result.data);
}
}
Make sure to pass the response data to onSuccess function, so you will need to change on the form from OnSuccess = "onSuccess()" to OnSuccess = "onSuccess(data,this)".
So your form will look like this:
#using (Ajax.BeginForm("Index", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnBegin = "onBegin();",
OnComplete = "onCompleated();",
OnSuccess = "onSuccess(data,this)",
OnFailure = "onFailure()"
}))
Hope this is what you need.
Related
Goal: I'm setting up the checkout page for my website and would like to have the user select from a list of their addresses. When they select it, it will add it to the cache and save it for when they have everything set up and are ready to complete their order.
Issue: When selecting the address, and pressing save changes, it returns 0 instead of the actual value of the item and I don't know why.
Here's the form:
Here's the view:
#model AirmotionEcommerceWebsite.Models.Home.CheckoutModel
#{
ViewBag.Title = "Checkout";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<br />
<div class="container">
<h1>Checkout</h1>
<form>
<div class="jumbotron row product-container">
<div class="col-md-4">
#{
IEnumerable<SelectListItem> dataItems = ViewBag.UserAddresses;
}
<div class="form-group">
<h4>To Address:</h4>
#Html.DropDownListFor(model => model.selectedShippingAddress.IntShippingAddressId, dataItems, "-- Select --", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.selectedShippingAddress.IntShippingAddressId, "", new { #class = "text-danger" })
<a asp-controller="Home" asp-action="AddShippingAddress">Add New Address</a>
</div>
<div class="form-group">
<button type="button" class="btn btn-primary" data-ajax-method="get" data-toggle="ajax-modal" data-target="#ValidateAddress"
data-url="#Url.Action("CheckoutChanges", new { intShippingAddressID = Model.selectedShippingAddress.IntShippingAddressId })">Verify Address</button>
</div>
</div>
</div>
</form>
</div>
<script>
$(function () {
$('button[data-toggle="ajax-modal"]').click(function (event) {
event.preventDefault();
var url = $(this).data('url');
// get the form containing the submit button
var form = $(this).closest('form')
// serialize all the fields in the form
var model = form.serialize();
// the the request to the url along with the form (model) data
$.get(url, model).done(function (data) {
PlaceHolderElement.html(data);
})
})
})
</script>
Here's the controllers:
[Authorize]
public ActionResult Checkout()
{
// get userid
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
// get addresses for this user
ViewBag.UserAddresses = GetShippingAddresses(userId);
CheckoutModel model = new CheckoutModel();
model.selectedShippingAddress = new TwebShippingAddress();
bool AlreadyExists = memoryCache.TryGetValue<CheckoutModel>("CachedModel", out model);
if (!AlreadyExists)
{
model = new CheckoutModel();
model.selectedShippingAddress = new TwebShippingAddress();
var cachEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(20));
memoryCache.Set("CachedModel", model, cachEntryOptions);
}
return View(model);
}
[HttpGet]
public ActionResult CheckoutChanges(int intShippingAddressID)
{
if (intShippingAddressID == 0)
return View();
CheckoutModel model = new CheckoutModel();
bool AlreadyExists = memoryCache.TryGetValue<CheckoutModel>("CachedModel", out model);
if (AlreadyExists)
{
model.selectedShippingAddress = context.TwebShippingAddresses.Where(x => x.IntShippingAddressId == model.selectedShippingAddress.IntShippingAddressId).FirstOrDefault();
var cachEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(20));
memoryCache.Set("CachedModel", model, cachEntryOptions);
}
return View();
}
Lastly, this is the GetShippingAddresses() method:
public IEnumerable<SelectListItem> GetShippingAddresses(string strUserID)
{
List<SelectListItem> list = new List<SelectListItem>();
var cat = context.TwebShippingAddresses.Include(x => x.IntState).Where(x => x.IntWebUserId == strUserID).OrderByDescending(x=>x.BlnIsDefault);
foreach (var item in cat)
{
list.Add(new SelectListItem { Value = item.IntShippingAddressId.ToString(), Text = item.StrName + " " + item.StrAttnTo + " " + item.StrStreet1 + " " + item.StrStreet2 + ", " + item.StrCity + " " + item.IntState.StrStateCode + " " + item.StrZip });
}
return list;
}
Replace the $.get like this to pass a Json object that will be auto binded to the action's parameter:
<script>
$(function () {
$('button[data-toggle="ajax-modal"]').click(function (event) {
event.preventDefault();
var url = $(this).data('url');
// get the form containing the submit button
var form = $(this).closest('form')
// serialize all the fields in the form
var model = form.serialize();
// the the request to the url along with the form (model) data
int selectedIndex = //Write jquery code to get the selected index here
$.ajax({
type: 'GET',
url: url,
data: { intShippingAddressID: selectedIndex },
dataType: 'json',
})
.success(function( data ) {
PlaceHolderElement.html(data);
});
})
})
</script>
Note the Json data type and the property that match the parameter of the Action.
I have an Ajax button that whenever I click it, it shows a single record from the database (in my Controller I used .Take(1) )
Here is my view :
<script src="~/Scripts/jquery-3.1.1.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script type="text/javascript" language="javascript">
function ClearResults() {
$("#current").empty();
}
</script>
<div class="row">
<div class="column">
<h2 class="header-text">CURRENT</h2>
<div class="current" id="current">
X000
</div>
</div>
</div>
<div class="row">
#Ajax.ActionLink(" ", "BtnNext", null, new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "current",
LoadingElementId = "loading",
OnBegin = "ClearResults",
}, new { #class = "Middle-next dim btn btn-large-dim", #id = "Link1"})
</div>
Here is my controller for the button :
public PartialViewResult BtnNext(int count = 0)
{
System.Threading.Thread.Sleep(1000);
var model = db.Queues.OrderBy(x => x.QueueNumber).Take(1);
return PartialView("_queuenumber", model);
}
What I would like to do here is - whenever I click the next button it will display the first record from the database, then when I click it again it will show the second record and so on..
(as of now it only show the first data from database)
I wonder if that is even possible and what kind of stuff should I use to do that?
You're going to want to track the current index client-side (JavaScript) or in the Model (Razor), then pass that into your controller method, incrementing the value with each call. From the server side you would do something like:
var model = db.Queues.OrderBy(x => x.QueueNumber).Skip(index).Take(1);
I'd recommend sending back a ViewModel. For the Razor implementation you could have a ViewModel that contains the displayed values, the Queue ID, and the index. I.e.
public class QueueViewModel
{
public int QueueId { get; set; }
public int QueueNumber { get; set; }
public string Name { get; set; }
// ..
public int NextIndex { get; set; }
}
public PartialViewResult BtnNext(int index = 0)
{
var model = db.Queues.OrderBy(x => x.QueueNumber)
.Select(x => new QueueViewModel
{
QueueId = x.QueueId,
QueueNumber = x.QueueNumber,
Name = x.Name,
NextIndex = index + 1
})
.Skip(index).Take(1);
return PartialView("_queuenumber", model);
}
Then in the view would be something like below: (Sorry, my Razor is pretty rusty)
#Ajax.ActionLink(" ", "BtnNext", "QueueController",
new { index = Model.nextIndex },
new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "current",
LoadingElementId = "loading",
OnBegin = "ClearResults",
}, new { #class = "Middle-next dim btn btn-large-dim", #id = "Link1"})
This would need some verification for behaviour to ensure that the first call goes through with an index value of "0", but should hopefully give you some ideas to pursue.
I have the following Action in my controller:
[HttpPost]
public JsonResult RedirectToAspReportViewer(MvcReportPeriodSelectionViewModel periodFilter, MvcMultipleLocationSelectionViewModel locationFilter)
{
var jsonObject = new { HasErrors = false, strUrl = "/ASPReports/TreatmentOutcome.aspx" };
if (ModelState.IsValid)
{
try
{
//some code
}
catch (ValidationException ex)
{
this.HandleValidationErrors(ex);
jsonObject = new { HasErrors = true, strUrl = "/TreatmentOutcomeReport/Report/" };
}
}
return Json(jsonObject);
}
Then, in my Javascript, I have the following function, which get's called on my ajax post's OnSuccess function.
onSuccessCall: function (response) {
if (response.HasErrors === true) {
//window.location = window.location.href;
location.reload();
} else {
window.open(response.strUrl);
}
};
As you can see from above, if my reponse object has errors, I would like to stay on the current page, just refresh it so that my ModelState errors would still show.
The problem I am facing, is that when I call location.reload, my ModelState errors do not show on my page. I have a feeling it is because I am posting to the server again, and the ModelState gets cleared.
How can I avoid this?
UPDATE
I cannot just add the validation error to the JsonResult, and in the client side, update the necessary DOM to display the errors. On all of my views, I have the following shared view which returns my errors: Below is the sample:
#model ModelStateDictionary
#{
var cls = "alert-danger";
var type = ViewBag.Type;
if (type != null && type.ToString().ToLower() == "warning")
{
cls = "alert-warning";
}
var message = "Please attend to the following problems:";
if (ViewBag.Message != null && ViewBag.Message.ToString().Trim() != "")
{
message = ViewBag.Message.ToString().Trim();
}
}
#if (ViewData.ModelState.Keys.Any(k => ViewData.ModelState[k].Errors.Count() > 0))
{
<div class="alert #cls">
<button class="close" data-dismiss="alert" aria-hidden="true">× </button>
#Html.ValidationSummary(false, message)
</div>
}
This will get called at the top of all my views as follow:
<div id="valSummary">
#Html.Partial("_ValidationSummaryDisplay", ViewData.ModelState)
</div>
If you want your ModelState errors to show on the page, then you should
return View(yourViewModel);
When coding the view, be sure to include the helpers to show your validation:
#Html.ValidationMessage(m => m.PropertyName)
I am assuming your handle method puts the errors in the ModelState (as that is what it is for).
I know that a very similar question was posted and ansered:
How to gracefully handle AJAX PartialView update when Javascript is disabled
But that solution isnt satisfying me.
Is it possible, to update a Element with the Ajax Helper Method when Java Script is disabled, that it will show a partial view in the same page and not in an extra tab?
I want that the Tag in the Index View is updated with a partial View (_Details), when I click on a AJAX ActionLink.
With the PView 1 Method, I get the same result with JS enabled and disabled. But i dont linke the PView 1 solution (as suggested in the similar question), because this makes the Partial View Class useless. Why would i need it when i reload the hole page anyway.
I would prefer a solution similar to PView 2. But there the Partial View is opened in a new Tab when JS is disabled.
My very simplified code so far:
HomeController Class
public class HomeController : Controller
{
public ActionResult Index()
{
var obj_str = new SimpleString { astr = "Nothing Yet" };
return View(obj_str);
}
public ActionResult PView1()
{
string str_posted = "String of PView 1";
var obj_str = new SimpleString {astr = str_posted};
if (Request.IsAjaxRequest())
{
return PartialView("_Details", obj_str);
}else
{
return View("Index", obj_str);
}
}
public PartialViewResult PView2()
{
var obj_str = new SimpleString {astr = "String of PView 2"};
return PartialView("_Details", obj_str);
}
}
Index.cshtml
<h2>AJAX Actionlink Index</h2>
#Ajax.ActionLink("Click me for PView 1", "PView1", new AjaxOptions
{
UpdateTargetId = "partv",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET"
})
#Ajax.ActionLink("Click me for PView 2", "PView2", "Home", new AjaxOptions
{
UpdateTargetId = "partv",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET"
})
<h3>Partial View here!</h3>
<div id="partv">
<p>#Model.astr</p>
</div>
_Details.cshtml (the Partial View)
#model MVCPartialViewTest.Models.SimpleString
<p>This is from the Partial View cshtml</p>
<p>#Model.astr</p>
SimpleString Class (the Model)
public class SimpleString
{
public string astr { get; set; }
}
we are writing an ASP.NET MVC3 Application and want to do it "rich", for example by using bootstrap und modal dialogs.
I now wonder how to implement modal dialogs, without breaking all the cool staff from ASP.NET (ModelErrors, ...).
The workflow should be like this:
IndexView with a list of items, each item with an actionlink that shows a modal dialog
#Ajax.ActionLink(
"Edit", // Link Text
"Edit", // ActionMethod
new { id = item.Id }, // RouteValues
new AjaxOptions {
HttpMethod = "Get",
OnBegin = "modal.showModalDiv()",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "modal-div",
OnSuccess = "modal.ajaxSuccess()" },
new { data-toggle = "edit-modal" } // HTML-Attributes
)
The modal dialog (simple div styled with css) renders the editview (returned from controller actionmethode)
[HttpGet]
public ActionResult Edit(int id) {
// Load Data and create Model
var model = new ...
return PartialView(model);
}
The form in the edit view can be used to edit the item, including client-side validation
#{
AjaxOptions ajaxOptions = new AjaxOptions() {
HttpMethod = "Post", OnSuccess="modal.hideModalDiv()"
};
}
#using (Ajax.BeginForm("Edit"), ajaxOptions){
... element to edit item ...
<input type="submit" value="submit" />
}
When the submit edit controller-methode recognizes errors (not caught by client-side-validation) the page should be displayed again with model-errors. Otherwize the index-page should be shown or the table refreshed and the modal dialog closed.
[HttpPost]
public ActionResult Edit(Guid id, ItemModel model) {
try{
...Save Item ...
return RedirectToAction("Index")
} catch (Exception ex){
ModelState.AddModelError("", "An error occured")
return PartialView(model);
}
}
My Problem is: How to implement step 4? Does Someone has an advice?
Try with this,
[HttpPost]
public ActionResult Edit(Guid id, ItemModel model)
{
if(model != null && ModelState.IsValid)
{
return RedirectToAction("Index")
}
else
{
return PartialView(model);
}
}
I guess you've already solved this problem or found a work around but in case you have not I suspect if you were to return a partial view that contains javascript to do the redirection you could get the behaviour you describe.
For example:
RedirectToIndex.cshtml
#{ Layout = null; }
<script type="text/javascript">
window.location.href = "#Url.Action("Index")";
</script>
Then update your action to return this partial view.
[HttpPost]
public PartialViewResult Edit(Guid id, ItemModel model) {
try{
//Save Item ...
return PartialView("RedirectToIndex")
} catch (Exception ex){
ModelState.AddModelError("", "An error occured");
return PartialView(model);
}
}
Not the most graceful but should work, I say should as I have not tested this...