Returning ValidationSummary in Partial View to Ajax error handler - nothing else getting rendered - ajax

I'm calling a method from Ajax that updates a some content in the page by loading up a partial view & setting a contained property appropriately.
This all works ok but I'm having problems with server side validation using the ValidationSummary control. I'm using an approach suggested elsewhere to stick the ValidationSummary in a partial view. (I've actually got client side validation turned off & I'm trying to get server side validation to work using the same validation summary that client side validation uses)
When there is a validation error, ajax error handler should update the contents of a div with the partial view that is returned.
All this works ok in the sense that the validation summary is rendered with the expected error messages except for the fact that the nothing else is getting displayed on the page other than the partial view containing the validation summary and its containing elements. i.e. no siblings / of the div are getting rendered
I reaslly haven't got a clue what's going on. Intersetingly, if I comment out the line in the error handler that updates the div with the partial, I see the form in its entirety again.
I'm using an ExceptionFilter:
public class ValidationErrorAttribute : FilterAttribute, IExceptionFilter
{
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode =
(int)System.Net.HttpStatusCode.BadRequest;
//return the partial view containing the validationsummary and set the ViewData
//so that it displays the validation error messages
filterContext.Result = new PartialViewResult { ViewName = "validation",
ViewData = filterContext.Controller.ViewData };
}
}
}
when there's a validation error, throw a validationexception which will
trigger the OnException method of the filter
//Controller
[HttpPost]
[ValidationErrorAttribute]
public ActionResult Save(ConsumptionData consumptionData)
{
if (ModelState.IsValid)
{
var dto = Mapper.Map<ConsumptionData, ConsumptionDataDto>(consumptionData);
var refId = _personalProjectionRepository.AddPersonalProjectionRecord(dto);
consumptionData.ReferenceId = refId;
return PartialView("Reference", consumptionData);
}
else
{
throw new ValidationException();
}
}
The validation partial view:
#model PersonalProjection.Web.Models.ConsumptionData
#Html.ValidationSummary()
and the jquery:
$('form').submit(function () {
var form = $(this);
if (form.valid())
{
$.ajax({
url: form.attr("action"),
type: "POST",
dataType: "html",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
SiteId: $('#siteid').val(),
ElectricityConsumption: $('#electricityconsumption').val(),
ElectricitySpend: $('#electricityspend').val(),
GasConsumption: $('#gasconsumption').val(),
GasSpend: $('#gasspend').val()
}),
success: function (result) {
$('#rightPanelSection').html(result);
},
error: function (jqXHR) {
$('#validationSummary').html(jqXHR.responseText);
}
});
}
return false;
});
and the index.cshtml markup
<div class="panel panel-outcome">
#using (Html.BeginForm("Save", "Home", FormMethod.Post, (object)new { ID =
"projectionForm" }))
{
<div id="validationSummary"/>
<div id="topPanelSection" class="panel-section">
#Html.LabelFor(m => m.SiteId, new { id = "siteidlabel" })
#Html.TextBoxFor(m => m.SiteId, ViewBag.TextBoxDisabled ? (object)new { id =
"siteid", disabled = true, maxlength = 9, #class = "input-disabled", #Value =
ViewBag.SiteId } : new { id = "siteid", maxlength = 9 })
<span id="errorText" class="error-text" />
</div>
<div id="leftPanelSection" class="panel-section">
<div class="column-heading">
<span class="column-heading-left">Total Cost</span>
<span class="column-heading-right">Total Consumption</span>
</div>
<div class="panel-section-row">
#Html.LabelFor(m => m.ElectricitySpend, new { id = "electricitylabel" })
<span>£</span>
#Html.TextBoxFor(m => m.ElectricitySpend, new { #class = "textbox-right-
margin", id = "electricityspend" })
#Html.TextBoxFor(m => m.ElectricityConsumption, new { #class = "textbox-
left-margin", id = "electricityconsumption" })
<span>kWhs</span>
</div>
<div class="panel-section-row">
#Html.LabelFor(m => m.GasSpend, new { id = "gaslabel" })
<span>£</span>
#Html.TextBoxFor(m => m.GasSpend, new { #class = "textbox-right-margin", id =
"gasspend" })
#Html.TextBoxFor(m => m.GasConsumption, new { #class = "textbox-left-margin",
id = "gasconsumption" })
<span>kWhs</span>
</div>
<div class="panel-section-row">
<button type="reset" id="resetbutton">Reset Form</button>
<button type="submit">Generate Reference Id</button>
</div>
</div>
<div id="rightPanelSection" class="panel-section">
#Html.Partial("Reference", Model)
</div>
}

