Controller post action is not being called from Jquery dialog using ajax - javascript

I am trying to call a post action method of my controller from jquery dialog using ajax but unable to do that. I am sure there is something very small but couldn't find anything even after checking my code several times. Can somebody help me to identify the problem?
Javascript in my view:
$('a.actionLinkAttentionButton').click(function () {
var url = $(this).attr('href');
var dialogDiv = $('<div id="success" style="display:none;background-color:#efeeef;"></div>').appendTo('body');
dialogDiv.load(url, {},
function (responseText, textStatus, XMLHttpRequest) {
dialogDiv.dialog({
title: "Resolve log issue",
close: function (event, ui) {
dialogDiv.remove();
},
width: 600,
show: { effect: "blind", duration: 400 },
hide: { effect: "blind", duration: 500 },
modal: { backdrop: 'static', keyboard: false },
buttons: {
"Save": function () {
$.ajax({
url: '#Url.Action("ResolveLogPost","Attendance")',
type: "POST",
async: false,
data: $('ResolveLogForm', $(this)).serialize()
});
},
"Close": function () {
$(this).dialog('close');
},
},
success: function (response) {
dialogDiv.dialog('close');
},
dialogClass: 'no-close success-dialog'
});
});
return false;
});
Post action in my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ResolveLogPost(BiometricLog model)
{
return PartialView();
}
Partial view:
#using (Html.BeginForm("ResolveLogPost", "Attendance", FormMethod.Post, new { enctype = "multipart/form-data", #id = "ResolveLogForm" }))
{
#Html.AntiForgeryToken()
<fieldset>
<legend>BiometricLogAction</legend>
<table class="ListTable" style="width:100%">
#Html.HiddenFor(model => model.EmployeeCode)
#Html.HiddenFor(model => model.date)
<tr><td colspan="2" style="background-color:#a6c100;color:white">#Html.Label("Log Details")</td></tr>
<tr>
<td style="width:200px;">#Html.LabelFor(model => model.LogDate)</td>
<td>#Html.DisplayFor(model => model.LogDate)</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.FinalInTime)</td>
<td>#Html.DisplayFor(model => model.FinalInTime)</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.FinalOutTime)</td>
<td>#Html.DisplayFor(model => model.FinalOutTime)</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.WorkingTime)</td>
<td>#Html.DisplayFor(model => model.WorkingTime)</td>
</tr>
</table>
<table>
<tr>
<td>#Html.CheckBoxFor(model => model.Accepted, new { #id = "chkprivate" })</td>
<td>#Html.LabelFor(model => model.Accepted)</td>
</tr>
</table>
<table>
<tr>
<td>#Html.LabelFor(model => model.ProofFileName)</td>
<td>#Html.TextBoxFor(m => m.ProofFileName, new { type="file", name = "file", #id="uploadfile"})</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.Remarks)</td>
<td>#Html.TextAreaFor(model => model.Remarks, new { style = "width:300px;" })</td>
</tr>
</table>
</fieldset>
}

Finally its working after couple of changes. Although, I am really not convinced that these were really the root of the cause but still happy that it worked out ;). I made following changes:
Partial view:
Added null route values to use different overload
Changed enctype to #enctype
#using (Html.BeginForm("ResolveLogPost", "Attendance", null, FormMethod.Post, new { #id = "form", #enctype = "multipart/form-data" }))

Related

Trigger update action from checkboxfor with AnitForgeryToken from ajax call

