How to bind an ActionLink for DataTable row click event? - javascript

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>

Related

Dropdownlist as selected in ASP.NET MVC using ViewBag?

I want to display dropdownlist in Bootstrap Modal When I click on button add it displays this error ( Server Error in '/' Application. There is no ViewData item of type 'IEnumerable' that has the key 'DepartmentId'.)
it returns the NULL value someone help me to solve this problem
In Controller:
Database1Entities db = new Database1Entities();
public ActionResult Index()
{
List<Department> DeptList = db.Departments.ToList();
ViewBag.ListOfDepartment = new SelectList(DeptList, "DepartmentId", "DepartmentName");
return View();
}
public ActionResult GetStudent()
{
List<StudentViewModel> data = db.Students.Select(x => new StudentViewModel
{
StudentId =x.StudentId,
FirstName = x.FirstName,
LastName = x.LastName,
DepartmentName = x.Department.DepartmentName,
}).ToList();
return Json(new { data = data }, JsonRequestBehavior.AllowGet);
}
public ActionResult GetStudentPartial(int? id)
{
var student = db.Students.Find(id) ?? new Student();
return PartialView("_CreateOrUpdateStudentPartial", student);
}
public ActionResult CreateOrUpdateStudent(Student student)
{
if (ModelState.IsValid)
{
if (student.StudentId > 0)
{
db.Entry(student).State = System.Data.Entity.EntityState.Modified;
}
else
{
db.Students.Add(student);
}
db.SaveChanges();
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json(false, JsonRequestBehavior.AllowGet);
}
public ActionResult Delete(int id)
{
try
{
var student = db.Students.Find(id);
db.Students.Remove(student);
db.SaveChanges();
return Json(true, JsonRequestBehavior.AllowGet);
}
catch (Exception)
{
return Json(false, JsonRequestBehavior.AllowGet);
}
}
Partial View:
#model Example1.Models.Student
<form name="studentForm">
<div class="modal-header">
<h5 class="modal-title">
Modal title
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</h5>
</div>
<div class="modal-body">
#Html.HiddenFor(x => x.StudentId)
<div class="row">
<div class="col-md-12">
<div class="col-md-6">
<div class="form-group">
<label>First Name</label>
#Html.EditorFor(x => x.FirstName, new { htmlAttributes = new { #class = "form-control", #placeholder = "First Name*", Required = true } })
#Html.ValidationMessageFor(x => x.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Last Name</label>
#Html.EditorFor(x => x.LastName, new { htmlAttributes = new { #class = "form-control", #placeholder = "Last Name*", Required = true } })
#Html.ValidationMessageFor(x => x.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Department Name</label>
#Html.DropDownListFor(m => m.DepartmentId, ViewBag.ListOfDepartment as SelectList, "--Select Dept--", new { #id = "DropDwn", #class = "form-control" })
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm pull-left" data-dismiss="modal">Cancel</button>
<button type="button" onclick="createOrUpdate()" class="btn btn-success pull-left">Save</button>
</div>
</form>
View:
<div style="width:90%; margin:0 auto" class="tablecontainer">
<p><button type="button" class="btn btn-sm btn-success" onclick="getStudent()">Add New Student</button></p>
<table id="myDatatable" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>FirstName</th>
<th>LastName</th>
<th>DepartmentName</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
</table>
</div>
<div class="modal fade" role="dialog" id="studentModal" aria-labelledby="studentModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" id="studentmodalBody">
</div>
</div>
</div>
Script:
<script>
var datatable;
$(document).ready(function () {
datatable = $('#myDatatable').DataTable({
"ajax": {
"url": '/home/GetStudent',
"type": "get",
"datatype": "json"
},
"columns": [
{ "data": "FirstName", "autoWidth": true },
{ "data": "LastName", "autoWidth": true },
{
"data": "DepartmentName", "render": function (data) {
return data;
}
},
{
"data": "StudentId", "width": "50px", "render": function (data) {
return '<button class="btn btn-success" onclick="getStudent(' + data + ')">Edit</button>';
}
},
{
"data": "StudentId", "width": "50px", "render": function (data) {
return '<button class="btn btn-danger" onclick="Delete(' + data + ')">Delete</button>';
}
},
]
})
})
function getStudent(id) {
$.get("/Home/GetStudentPartial", { id: id }, function (res) {
$("#studentmodalBody").html(res);
$("#studentModal").modal('show');
})
}
function createOrUpdate() {
var modal = $("#studentModal");
var form = $('form[name= "studentForm"]');
form.validate();
if (!form.valid()) {
return;
} else {
var data = form.serialize();
$.post("/home/CreateOrUpdateStudent", data, function (res) {
if (res) {
modal.modal('hide');
datatable.ajax.reload();
}
})
}
}
function Delete(id) {
if (confirm("Are you sure ? ") == true) {
$.get("/Home/Delete", { id: id }, function (res) {
if (res) {
datatable().ajax.reload();
}
})
}
}
</script>
If your DeptList is not null, try change code below :
#Html.DropDownListFor(m => m.DepartmentId, ViewBag.ListOfDepartment as SelectList, "--Select Dept--", new { #id = "DropDwn", #class = "form-control" })
to:
#Html.DropDownListFor(m => m.DepartmentId, ViewBag.ListOfDepartment as IEnumerable<SelectListItem>, "--Select Dept--", new { #id = "DropDwn", #class = "form-control" })
EDIT: I've just realized that you are filling your ViewBag in the Index() method but your are calling it in _CreateOrUpdateStudentPartial and you can't do that. ViewBag is not tranffering data between views.
public ActionResult GetStudentPartial(int? id)
{
var student = db.Students.Find(id) ?? new Student();
// you need to add here
List<Department> DeptList = db.Departments.ToList();
ViewBag.ListOfDepartment = new SelectList(DeptList,"DepartmentId","DepartmentName");
return PartialView("_CreateOrUpdateStudentPartial", student);
}

MVC Null Parameter at Controller

I am calling an Action (Edit or Delete) on a Controller from an <a> tag generated in Javascript. I am trying to pass a parameter ('abc123') but the parameter is null when it gets to the controller.
I have tried various ways to create the <a> tag but I always end up with a null parameter showing up at the controller.
End result of generated <a> tags:
<a class="popup" href="./SiteAdmin/Edit?=abc123">Edit</a>
<a class="popup" href="./SiteAdmin/Delete/abc123">Delete</a>
HTML:
<div style="width: 90%; margin: 0, auto" class="tableContainer">
<a class="popup" href="./SiteAdmin/Save/0" style="margin-bottom:20px; margin-top: 20px;">Add New User</a>
<table id="tblUserAdmin" class="table admin-table">
<thead>
<tr>
<th>USERID</th>
<th>Name</th>
<th>role_id</th>
<th>Role</th>
<th>Modified By</th>
<th>Modified Date</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
</table>
</div>
Javascript:
var oTable = $('#tblUserAdmin').DataTable({
"ajax": {
"url": './SiteAdmin/getUsers',
"type": "GET",
"datatype": "json"
},
"columns": [
{ "data": "userid" },
{ "data": "name" },
{ "data": "role_id" },
{ "data": "role_nm" },
{ "data": "modifiedBy" },
{ "data": "modifiedDT" },
{
"data": "userid", "render": function (data) {
return '<a class="popup" href= "./SiteAdmin/Edit?=' + data + '">Edit</a>';
}
},
{
"data": "userid", "render": function (data) {
return '<a class="popup" href= "./SiteAdmin/Delete/' + data + '">Delete</a>';
}
}
Controller:
public class SiteAdminController : Controller
{
private Customer db = new Customer();
public ActionResult Index()
{
return View("~/Views/SiteAdmin/Index.cshtml");
}
[HttpGet]
public ActionResult Edit(string id)
{
// put a break point here to see id was null
var usr = new sec_users();
usr = db.sec_users.Where(a => a.userid == id).FirstOrDefault();
return View(usr);
}
}

jQuery Datatable cell not updating

I have a table that I am using jQuery Datatables with.
Picture:
Scenario:
As you can see in the picture, there is a Delete link. When that link is clicked, a modal pop-up will show asking the user if they really want to delete that item. If yes, delete.. if no.. cancel out of the modal.
What I want:
When a user decides to delete an item and confirms it.. I would like to change the status of that item to "Deleted", via ajax. I am able to change the value, but that value does not show in the table. I have researched this for a couple of days now, but nothing seems to work.
My Code
<table id="Item-Table" class="table table-bordered">
<thead>
<tr>
<th class="text-center">
#Html.DisplayNameFor(model => model.AssetTag)
</th>
<th class="text-center">
#Html.DisplayNameFor(model => model.codeMakeModel.MakeModel)
</th>
<th class="text-center">
#Html.DisplayNameFor(model => model.codeStatu.Status)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr class="text-center">
<td>
#Html.ActionLink(item.AssetTag, "Edit", new { id = item.Id })
</td>
<td>
#Html.DisplayFor(modelItem => item.codeMakeModel.MakeModel)
</td>
<td class="changeStatus">
#Html.DisplayFor(modelItem => item.codeStatu.Status)
</td>
<td>
Delete
</td>
</tr>
}
</tbody>
</table>
#section scripts{
<script>
var settings = {};
settings.baseUri = '#Request.ApplicationPath';
var infoGetUrl = "";
if (settings.baseUri === "/projectonservername") {
infoGetUrl = settings.baseUri + "/api/itemsapi/";
} else {
infoGetUrl = settings.baseUri + "api/itemsapi/";
}
$(document).ready(function () {
var itemsTable = $("#Item-Table").DataTable({
"aoColumnDefs": [
{ "bSortable": false, "aTargets": [3] },
{ "bSearchable": false, "aTargets": [3] }
]
});
$("#Item-Table").on("click",
".js-item-delete",
function() {
var link = $(this);
bootbox.confirm({
title: "Delete Item?",
message: "Are you sure you want to delete this item?",
buttons: {
cancel: {
label: '<i class="fa fa-times"></i> Cancel'
},
confirm: {
label: '<i class="fa fa-check"></i> Confirm'
}
},
callback: function(result) {
if (result) {
toastr.options = {
timeOut: 5000
}
$.ajax({
url: infoGetUrl + link.data("item-id"),
method: "DELETE",
success: function (result) {
//itemsTable.cell(itemsTable.row(this), 2).data("Deleted");
//itemsTable.draw();
//itemsTable.reload();
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data("Deleted").draw();
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
toastr.success("Item successfully deleted");
},
error: function(jqXHR, textStatus, errorThrown) {
var status = capitalizeFirstLetter(textStatus);
console.log(jqXHR);
toastr.error(status + " - " + errorThrown, "Sorry, something went wrong.");
}
});
}
}
});
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
})
</script>
}
What I am Receiving
In the above code, specifically these lines:
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data("Deleted").draw();
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
I am logging the value of the cell before I update that cell value, then changing the cell value, then logging the new/updated cell value.
Here is what I am receiving in the console:
But the table is not updating, or rather.. redrawing itself to show deleted.. the only way for it show deleted is to refresh the page which defeats the purpose of ajax..
How do I get the table to update the cell value without a page refresh?
Any help is appreciated.
I was able to answer this myself with some help of DavidDomain in the comments.
He suggested that I could possibly be selecting an incorrect row. So that gave me the idea to get the row at the start of this by adding:
$("#Item-Table").on("click",
".js-item-delete",
function() {
var link = $(this);
var row = $(this).parents("tr"); // get row element
Then set the cell data using that variable like so:
itemsTable.cell(itemsTable.row(row), $('.changeStatus')).data("Deleted").draw();
This worked and successfully drew the table with the updated value.

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

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" }))

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>

Categories

Resources