I'm fairly new to Ajax so this is probably a schoolboy error but here goes:
It's because when I use this jquery
$('#validationSummary').html(jqXHR.responseText);
To update this
<div id="validationSummary"/>
it doesn't like the closing tag & it messes up
Change to this
<div id="validationSummary">
</div>
And it works ok.

Related

MVC 4 Razor using Ajax forms to update a foreach loop

Where to start...I can find similar things on the Internet as to how, but they never seem to work with my specific way of wanting to do something. I have tried with and without partial views with very little success.
Quick rundown: I have a strongly-typed View with an Ajax form. underneath the form, I have a foreach loop that repeats a code block. I need to be able to update the code block from the forms choices (filters).
Here's my View, 'FindATeacher.cshtml', as it currently stands (after trying many different ideas):
#model Teachers.Models.OmniModel
#{
ViewBag.Title = "FindATeacher";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Find a Teacher</h2>
#using (Ajax.BeginForm("FilterTeachers", "Home", new AjaxOptions { HttpMethod = "Post", OnSuccess = "onSuccess" }))
{
<div id="ContentFilter">
<div class="filterLabels">
<p>Search by Name</p>
<p>Filter By Instrument</p>
<p>Filter By City</p>
</div>
<div class="filterObjects">
<p>
<input type="text" id="nameTXT" />
<button type="submit" id="findButton">find</button>
</p>
<p>#Html.DropDownList("InstrumentID", (SelectList)Model.Instruments, "-- Select an Instrument --", new { id = "instrumentDD" })</p>
<p>#Html.DropDownList("CityID", (SelectList)Model.Cities, "-- Select a City --", new { id = "cityDD" })</p>
</div>
</div>
}
<hr />
#foreach (var r in Model.Teachers)
{
<div id="ContentResults">
<div id="avatar">
<img src="i" />
</div>
<div id="demographics">
<h6>#r.Teacher</h6>
<strong>#r.StudioName</strong>
<p>#r.URL</p>
<p>#r.StreetAddress</p>
<p>#r.City, #r.AddressStateID #r.Zip</p>
<p>#r.Phone</p>
<p>#r.EmailAddress</p>
</div>
<div id="studioDetails">
<p><strong>Instrument(s) Taught</strong></p>
<p>
#{
var instrumentString = r.Instruments.Aggregate("", (a, b) => a + b.Instrument + ", ");
if (instrumentString.Length != 0)
{
instrumentString = instrumentString.Remove(instrumentString.LastIndexOf(","));
}
}
#instrumentString
</p>
<br />
#if (r.Information != "" && r.Information != null)
{
<p><strong>Information</strong></p>
<p>#r.Information</p>
}
</div>
</div>
}
Now here's my Controller. I get the results back correctly in the Controller, just not updating the code block:
public ActionResult FindATeacher()
{
Model.Instruments = new SelectList(TeacherService.GetInstrumentList(0),"InstrumentID","Instrument");
Model.Cities = new SelectList(TeacherService.GetCityList(),"CityID","City");
Model.Teachers = TeacherService.GetTeacherList("", 0);
return View(Model);
}
[HttpPost]
public JsonResult FilterTeachers(String teacherName, String instrumentID, String cityID)
{
Model.Teachers = TeacherService.GetTeacherList("John", 0, 0);
return Json(Model.Teachers);
}
Thanks.
#VishalVaishya presents the right idea, but there's a simpler way, which doesn't involve custom javascript code: AjaxOptions has an UpdateTargetId property that the AJAX toolkit will interpret to mean you want the given target to be updated with the results sent back from the controller.
FindATeacher.cshtml:
#using (Ajax.BeginForm("FilterTeachers", "Home", new AjaxOptions {
HttpMethod = "Post", UpdateTargetId = "TeacherList" }))
{
...
}
<hr />
<div id="TeacherList">
#Partial("TeacherList", Model.Teachers)
</div>
TeacherList.cshtml
#model IEnumerable<Teacher>
#foreach(var teacher in Model)
{
...
}
Controller action:
[HttpPost]
public ActionResult FilterTeachers(String teacherName, String instrumentID, String cityID)
{
Model.Teachers = TeacherService.GetTeacherList(teacherName, instrumentID, cityID);
return PartialView("TeacherList", Model.Teachers);
}
You can try following method:
Separate your foreach loop into another partial view.
And load your partial view on filter / click event and pass filtered parameters to your controller-action.
JS change event code will be something like this:
var teacherName = ''; //get your selected teachername
var instrumentID = ''; //get your selected instrumentid
var cityID = ''; //get your selected city id
var url = '#Url.Action("FilterTeachers", "ControllerName", new { teacherName = "teacher-Name", instrumentID="instrument-ID", cityID="city-ID" })';
url = url.replace("teacher-Name", teacherName).replace("instrument-ID", instrumentID).replace("city-ID", cityID);
$('#result').load(url);