I have this view that brings in all data from an entity:
#model MyApplication.Application.TableModel<Entities.FPDrinkingWater>
#{
Layout = null;
var insertionMode = InsertionMode.Replace;
var fail = "displayFailure";
var target = "UnverifiedDrinkingWatersContent";
var ajax = "UnverifiedDrinkingWaterLogLoader";
var verify = Html.UserHasClaim("FP/DrinkingWater/VerifyDrinkingWater");
var action = "VerifyDrinkingWater";
string form = action + "Form";
}
<br />
#if (Model != null && Model.Alert != null && Model.Alert.Message != null)
{
#Html.Alert(Model.Alert)
}
#MyHelper.Loader(ajax)
<div class="form-group">
<table id="UnverifiedDrinkingWaterTable" class="table table-hover">
<thead>
<tr>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().SID)</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().Location)</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().Replicate)</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().CollectionDate)
</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().CollectionTime)
</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().Collectors)</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().Clorinated)</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().Comments)</th>
<th>#Html.LabelFor(m => m.Data.FirstOrDefault().Verified)</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Data.Count(); i++)
{
using (Ajax.BeginForm(actionName: action, routeValues: null,
htmlAttributes: new { id = form, #class = "form-horizontal" },
ajaxOptions:
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = target,
OnSuccess = "success",
LoadingElementId = ajax
}))
{
#Html.AntiForgeryToken()
<tr>
<td>#Html.DisplayFor(m => m.Data[i].SID)</td>
<td>#Html.DisplayFor(m => m.Data[i].Location)</td>
<td>#Html.DisplayFor(m => m.Data[i].Replicate)</td>
<td>#Html.DisplayFor(m => m.Data[i].CollectionDate)</td>
<td>#Html.DisplayFor(m => m.Data[i].CollectionTime)</td>
<td>#Html.DisplayFor(m => m.Data[i].Collectors)</td>
<td>#Html.DisplayFor(m => m.Data[i].Clorinated)</td>
<td>#Html.DisplayFor(m => m.Data[i].Comments)</td>
<td>#Html.CheckBoxFor(v => v.Data[i].Verified, new {
data_url = Url.Action("VerifyDrinkingWater", "DrinkingWater"), id =
"checkboxid" }) </td>
</tr>
}
}
</tbody>
</table>
</div>
<hr />
#if (Model.Data.Count > 0)
{
<script>
$(document).ready(function () {
makeDataTable('UnverifiedDrinkingWaterTable');
});
(function () {
$('#checkboxid').change(function () {
var data = {};
data[$(this).attr('name')] = $(this).is('checked');
$.ajax({
url: $(this).data('url'),
type: 'POST',
data: data,
success: function (result) {
}
});
});
});
</script>
}
What my goal is that I want to update 3 fields on the record displayed in the table by checking the "Verified" checkbox. I followed This Link to enable a click event on the checkboxfor. However, I receive an error claiming that The required anti-forgery form field __RequestVerificationToken is not present.. This is the only error I receive.
So my question is, how can I combine the AntiForgeryToken creation by the Ajax call AND pass the checked status of the checkbox at the same time and then pass it to the controller? Right now it's not even touching the controller and just giving me the AntiForgeryToken Error.
just get the token using
var token = $('input[name="__RequestVerificationToken"]').val();
and put it in data like this
var data = {
__RequestVerificationToken: token
}
please note that you render the AntiForgeryToken multiple times, so modify your code to have the ability to get the right AntiForgeryToke of each form

How to bind an ActionLink for DataTable row click event?