Jquery Accordion Validation Not working when rendering Partial View through $.ajax call

Hi friends,I am working on MVC 4 Razor and I am stuck in a situation
where Employee Personal Details form is to be filled in
steps(wizard)..for which i used jquery accordion control..for every
step i put an accordion..The html in each accordion section is
rendered from partial view through ajax call on every click of
respective accordion (i.e. <h3></h3> tag)..
On page load first/top accordion is active by default. My problem is
to restrict the user to click on next accordion until he/she fills the
presently active accordion correctly..
Here is my full code:
View:
#model XXX.ViewModels.PersonalDetailsViewModel
#{
ViewBag.Title = "PersonalDetails";
Layout = "~/Views/Shared/Template.cshtml";
}
#using (Html.BeginForm("Lifestyle", "Apply", FormMethod.Post, new { id = "personalDetailForm" }))
{
<div class="centerdiv margin_top20">
<div class="row">
#Html.ValidationSummary(true, "Please Correct the following errors:")
</div>
<div style="width: 1000px;">
<div id="Personalaccordion" class="acordion_div" style="padding: 10px; float: left;">
<h3 class="acordion_div_h3" onclick="javascript:PersonalModule.GetRenderingView('Apply/GetBasicDetailsView','personalDetailForm','BasicDetailsDiv');">
<p>
Basic Details<span id="BasicDetailsDivExp"></span>
</p>
</h3>
<div id="BasicDetailsDiv">
</div>
<h3 class="acordion_div_h3" onclick="javascript:PersonalModule.GetRenderingView('Apply/GetPersonalAddressView','personalDetailForm','PersonalAddressDiv');">
<p>
Address<span id="PersonalAddressDivExp"></span></p>
</h3>
<div id="PersonalAddressDiv">
</div>
</div>
<ul id="conlitue_ul" style="margin-top: 20px;">
<li style="margin-left: 140px;">
<input type="submit" class="compareBtn float_lt" value="Continue Buying >" id="continue" /></li>
</ul>
</div>
</div>
}
#Scripts.Render("~/bundles/PersonalDetails")
<script type="text/javascript">
PersonalModule.GetRenderingView('Apply/GetBasicDetailsView', '', 'BasicDetailsDiv');
</script>
My Controller:
public ActionResult PersonalDetails(int leadId)
{
var personalDetailsViewModel = LeadHelper.GetPersonalDetails(leadId);
return View(personalDetailsViewModel);
}
public ActionResult GetBasicDetailsView(PersonalDetailsViewModel personalDetailsViewModel)
{
if (personalDetailsViewModel.BasicDetails == null)
{
ModelInitializerHelper.InitilaizeBasicDetailsVModel(personalDetailsViewModel);
}
ModelInitializerHelper.InitializeBasicLookup(personalDetailsViewModel);
return PartialView("Personal/BasicDetails", personalDetailsViewModel);
}
public ActionResult GetPersonalAddressView(PersonalDetailsViewModel personalDetailsViewModel)
{
if (personalDetailsViewModel.PersonalAddressDetails == null)
{
ModelInitializerHelper.IntializePersonalAddressVModel(personalDetailsViewModel);
}
ModelInitializerHelper.InitializePersonalAddressLookup(personalDetailsViewModel);
return PartialView("Personal/PersonalAddress", personalDetailsViewModel);
}
My JS :
var PersonalModule = {
GetRenderingView: function (url, formId, containerID) {
var applicationurl = ApplicationRoot + '/' + url;
var objects = $('#BasicDetailsDivExp , #PersonalAddressDivExp' );
viewDivID = containerID;
GetAccordionView(applicationurl, formId, objects, containerID, 'accordion_plus', 'accordion_minus');
}
}
GetAccordionView: function (url, formId, objects, containerID, accordion_plus, accordion_minus) {
var formObjectData = null;
if (formId != undefined) {
formObjectData = $("#" + formId).serialize();
}
var renderView = function (data) {
$('#' + containerID).innerHtml = data;
}
ExpandAccordion(objects, containerID, accordion_plus, accordion_minus);
DoServerRequest(url, formObjectData, renderView);
}
ExpandAccordion: function (objects, spanIconID, accordion_plus, accordion_minus) {
var Objects = objects;
Objects.removeClass(accordion_minus);
Objects.addClass(accordion_plus);
$('#' + spanIconID + 'Exp').removeClass(accordion_plus).addClass(accordion_minus);
if (Browser.ie7) {
Objects.css("margin-top", "-22px");
}
}
DoServerRequest: function (url, data, funSuccess) {
$.ajax({
type: "POST",
url: url,
data: data,
async: false,
dataType: "json",
success: funSuccess,
error: function (errorResponse) {
if (errorResponse.readyState == 4 && errorResponse.status == 200) {
renderCurrentView(errorResponse.responseText)
}
else {
alert(errorResponse.responseText);
}
}
});
}
Please somebody help..I have heard lots of good thing about this forum
and this is my first Question...Thanks in advance..
I have removed my jquery validation attempt as it made the code
garbage thing Now I dont know what to write and where to write
If you are trying to validate data that has been added to form via Ajax after page load then you will need to use the rules method and add rules for these new elements. Jquery Validate has no way of knowing about them otherwise.
Example
Once you have loaded your new content via Ajax you need to find each element and add the necessary rules to them.
$('#yourDiv').find(".newElements").rules("add", {
required: true,
messages: {
required: "Bacon is required"
}
});
If you are using unobtrusive validate you may need to add your new elements to that also. See this SO question for more details.
Validating the Form
To check if the fields are valid, you will need to validate the form on click. This can be done using .validate(). You can then check if the form validated using .valid()
Example
$('#yourForm').validate();
if(!$('#yourForm').valid()) {
alert('Bacon is required');
}

Return PartialView to specific div from Action

I am playing about with jQuery UI and PartialViews and have run into a problem I can't quiet get my head around.
This bit works as I expect:
<div>
#Ajax.ActionLink("Test Me!", "dialogtest", new { id = Model.Id }, new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "dialogtest-view" })</td>
</div>
<div id="dialogtest-view">
</div>
this GETs to this action method
[HttpGet]
public PartialViewResult DialogTest(int id)
{
//pretend to get something from DB here
var vm = new DialogUITestVM();
return PartialView("uidialog_partial", vm);
}
And returns me a PartialView which displays in the targeted div. jQuery + jQueryUI is used to pop this div up as a modal dialog. Part 1 of test done!
OK so now let's say the PartialView returned is just a basic form with a textbox, something along the lines of:
#using (Html.BeginForm("DialogTest", "pages", FormMethod.Post))
{
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x => x.Name)
<button type="submit">Test Me!</button>
}
This is POSTd back to the controller fine -
[HttpPost]
public ActionResult DialogTest(DialogUITestVM vm)
{
//arbitrary validation so I can test pass and fail)
if (vm.Name.Equals("Rob"))
{
//error!
vm.ErrorMessage = "There was an error you numpty. Sort it out.";
return PartialView(vm);
}
//hooray it passed - go back to index
return RedirectToAction("index");
}
However - if I make the action fail the validation, rather than targeting the PartialView to the div again, it redraws the whole page (which obviously loses the jQuery UI dialog).
What I want is: if validation fails, just update the div that contained the form.
Where am I going wrong?
You could use an Ajax form in your partial instead of a normal form and use a OnSuccess callback in your AjaxOptions:
#using (Ajax.BeginForm("DialogTest", "pages", new AjaxOptions { UpdateTargetId = "dialogtest-view", OnSuccess = "success" }))
{
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x => x.Name)
<button type="submit">Test Me!</button>
}
and then modify your controller action respectively:
[HttpPost]
public ActionResult DialogTest(DialogUITestVM vm)
{
//arbitrary validation so I can test pass and fail)
if (vm.Name.Equals("Rob"))
{
//error!
vm.ErrorMessage = "There was an error you numpty. Sort it out.";
return PartialView(vm);
}
//hooray it passed - go back to index
return Json(new { redirectUrl = Url.Action("Index") });
}
and of course define the corresponding success callback in your javascript files:
function success(result) {
if (result.redirectUrl) {
window.location.href = result.redirectUrl;
}
}