I have a server-side dataTable where when I click each row, I want it to show its Edit and Delete action links for the user to click on it and be directed to those pages.
#*<td>
#Html.ActionLink("Edit", "Edit", new { id = item.DepartmentID }) |
#Html.ActionLink("Details", "Details", new { id = item.DepartmentID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.DepartmentID })
</td>*#
When I search on their website, they use the editor for datatables. But I am not able to implement the actionlinks with the editor for many undefined errors.
Can someone please assist me to figure out how to make the on click event work?
This is the script for the dataTable
init: function () {
dt = $('#datatableServer').DataTable({
"serverSide": true,
"processing": true,
"ajax": {
"url":
"#Url.Action("DataHandler","Department")"
},
"columns": [
{ "data": "Name",
"searchable": true },
{
"data": "Budget", "render": $.fn.dataTable.render.number(',', '.', 0, '$'),
"searchable": false },
{ "data": "StartDate",
"searchable": false,
"type" : "datetime"},
{ "data": "Administrator",
"searchable": true }
],
............
departmentsList.init();});
$('#datatableServer tbody').on('click', 'tr', function () {
//editor.edit(this, 'Edit record', {
//"label": "Update",
//"fn": function () {
//editor.submit()
//}
//})
console.log('clicked');
console.log(dt.row(this).data().DT_RowId); // DT_RowId is each row's Id
});
I have the DT_RowId getting the id for each table row for my data.
var data = query.Select(a => new DepartmentData
{
DT_RowId = a.DepartmentID.ToString(),
Name = a.Name,
..........
}).ToList();
First thing first
When I have them in my , my dataTable does not show.
The number in your column should match the number of you have. From what i can see, you specified 4 columns
"columns": [
{ "data": "Name", "searchable": true },
{ "data": "Budget", "render": $.fn.dataTable.render.number(',', '.', 0, '$'), "searchable": false },
{ "data": "StartDate", "searchable": false, "type" : "datetime"},
{ "data": "Administrator", "searchable": true }
]
but you also have an action column where your Actionlinks sit. So i suggest adding an addtional data column
{ data: "Action" }
Also make sure your have five header columns too
<thead>
<tr>
<th>Name</th>
<th>Budget</th>
<th>StartDate</th>
<th>Administrator</th>
<th>Action</th>
</tr>
</thead>
Now next thing, i haven't acutally tried to use their editor before, the way i do it is to use my own modal, any modal will do, bootstrap modal is an good option.
For example, you specify a modal in your dataTable view, I place it at the end of the page
<div id="companyModal" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myCompanyModalLabel"></h3>
</div>
#{Html.RenderAction("CompanyModal", "CV");}
</div>
</div>
</div>
I like to use ViewModal in my modal, so i do RenderAction to get all the goodies from ViewModal validation. Of course, you can do #Html.Partial() too instead of RenderAction, RenderAction only if you want to get some value for the ViewModel before returning it.
[ChildActionOnly]
public ActionResult CompanyModal()
{
var model = new CompanyViewModel();
return PartialView("~/Views/Dashboard/CV/_CompanyModal.cshtml", model);
}
The partial view:
#model XXX.CompanyViewModel
<form id="companyForm" style="margin: 0px;">
#Html.AntiForgeryToken()
<div class="modal-body">
#Html.HiddenFor(m => m.CompanyId)
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.CompanyName)
#Html.TextBoxFor(m => m.CompanyName, new { #class = "input-block-level" })
#Html.ValidationMessageFor(m => m.CompanyName)
</div>
<div class="span6">
#Html.LabelFor(m => m.JobTitle)
#Html.TextBoxFor(m => m.JobTitle, new { #class = "input-block-level" })
#Html.ValidationMessageFor(m => m.JobTitle)
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">Close</button>
<button id="companyEditSubmitBtn" name="edit" class="ladda-button btn btn-primary" data-style="zoom-in" data-spinner-size="25" type="button">Save</button>
</div>
</form>
Now on to the script:
//init dataTable
var cTable = $("#company-table").dataTable();
//open work experience edit modal
$("#company-table").on("click", ".companyEditBtn", function () {
//do
$("#myCompanyModalLabel").text("Edit Work Experience");
//get current position
position = cTable.fnGetPosition((this).closest("tr"));
data = cTable.fnGetData(position);
//set values to modal
$("#companyModal #CompanyId").val(data[0]);
$("#companyModal #CompanyName").val(data[1]);
$("#companyModal #JobTitle").val(data[2]);
//open modal
$("#companyModal").modal("show");
});
After you open the modal, post the value to your server to save using ajax:
//work experience edit
$("#companyEditSubmitBtn").click(function () {
//get the form
var form = $("#companyForm");
//validate form
if (!form.valid()) {
return;
}
//serialize the form
serializedForm = form.serialize();
//ajax post
$.ajax({
url: "#Url.Action("CompanyEdit", "CV")",
type: "POST",
data: serializedForm,
beforeSend: function () {
l.ladda("start");
},
success: function (result) {
if (result.success) {
//update row of table
cTable.fnUpdate([
result.id,
result.name,
result.title,
"<button class='companyEditBtn btn' title='Edit Work Experience'><i class='icon-pencil'></i></button>" + " " + "<button class='companyDeleteBtn btn' title='Delete Work Experience'><i class='icon-trash'></i></button>"
], position);
toastrSuccess(result.message);
} else {
toastrError(result.message);
}
},
error: function (jqXHR, textStatus, errorThrown) {
toastrError(textStatus);
},
complete: function () {
//stop ladda button loading
l.ladda("stop");
//hide modal
$(".modal").modal("hide");
}
});
});
And your edit controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CompanyEdit(CompanyViewModel model)
{
if (ModelState.IsValid)
{
var company = repository.FindCompany(model.CompanyId);
if (company != null)
{
try
{
//map automapper
model.Description = model.Description.Replace(Environment.NewLine, "<br />");
mapper.Map(model, company);
repository.EditCompany(company);
return Json(new { success = true, message = "Wokr Experience Edited", id = company.CompanyId, title = company.JobTitle, name = company.CompanyName });
}
catch (Exception ex)
{
return Json(new { success = false, message = string.Format("{0}", ex) });
}
}
else
{
return Json(new { success = false, message = "Work Experience not found" });
}
}
return Json(new { success = false, message = "Modal state is not valid" });
}
Another thing to mention, instead of using a foreach loop, use DisplayTemplate,
where the Companies property is an IEnumerable which will
automatically do the looping and render the CompanyViewModel.cshtml
display template for each item of this collection.
Source here
<table id="company-table" class="table table-striped table-bordered table-hover dataTables" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Company</th>
<th>Title</th>
<th>Action</th>
</tr>
</thead>
<tbody>
#Html.DisplayFor(m => m.Companies)
</tbody>
<tfoot>
<tr>
<th>ID</th>
<th>Company</th>
<th>Title</th>
<th>Action</th>
</tr>
</tfoot>
</table>
And specify your display template inside Shared -> DisplayTemplates -> CompanyViewModel.cshtml
#model Taw.WebUI.Models.CompanyViewModel
<tr>
<td>
#Html.DisplayFor(m => m.CompanyId)
</td>
<td>
#Html.DisplayFor(m => m.CompanyName)
</td>
<td>
#Html.DisplayFor(m => m.JobTitle)
</td>
<td>
<button class="companyEditBtn btn" title="Edit Work Experience"><i class="icon-pencil"></i></button>
<button class='companyDeleteBtn btn' title="Delete Work Experience"><i class="icon-trash"></i></button>
</td>
</tr>