pass input text from view to controller

i know there are a lot questions like this but i really cant get the explanations on the answers... here is my view...
<script type="text/javascript" language="javascript">
$(function () {
$(".datepicker").datepicker({ onSelect: function (dateText, inst) { },
altField: ".alternate"
});
});
</script>
#model IEnumerable<CormanReservation.Models.Reservation>
#{
ViewBag.Title = "Index";
}
<h5>
Select a date and see reservations...</h5>
<div>
<div class="datepicker">
</div>
<input name="dateInput" type="text" class="alternate" />
</div>
i want to get the value of the input text... there's already a value in my input text because the datepicker passes its value on it... what i cant do is to pass it to my controller... here is my controller:
private CormantReservationEntities db = new CormantReservationEntities();
public ActionResult Index(string dateInput )
{
DateTime date = Convert.ToDateTime(dateInput);
var reservations = db.Reservations.Where(r=>r.Date==date).Include(r => r.Employee).Include(r => r.Room).OrderByDescending(r => r.Date);
return View(reservations.ToList());
}
i am trying to list in my home page the reservations made during the date the user selected in my calender in my home page....
I don't see a Form tag in your View...or are you not showing the whole view? hard to tell.. but to post to your controller you should either send the value to the controller via an ajax call or post a model. In your case, your model is an IEnumerable<CormanReservation.Models.Reservation> and your input is a date selector and doesn't look like it is bound to your ViewModel. At what point are you posting the date back to the server? Do you have a form with submit button or do you have an ajax call that you aren't showing?
Here is an example of an Ajax request that could be called to pass in your date
$(function () {
$(".datepicker").onselect(function{
searchByDate();
})
});
});
function searchbyDate() {
var myDate = document.getElementById("myDatePicker");
$.ajax({
url: "/Home/Search/",
dataType: "json",
cache: false,
type: 'POST',
data: { dateInput: myDate.value },
success: function (result) {
if(result.Success) {
// do something with result.Data which is your list of returned records
}
}
});
}
Your datepicker control needs something to reference it by
<input id="myDatePicker" name="dateInput" type="text" class="alternate" />
Your action could then look something like this
private CormantReservationEntities db = new CormantReservationEntities();
public JsonResult Search(string dateInput) {
DateTime date = Convert.ToDateTime(dateInput);
var reservations = db.Reservations.Where(r=>r.Date==date).Include(r => r.Employee).Include(r => r.Room).OrderByDescending(r => r.Date);
return View(reservations.ToList());
return Json(new {Success = true, Data = reservations.ToList()}, JsonRequestBehaviour.AllowGet());
}
Update
If you want to make this a standard post where you post data and return a view then you need to make changes similar to this.
Create a ViewModel
public class ReservationSearchViewModel {
public List<Reservation> Reservations { get; set; }
public DateTime SelectedDate { get; set; }
}
Modify your controller actions to initially load the page and then be able to post data return the View back with data
public ActionResult Index() {
var model = new ReservationSearchViewModel();
model.reservations = new List<Reservation>();
return View(model);
}
[HttpPost]
public ActionResult Index(ReservationSearchViewModel model) {
if(ModelState.IsValid)
var reservations = db.Reservations.Where(r => r.Date = model.SelectedDate).Include(r => r.Employee).Include(r => r.Room).OrderByDescending(r => r.Date);
}
return View(model)
}
Modify your view so that you have a form to post to the Index HttpPost action
#model CormanReservation.Models.ReservationSearchViewModel
<h5>Select a date and see reservations...</h5>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.SelectedDate)
#Html.EditorFor(model => model.Reservations) // this may need to change to a table or grid to accomodate your data
<input type="submit" value="Search" />
}

How to update DIV in _Layout.cshtml with message after Ajax form is submitted?

Currently I have a Razor View like this:
TotalPaymentsByMonthYear.cshtml
#model MyApp.Web.ViewModels.MyViewModel
#using (#Ajax.BeginForm("TotalPaymentsByMonthYear",
new { reportName = "CreateTotalPaymentsByMonthYearChart" },
new AjaxOptions { UpdateTargetId = "chartimage"}))
{
<div class="report">
// MyViewModel fields and validation messages...
<input type="submit" value="Generate" />
</div>
}
<div id="chartimage">
#Html.Partial("ValidationSummary")
</div>
I then display a PartialView that has a #Html.ValidationSummary() in case of validation errors.
ReportController.cs
public PartialViewResult TotalPaymentsByMonthYear(MyViewModel model,
string reportName)
{
if (!ModelState.IsValid)
{
return PartialView("ValidationSummary", model);
}
model.ReportName = reportName;
return PartialView("Chart", model);
}
What I'd like to do is: instead of displaying validation errors within this PartialView, I'm looking for a way of sending this validation error message to a DIV element that I have defined within the _Layout.cshtml file.
_Layout.cshtml
<div id="message">
</div>
#RenderBody()
I'd like to fill the content of this DIV asynchronously. Is this possible? How can I do that?
Personally I would throw Ajax.* helpers away and do it like this:
#model MyApp.Web.ViewModels.MyViewModel
<div id="message"></div>
#using (Html.BeginForm("TotalPaymentsByMonthYear", new { reportName = "CreateTotalPaymentsByMonthYearChart" }))
{
...
}
<div id="chartimage">
#Html.Partial("ValidationSummary")
</div>
Then I would use a custom HTTP response header to indicate that an error occurred:
public ActionResult TotalPaymentsByMonthYear(
MyViewModel model,
string reportName
)
{
if (!ModelState.IsValid)
{
Response.AppendHeader("error", "true");
return PartialView("ValidationSummary", model);
}
model.ReportName = reportName;
return PartialView("Chart", model);
}
and finally in a separate javascript file I would unobtrusively AJAXify this form and in the success callback based on the presence of this custom HTTP header I would inject the result in one part or another:
$('form').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result, textStatus, jqXHR) {
var error = jqXHR.getResponseHeader('error');
if (error != null) {
$('#message').html(result);
} else {
$('#chartimage').html(result);
}
}
});
return false;
});

Resources