ASP.NET MVC jQuery Ajax - close and refresh parent table from modal dialog

Very new to both MVC and jQuery, so I'm not sure how to get this to work. I've cobbled together a (somewhat) working modal dialog form with an ajax postback. Been searching for two days and not finding great MVC+jQuery examples out there. The data gets inserted as expected, it's just the UI I'm having a hard time with.
I've got two views: Index and Create. Index lists all records inside a plain table, looping the results with Razor. Create is the insert form which I'm loading into a modal dialog:
#model IEnumerable<MyProject.Models.StockIndex>
#{
ViewBag.Title = "Admin: Stock Index Home";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>View Stock Indices</h2>
<p>
Create New
<div id="createStockIndexForm"></div>
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
#section Scripts {
<script>
$('#createLink').on('click', function () {
$("#createStockIndexForm").dialog({
autoOpen: true,
position: { my: "center", at: "top+350", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true,
open: function () {
$(this).load('#Url.Action("Create", "AdminStockIndex")');
}
});
return false;
});
</script>
}
Controller actions:
public ActionResult Create()
{
var model = new StockIndexEditViewModel()
{
StockIndices = GetIndices()
};
return View(model);
}
[HttpPost]
public ActionResult Create(StockIndexEditViewModel model)
{
if (ModelState.IsValid)
{
...
}
return PartialView(model);
}
The "Create" form that was loaded into the dialog (above):
#model MyProject.Models.StockIndexEditViewModel
#{
ViewBag.Title = "CreatePartial";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>CreatePartial</h2>
#using (Html.BeginForm("Create", "AdminStockIndex", FormMethod.Post, new { id = "createForm" }))
{
#Html.AntiForgeryToken()
<div id="result"></div>
<div class="form-horizontal">
<h4>StockIndexEditViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SelectedParentId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedParentId, Model.StockIndices, "- Select One -", new { #class = "form-control" })
</div>
</div>
<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" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" id="createButton" class="btn btn-default" />
</div>
</div>
</div>
}
#section Scripts {
<script>
$("#createForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: this.action,
type: this.method,
async: true,
data: $(this).serialize(),
success: function (data) {
if (data) {
var createForm = $("#createStockIndexForm").dialog();
createForm.dialog("close");
}
else {
$("#result").append("Something went fail.");
}
}
});
});
</script>
}
The modal dialog always goes blank, rather than closing. In testing in Firefox using Firebug, I occasionally see this error, but not every time:
InvalidAccessError: A parameter or an operation is not supported by
the underlying object
Upon searching, I see that it's a CORS problem where FF is enforcing the rules and other browsers may not be. It's frustrating that the error doesn't always occur - appears to be random. Behaves the same in Chrome but doesn't ever throw an error in the JS console.
Firstly, how would I pull this off? Secondly, is there a way to refresh the table on the parent page via ajax, without plugins?
UPDATE:
With Eckert's help, I've made a little progress.
I'm trying to avoid the MVC Ajax helpers and sticking with a "pure" jQuery approach. I replaced the list of records on Index with a div, which contains a PartialView:
<div id="stockIndices">
#Html.Partial("_StockIndices", Model)
</div>
I've got the underlying table refresh working by reloading the div with the close property of the jQuery dialog:
$('#createLink').on('click', function () {
$("#createStockIndexForm").dialog({
autoOpen: true,
position: { my: "center", at: "top+400", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true,
open: function () {
$(this).load('#Url.Action("Create", "AdminStockIndex")');
},
close: function () {
$("#stockIndices").load('#Url.Action("GetStockIndices", "AdminStockIndex")');
}
});
return false;
});
Upon closing the modal dialog manually, the div reloads just how I wanted. Great! Now if I could get the dialog to close when the form posts, I'd be set. This does not work:
$("#createStockIndexForm").dialog("close");
Firebug reports the error:
Error: cannot call methods on dialog prior to initialization;
attempted to call method 'close'
The modal dialog always goes blank, rather than closing.
It's probably behaving improperly because you're creating a variable based on the dialog method of an object, and not the object itself. Try this instead:
if (data) {
$("#createStockIndexForm").dialog("close");
}
Secondly, is there a way to refresh the table on the parent page via
ajax, without plugins?
There sure is, but it may require you to change some things, including the code to close your dialog. Here's how I would handle it:
1) your table of records should be a partial view within the main Index view. The reason for this is because when we submit your form, we'll use the ajax-option "InsertionMode" coupled with the target-id to replace your old records table with the updated one from the form.
2) rather than handling your dialog-close code in the on-submit method, we'll instead use the ajax-option "OnSuccess" to do that (and also "OnFailure" to handle your 'Something went fail' error).
So here's your new Index view:
#model IEnumerable<MyProject.Models.StockIndex>
#{
ViewBag.Title = "Admin: Stock Index Home";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>View Stock Indices</h2>
<p>
Create New
<div id="createStockIndexForm"></div>
</p>
<div id="recordsDiv">
#Html.Partial("RecordsPartial", model)
</div>
// all your script stuff can still go at the end
Most of the Index view is unchanged, with the exception that we're now using a that contains a partial view. This partial view will include the table of records, coded here:
Create a new partial view named "RecordsPartial":
#model IEnumerable<MyProject.Models.StockIndex>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
Now your "Create" view will be updated to use the mvc-ajax helper rather than use all that additional javascript code:
#model MyProject.Models.StockIndexEditViewModel
#{
ViewBag.Title = "CreatePartial";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>CreatePartial</h2>
#using (Ajax.BeginForm("CreateRecord", "AdminStockIndex", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "recordsDiv", OnSuccess = "postSuccess", OnFailure = "postFailed" })
{
#Html.AntiForgeryToken()
<div id="result"></div>
<div class="form-horizontal">
<h4>StockIndexEditViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
/* form fields here */
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" id="createButton" class="btn btn-default" />
</div>
</div>
</div>
}
We changed your form to be ajax-post, and we've added ajax-options to handle what happens after your form has posted. The data that returns after the post (our updated records partial) will replace the current contents of the target-id "recordsDiv". OnSuccess function "postSuccess" will handle closing the dialog box. OnFailure function "postFailed" will report that something bad happened. The last thing to mention is that we changed the post-action from "Create" to "CreateRecord". I prefer to have unique action names when working with ajax-data returns. It's just a cleaner approach.
Before we define the new "CreateRecord" post-action, we need to implement our success and failure functions. Just create them at the bottom of your script-section block in the main "Index" view:
#section Scripts {
<script>
// ... other stuff that was already here ...
function postSuccess() {
$("#createStockIndexForm").dialog("close");
}
function postFailed() {
alert("Failed to post"); // you can define your own error
}
</script>
}
Lastly, we create the "CreateRecord" post-action, which will process our form and return an updated "records partial" view:
[HttpPost]
public ActionResult CreateRecord(StockIndexEditViewModel model)
{
if (ModelState.IsValid)
{
... create record here ...
}
var records = db.Indexes.ToList(); // or whatever your table name is
return PartialView("RecordsPartial", records);
}
This is repeating some of your existing "Create" action. We simply process our post data, then we get a new updated list of records, and lastly we return that list of records back to our "RecordsPartial" view, which will be inserted into our "recordsDiv" as we specified in our ajax-options.
Very clean and simple solution. If you have questions, feel free to ask.
In your main Index view, rather than calling for your Create view to be inserted into your view, have it initially present at view load, and hide it within a div:
<div id="createStockIndexForm">
#Html.Action("Create", "AdminStockIndex")
</div>
In your Index script section, we're creating the dialog OUTSIDE your click event. We're also turning the "autoOpen" value to false so the div hides at view load.
Index script section:
#section Scripts {
<script>
$("#createStockIndexForm").dialog({
autoOpen: false,
position: { my: "center", at: "top+350", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true
});
$('#createLink').on('click', function () {
$("#createStockIndexForm").show();
});
</script>
}
Also, when you use the PreventDefault() command, it appears to be interfering with your modal-close command (after some of my own testing). I suggest that you change your form's "Create" button to type="button" rather than "submit", and then use the ID of the button to handle your ajax-post method.
<input type="button" id="createButton" value="Create" class="btn btn-default" />
Add this to your main Index script section at the bottom:
$("#createButton").on("click", function () {
$.ajax({
url: this.action,
type: this.method,
async: true,
data: $(this).serialize(),
success: function (data) {
if (data) {
$("#createStockIndexForm").dialog("close");
}
else {
$("#result").append("Something went fail.");
}
}
});
});
Make sure to direct your "close" command at the dialog object itself.
Here's a fiddle I created that shows you the gist of how it should be:
http://jsfiddle.net/ch5aLezg/
So to recap on the script stuff, you should have NO SCRIPTS in your "Create" partial. All the scripts go into the main Index view, as I've detailed in this answer.
I found a way to get everything I wanted, while retaining the "pure" jQuery Ajax approach, instead of resorting to the MVC Ajax helpers, suggested by Eckert. His suggestions lead me to the solution, however. Big thanks!
I created a PartialView in the controller:
public ActionResult GetStockIndices()
{
_service = new StockIndexService();
var data = _service.GetAll();
return PartialView("_StockIndices", data);
}
...and its view:
#model IEnumerable<MyApp.Models.StockIndex>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
Then I changed the modal dialog script to load the partial view when closed. Here's the entire Index view, for posterity:
#model IEnumerable<MyApp.Models.StockIndex>
#{
ViewBag.Title = "Admin: Stock Index Home";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>View Stock Indices</h2>
<p>
Create New
<div id="createStockIndexForm"></div>
</p>
<div id="stockIndices">
#Html.Partial("_StockIndices", Model)
</div>
#section Scripts {
<script>
var _dialog;
$('#createLink').on('click', function () {
_dialog = $("#createStockIndexForm").dialog({
autoOpen: true,
position: { my: "center", at: "top+400", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true,
open: function () {
$(this).load('#Url.Action("Create", "AdminStockIndex")');
},
close: function () {
$("#stockIndices").load('#Url.Action("GetStockIndices", "AdminStockIndex")');
}
});
return false;
});
</script>
}
Notice the global "_dialog" variable. This gave me access to the dialog from the Create form, so it could be closed:
<script>
$("#createForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: this.action,
type: this.method,
async: true,
data: $(this).serialize(),
success: function (data) {
if (data) {
if (_dialog) {
_dialog.dialog("close");
}
}
else {
$("#result").append("Error! Record could not be added.");
}
},
error: function (error) {
console.error(error);
}
});
});
</script>

JQuery Postback triggered by nested DropDownListFor returning only the first row

I have a list of objects, which are shown using Editor Template. When a task's dropdownlistfor changes, the JSON postback always returns the values of the first task, regardless of which row's dropdownlist is updated.
I see many of the same problem, but not when the trigger is in a nested element inside a editor template. I'm lost as to what i'm missing out here. Help?
#model NSCEngineering.Models.task
#Html.HiddenFor(model => model.task_id, new { #id = "taskID" })
#Html.HiddenFor(model => model.task_name, new { #id = "TaskName" })
#Html.HiddenFor(model => model.task_desc, new { #id = "TaskDesc" })
#Html.HiddenFor(model => model.completion_date, new { #id = "CompletionDate" })
<table style="width:80%">
<tr style="width:60%">
<th colspan="3">#Html.DisplayFor(model => model.task_name)</th>
<th align="left">
#Html.DropDownListFor(model => model.task_state_id,
new SelectList((System.Collections.IEnumerable)ViewData["TaskStates"], "task_state_id", "state"),
new { #class = "ddlState"})
</th>
<td>
#Html.EditorFor(model => model.notes, new { #Id = "Notes" })
#Html.ValidationMessageFor(model => model.notes, "", new { #class = "text-danger" })
</td>
</tr>
<tr>
<td colspan="3">#Html.DisplayFor(model => model.task_desc)</td>
<td>#Html.Label("Completed by ")#Html.DisplayFor(model => model.user_completed)#Html.Label(", ")#Html.DisplayFor(model => model.completion_date)</td>
</tr>
</table>
<div class="tasks">
#Html.EditorFor(m => m.Tasks, new { #class = "Tasks"})
</div>
</div>
}
<p>
#Html.ActionLink("Back to List", "Index")
</p>
#section Scripts{
<script type="text/javascript">
$(this.document).ready(function () {
$('.ddlState').change(function ()
{
var task = {
"task_id": $("#taskID").val(),
"task_state_id": $('#ddlState').val(),
"task_name": $('#TaskName').val(),
"task_desc": $('#TaskDesc').val(),
"notes": $('#Notes').val(),
}
var url = '#Url.Action("UpdateTaskState")';
$.ajax({
type: 'POST',
url: url,
data: JSON.stringify(task),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function (result) {
alert("success");
$("#message").html("Success");
location.reload(true);
},
error: function (xhr, status, error) {
// Show the error
$('#message').html(xhr.responseText);
}
})
})
});
</script>
}
your code is : new { #class = "ddlState"}) so you shoud use "task_state_id": $('.ddlState').val(), with "." not "task_state_id": $('#ddlState').val(), with "#" because it's class not id

form.valid() returns true when form is invalid

I have a form consisting of listboxes and radiobuttons. I submit the form through ajax which sends a post. The actionlink in my controller returns a partial view. If the form is invalid I don't want to clear the selected items in the listboxes, but if the form is valid then everything should be unselected. For some reason form.valid() is always true. The error message shows perfectly so I think there is nothing wrong with the validation. Here are some snippets of my code:
View: Form
<div id="researchContainer">
#using (Html.BeginForm("ResearchCompetence", "Planning", FormMethod.Post, new { #id = "researchForm" }))
{
#Html.HiddenFor(x => x.Input.PlanningId)
#Html.ValidationSummary()
<h1>#Html.Label("OverviewResearchCompetences", Labels.OverviewResearchCompetences)</h1>
<table class="table-output">
<thead>
<tr>
<td>#Html.Label("Name", Labels.Name)</td>
<td>#Html.Label("Level", Labels.Level)</td>
<td class="text-align-right"></td>
</tr>
</thead>
<tbody>
#if (Model.CurrentResearchCompetences.Count == 0)
{
<tr>
<td>#Html.Label("NoCurrentCompetencesRes", Labels.NoCurrentCompetencesRes)</td>
</tr>
}
else
{
foreach (var item in Model.CurrentResearchCompetences)
{
<tr>
<td>#item.Vtc</td>
#{
var scoreOutput = new ILVO.Services.EmployeeManagement.ScoreOutput(item.VtcLevel);
}
<td>
#scoreOutput.ToString()
#if (!scoreOutput.ToString().Equals("Gevorderd"))
{
<span class="arrow-up">
#Html.ImageLink("IncreaseLevel", "Planning", new { id = #item.Id, planningId = Model.Input.PlanningId, actionMethod = "JobCompetence" },
"/Content/arrow-icon.png", Labels.IncreaseLevel, "")
</span>
}
#if (!scoreOutput.ToString().Equals("Basis"))
{
#Html.ImageLink("DecreaseLevel", "Planning", new { id = #item.Id, planningId = Model.Input.PlanningId, actionMethod = "JobCompetence" },
"/Content/arrow-icon.png", Labels.DecreaseLevel, "")
}
</td>
<td class="text-align-right deleteLink">
#Html.ImageLink("DeleteVtc", "Planning", new { id = #item.Id, planningId = #Model.Input.PlanningId, actionMethod = "JobCompetence" },
"/Content/delete-icon.png", Labels.Delete, "")
</td>
</tr>
}
}
</tbody>
</table>
<br />
<h1>#Html.Label("AddResearchCompetence", Labels.AddResearchCompetence)</h1>
<table>
<thead>
<tr>
<td>#Html.Label("Disciplines", Labels.Disciplines)</td>
<td>#Html.Label("Organisms", Labels.Organisms) <a id="clear-organisme" class="button-clear-vtc">Clear</a></td>
<td>#Html.Label("Techniques", Labels.Techniques) <a id="clear-technique" class="button-clear-vtc">Clear</a></td>
</tr>
</thead>
<tbody>
<tr>
<td>
#Html.ListBoxFor(x => x.Input.SelectedDiscipline, Model.Disciplines, new { #class = "vtc-listbox-height", #size = 1 })
</td>
<td>
#Html.ListBoxFor(x => x.Input.SelectedOrganism, Model.Organisms, new { #class = "vtc-listbox-height" })
</td>
<td>
#Html.ListBoxFor(x => x.Input.SelectedTechnique, Model.Techniques, new { #class = "vtc-listbox-height" })
</td>
</tr>
</tbody>
</table>
<div id="after-selection-ok">
<h1>#Html.Label("ChooseLevel", Labels.ChooseLevel)</h1>
#Html.RadioButtonFor(x => x.Input.Score, 0, new { #id = "radio1" }) #Html.Label("radio1", Labels.Basic, new { #class = "radio-label" })<br />
#Html.RadioButtonFor(x => x.Input.Score, 1, new { #id = "radio2" }) #Html.Label("radio2", Labels.Intermediate, new { #class = "radio-label" })<br />
#Html.RadioButtonFor(x => x.Input.Score, 2, new { #id = "radio3" }) #Html.Label("radio3", Labels.Expert, new { #class = "radio-label" })<br />
<br />
<input class="button" type="submit" name="AddResearchCompetence" value="#Labels.Submit" />
#Html.ActionLink(Labels.GoBack, "Detail", new { id = #Model.Input.PlanningId }, new { #class = "button margin-left" })
</div>
}
Controller : Actionlink
[HttpPost]
public ActionResult ResearchCompetence(ResearchCompetenceInputModel input)
{
// do some validations
// if validations are ok = add competence else return view
return PartialView("ResearchCompetence", new ResearchCompetenceModel(researchCompetences, currentResearchCompetences, input.PlanningId));
}
My Ajax Call
$("#researchForm").submit(function () {
$.ajax({
type: 'POST',
url: '#Url.Action("ResearchCompetence")',
data: $("#researchForm").serialize(),
success: function (data) {
$("#researchContainer").html(data);
$("#researchForm").validate();
if ($("#researchForm").valid()) { // always true
$("#Input_SelectedOrganism").val('');
$("#Input_SelectedTechnique").val('');
$("input[name='Input.Score']").attr('checked', false);
}
}
});
return false;
});
I also have these scripts loaded:
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/Scripts/jquery-1.10.2.js")
#Scripts.Render("~/Scripts/jquery.validate.min.js")
#Scripts.Render("~/Scripts/jquery.validate.unobtrusive.js")
#Scripts.Render("~/Scripts/jquery-ui-1.10.3.js")
#Scripts.Render("~/Scripts/tinymce/tinymce.min.js")
#Scripts.Render("~/Scripts/ckeditor/ckeditor.js")
#Scripts.Render("~/Scripts/site.js")
#Scripts.Render("~/Scripts/jquery.ui.datepicker-nl-BE.js")

Categories

Resources