Refresh Jquery datatable after deleting a row - asp.net-mvc

I have a Datatable that display data from a table (role) and i use an ajax call to delete a row :
function OnDeleteClick(el)
{
//var url = ($(this).attr('href'));
//alert(url);
//var employeeId = event.getAttribute(id); //e.target.id
//var url = ($(this).attr('id'));
var id = $(el).attr('id');
var roleid = getURLParameter(id, 'id');
var username = getURLParameter(id, 'name');
var flag = confirm('You are about to delete Role ' + username + ' permanently.Are you sure you want to delete this record?');
if (flag) {
$.ajax({
url: '/Role/DeleteAJAX',
type: 'POST',
data: { roleId: roleid },
dataType: 'json',
//success: function (result) { alert(result); $("#" + Name).parent().parent().remove(); },
success: function (result) {
alert(result);
},
error: function () { alert('Error!'); }
});
}
return false;
}
My question is how to refresh the jquery datatable once the delete has succeed, so the deleted row will be also deleted from the datatable?
Below is the code that i use to build my JQuery datatable :
<div class="row" >
<fieldset class="col-md-10 col-md-offset-1" >
<legend>Liste des rôles </legend>
<div class="table-responsive" style="overflow-x: hidden;">
<table width="98%" class="table table-bordered table-hover" id="dataTables-example">
<thead>
<tr>
<th>Designation Role</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#item.Name
</td>
<td width="110">
<a class="btn btn-custom btn-xs" href="" onclick="return getbyID('#item.Id',false)" title="consulter">
<i class="fa fa-folder-open-o"></i>
</a>
<a class="btn btn-custom btn-xs" href="" onclick="return getbyID('#item.Id',true)" title="Editer">
<i class="fa fa-edit"></i>
</a>
<a class="btn btn-custom btn-xs" onclick="OnDeleteClick(this);" id="#?id=#item.Id&name=#item.Name" title=" supprimer">
<i class="fa fa-trash-o"></i>
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="b-right">
<button type="button" class="btn btn-primary" onclick="location.href='#Url.Action("Create", "Role")'">
<i class="fa fa-plus"></i> Ajouter un role
</button>
</div>
</fieldset>
</div>
Thanks for your help.

If you are using DataTables.net and you built your table like this with an AJAX data source:
var myTable = $("#myTable").DataTable({ ajax: "path/to/my/data" });
You can refresh the data in the table like this:
$.ajax({
<your deleting code>
success: function() {
myTable.ajax.reload();
})
});
If you've built your table with Razor using the view model from MVC, you will need to either 1) reload the page in the success callback:
$.ajax({
<your deleting code>
success: function() {
window.location.reload();
})
});
or 2) remove the row from the DOM, knowing it's already deleted from the database, to keep the DB and the UI in sync:
$.ajax({
<your deleting code>
success: function() {
myTable.row($(el).parents("tr")).remove().draw();
})
});

Related

ASP.NET MVC code modal popup not sending parameter to controller

I am trying to present Organization Details in a partial view in a Bootstrap Modal. After a day of reading/watching tutorials I am stumped.
Organizations are loaded into a Table and there is a View option on each row. The table has an id="OrganizationGrid"
<table class="table table-striped table-bordered dt-responsive nowrap" width="100%" cellspacing="0" id="OrganizationGrid">
<thead>
<tr style="color:#126E75; background-color:lightcyan">
<th style=" width:5%">
#Html.ActionLink("ID", "Index", new { sortOrder = ViewBag.IDSortParm, currentFilter=ViewBag.CurrentFilter })
</th>
<th style=" width:25%">
#Html.ActionLink("Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
</th>
<th style=" width:25%">
Parent Org
</th>
<th style=" width:10%; text-align:center">
Website
</th>
<th style="width:25%">
Comment
</th>
<th scope="col" colspan="3" style=" width:10%; text-align:center">Action</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr class=" table-light">
<td style="text-align:left">
#Html.DisplayFor(modelItem => item.Id)
</td>
<td style="text-align:left">
#Html.DisplayFor(modelItem => item.Name)
</td>
<td style="text-align:left">
#Html.DisplayFor(modelItem => item.ParentOrg.Name)
</td>
<td style="text-align: center">
<a href=#Html.DisplayFor(modelItem=> item.Website) target="_blank">
<i class="fa-solid fa-globe" target="_blank" rel="noopener noreferrer"></i>
</a>
</td>
<td style="text-align:left">
#Html.DisplayFor(modelItem => item.Comment)
</td>
<td style="text-align: center">
<a asp-action="Edit" asp-route-id="#item.Id">
<i class="fas fa-edit"></i>
</a>
</td>
<td>
<a class="details" href="javascript:;">View</a>
</td>
<td style="text-align: center">
<a asp-action="Delete" asp-route-id="#item.Id">
<i class="fas fa-trash" style="color:red"></i>
</a>
</td>
</tr>
}
</tbody>
</table>
My understanding is that it triggers the JavaScript function which is currently residing at the bottom of the same page:
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
$("#OrganizationGrid .details").click(function() {
var organizationId = $(this).closest("tr").find("td").eq(0).html();
$.ajax({
type: "POST",
url: "/Organizations/Details",
data: '{organizationId: "' + organizationId + '" }',
contentType: "application/json; charset=utf-8",
dataType: "html",
success: function(response) {
$("#partialModal").find(".modal-body").html(response);
$("#partialModal").modal('show');
},
failure: function(response) {
alert(response.responseText);
},
error: function(response) {
alert(response.responseText);
}
});
});
});
</script>
I inserted a break in the controller to see if ajax was passing the organizationId but it is not. This is the Controller Details Action method:
[HttpPost]
public ActionResult Details(int organizationId)
{
var organization = _context.Organizations
.Include(o => o.ParentOrg).Where(p=>p.Id == organizationId);
if (organization == null)
{
return NotFound();
}
return PartialView("_Details", organization);
}
If I hard code organizationId in the Controller the popup loads with the correct information BUT if I hard code organizationId in the JavaScript function it still passes a null value to the controller. Could someone please point me in the right direction.
The issue was with the JavaScript function and specifically the syntax of the data attribute in ajax. Corrected solution is as follows:
$(function () {
$("#OrganizationGrid .details").click(function () {
var organizationId = $(this).closest("tr").find("td").eq(0).html();
//alert(organizationId)
$.ajax({
type: "POST",
url: "/Organizations/Details",
data: { 'id': organizationId },
//contentType: "application/json; charset=utf-8",
//dataType: "html",
success: function (response) {
$("#viewEditOrgModal").find(".modal-body").html(response);
$("#viewEditOrgModal").modal('show');
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
});
});

Pass Object Parameter from javascript\ajax to function in Controller

I have a table that i want to be able to update the status of each line that checkbox is on
(see attached screenshot)
The checkbox propery in the Model is Not Mapped to the database ([NotMapped])
Html:
<div class="row">
<div class="col-12 text-right">
<button class="btn btn-primary" onclick="ApproveStatus()">Approve Checked Lines</button>
</div>
</div>
javaScript:
#section Scripts{
<script type="text/javascript">
function ApproveStatus() {
var pdata = new FormData();
swal({
title: "Are you sure?",
text: "Once Updated, you will not be able to Undo this",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
$.ajax({
url: "PaymentHistory/ApproveStatus",
type: "POST",
data: pdata,
processData: false,
contentType: false,
success: function (data) {
swal("Success!", {
icon: "success",
});
}
});
setTimeout(function () {
location.reload()
}, 100);
} else {
swal("Nothing Changed!");
}
});
}
</script>
}
And in the Controller i have the function (haven't written the logic yet)
[HttpPost]
public IActionResult ApproveStatus()
{
}
table in html:
<table id="tblData" class="table table-striped table-bordered" style="width:100%">
<thead class="thead-dark">
<tr class="table-info">
<th>Address</th>
<th>Payment Type</th>
<th>Amount</th>
<th>Payment Date</th>
<th>Status</th>
<th></th>
</thead>
#foreach (PaymentHistory paymentHistory in Model)
{
<tr>
<td>#ViewBag.getPaymentAddress(paymentHistory.SentFromAddressId).ToString()</td> <td>#ViewBag.getPaymentType(paymentHistory.SentFromAddressId).ToString()</td>
<td>#paymentHistory.Amount$</td>
<td>#paymentHistory.PayDate</td>
<td>#paymentHistory.Status</td>
#if (paymentHistory.Status != "Approved")
{
<td>
<div class="text-center">
<input type="checkbox" asp-for="#paymentHistory.isChecked"/>
</div>
</td>
}
else
{
<td></td>
}
</tr>
}
</table>
My only issue is that i want to pass the Object from the View (that contains the lines and status of the checkbox) to the function in the controller as a parameter,
Any ideas how can i do this?
Thank you
i want to pass the Object from the View (that contains the lines and status of the checkbox) to the function in the controller as a parameter, Any ideas how can i do this?
To achieve your requirement, you can try to add a hidden field for SentFromAddressId field, like below.
<td>
<div class="text-center">
<input type="hidden" asp-for="#paymentHistory.SentFromAddressId" />
<input type="checkbox" asp-for="#paymentHistory.isChecked" />
</div>
</td>
then you can get the sentFromAddressId of each checked row and populate it in form data object.
var pdata = new FormData();
$("input[name='paymentHistory.isChecked']:checked").each(function (index, el) {
var sentFromAddressId = $(this).siblings("input[type='hidden']").val();
pdata.append("Ids", sentFromAddressId);
})
and post the data to action method with following code snippet.
$.ajax({
type: 'POST',
url: '/PaymentHistory/ApproveStatus',
data: pdata,
processData: false,
contentType: false,
datatype: 'json',
success: function (res) {
//...
}
});
ApproveStatus action method
public IActionResult ApproveStatus(int[] Ids)
{
//code logic here
//update corresponding record based on id within Ids
Get all checked checkboxes id in an array, use that array to update table

delete from model don't work correctly

i have a repeating table in razor mvc , my model is an array
#model Models.Event[]
when i delete any row , every thing is ok and i check data in break point...
but view can't render data correctly and always last row is deleted.
in my code, i want to delete row index 2 ... i give it as hard code , in controller and view true data is passed but at the end, in view (display page) , the last row is deleted and the row with index 2 is there again :(
i pass my model from an partial view to the main page.
here is my javascript codes:
<script type="text/javascript">
$(document)
.ready(function () {
$("#EventsTbl")
.on("click", "tbody .btn-delrow",
function () {
var deletedrow = $(this).closest("tr");
var index = 2; // deletedrow.attr("data-ownum");
var data = $("#edit-Event-form").serialize();
deleteEvent(data, index);
});
function deleteEvent(data,index) {
if (confirm("Do you want to delete product: " + index)) {
var postdata = data;
$.post('/Events/DeleteDetailRow', postdata,
function (response) {
$("#EventsTbl tbody").html(response);
});
}
}
});
</script>
and in controller we have :
[HttpPost]
public ActionResult DeleteDetailRow(Event[] model)
{
var list = new List<Event>(model);
list.RemoveAt(2);
return PartialView("AllDetailRows", list.ToArray());
}
"list" has true data after delete, but i don't know where is the problem. at AllDetailRows model has true data too.
here is my partial view code : (AllDetailRows)
#model Models.Event[]
#for (int i = 0; i < Model.Length; i++)
{
<tr data-rownum="#i">
<td class="input">
#Html.EditorFor(model => model[i].EventExp,new {value=Model?[i]?.EventExp})
#Html.ValidationMessageFor(model => model[i].EventExp, "", new { #class = "text-danger" })
</td>
<td>
<button class="btn btn-sm btn-danger btn-delrow" type="button">
<i class="fa fa-remove"></i>
</button>
</td>
</tr>
}
and this is my main view :
#model Models.Event[]
#* This partial view defines form fields that will appear when creating and editing entities *#
<fieldset>
<div>
<table class="table table-bordered table-striped" id="AdverseEventsTbl">
<thead>
<tr>
<th class="text-center or-bold" style="width: 10%">#Html.LabelFor(model => model.First().EventExp)</th>
<th style="width: 5%">
<button class="btn btn-sm btn-success btn-addrow" type="button">
<i class="fa fa-plus-square"></i>
</button>
</th>
</tr>
</thead>
<tbody>
#Html.Partial("AllDetailRows", Model)
</tbody>
</table>
</div>
</fieldset>
any idea?
thanks is advance

Delete Confirm with Bootstrap modal in Asp.Net MVC

I am trying to make a dialog with bootstrap modal to confirm a delete. The delete works well, except it doesn't get the data which I select but it gets the first data in ID order from the database. I am new on client-side programming, so if someone could help me it would be nice.
The code is:
[HttpPost]
public async Task<ActionResult> Delete(int id)
{
RepFilter repFilter = await db.RepFilters.FindAsync(id);
db.RepFilters.Remove(repFilter);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
(razor)
#foreach (var item in Model)
{
using (Html.BeginForm("Delete", "RepFilters", new { id = item.ID }))
{
<tr>
<td>#index</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Report.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
#Html.ActionLink("Details", "Details", new { id = item.ID }) |
<button type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#myModal">Delete</button>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Confirm Delete</h4>
</div>
<div class="modal-body">Are you sure you want to delete: <span><b>#item.Description</b></span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<input type="submit" value="Delete" class="btn btn-danger" />
</div>
</div>
</div>
</div>
</td>
</tr>
}
}
</tbody>
The button that opens the modal is getting the right ID, but the modal doesn't!
So how to make the modal take the adequate data to delete?
I am trying to avoid writing javascript and use data attributes, until there is no other choice
The modal this way has the same ID, no matter which data you try to delete.
So just add a variable to specify different ID for the mmodal:
using (Html.BeginForm("Delete", "RepFilters", new { id = item.ID }))
{
var myModal = "myModal" + item.ID;
<tr>
<td>...</td>
<td>...</td>
<button type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target="##myModal">Delete</button>
<!-- Modal -->
<div class="modal fade" id="#myModal" tabindex="-1" role="dialog" data-keyboard="false" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-sm">
...........<!--And the rest of the modal code-->
There are actually quite a few things you may want to address in your view. You are looping through the items in your model, and creating a separate form (and modal)for each item. This is probably not ideal, however if you really want to do it this way you can add a reference to the item id within the html for the modal. Just add a hidden input and set it's value to item.id.
However, I would not recommend this approach. I'm not certain for your reasons on wanting to shy away from JavaScript, but the functionality you want to create here is actually pretty basic.
See this post: Confirm delete modal/dialog with Twitter bootstrap?
Edit:
#foreach (var item in Model)
{
<tr>
<td>#index</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Report.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
#Html.ActionLink("Details", "Details", new { id = item.ID }) |
<button type="button" class="btn btn-danger btn-sm" data-item-id="#item.ID" data-item-description="#item.Report.Description" data-toggle="modal" data-target="#confirm-delete">Delete</button>
</td>
</tr>
}
<div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Confirm Delete</h4>
</div>
<div class="modal-body">
Are you sure you want to delete: <span class="description"></span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<input type="submit" value="Delete" class="btn btn-danger" />
</div>
</div>
</div>
<script>
$('#confirm-delete').on('click', '.btn-ok', function(e) {
var $modalDiv = $(e.delegateTarget);
var id = $(this).data('itemId');
$modalDiv.addClass('loading');
$.post('/RepFilters/Delete/' + id).then(function () {
$modalDiv.modal('hide').removeClass('loading');
});
});
$('#confirm-delete').on('show.bs.modal', function(e) {
var data = $(e.relatedTarget).data();
$('.description', this).text(data.itemDescription);
$('.btn-ok', this).data('itemId', data.itemId);
});
</script>
You first write a delete function in jquery .for displaye confirm message you can use sweetalert and write a custom file for sweetalert.
yo must add refrence sweetalert css and script in your view page.
function Delete(id) {
var submitdelete=function(){ $.ajax({
url: '#Url.Action("/mycontroller/Delete)',
type: 'Post',
data: { id: id }
})
.done(function() {
$('#' + id).remove();//if you want to delete table row
msgBox.success("Success","Ok");
});}
msgBox.okToContinue("warning", "Are you sure to delete ?", "warning", "ok","cancel", submitdelete);
}
Confirm dialog
var msgBox = {
message: {
settings: {
Title: "",
OkButtonText: "",
type:"info"
}
},
okToContinue: function(title, text, type, okButtonText,closeButtonText, isConfirmDo) {
swal({
title: title,
text: text,
type: type,
showCancelButton: true,
confirmButtonClass: 'btn-danger',
confirmButtonText: okButtonText,
cancelButtonText: closeButtonText,
closeOnConfirm: false,
closeOnCancel: true
},
function(isConfirm) {
if (isConfirm) {
isConfirmDo();
}
});
},
confirmToContinue: function(title, text, type, confirmButtonText, cancelButtonText, isConfirmDo, isNotConfirmDo, showLoader) {
if (!showLoader) {
showLoader = false;
}
swal({
title: title,
text: text,
type: type,
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: confirmButtonText,
cancelButtonText: cancelButtonText,
closeOnConfirm: true,
closeOnCancel: true,
showLoaderOnConfirm: showLoader
},
function(isConfirm) {
if (isConfirm) {
isConfirmDo();
}
});
} ,
success: function (title, text,okButtontex) {
swal({
title: title,
text: text,
type: "success",
confirmButtonText: okButtontex
});
},
info: function (title, text) {
swal({
title: title,
text: text,
type: "info",
confirmButtonText: "OK"
});
},
warning: function (title, text) {
swal({
title: title,
text: text,
type: "warning",
confirmButtonText: "OK"
});
},
error: function (title, text) {
swal({
title: title,
text: text,
type: "error",
confirmButtonText: "OK"
});
},
}
Here's an easy way to do this. You should be able to adapt what i did here to your case. This requires very little javascript code.
<script>
var path_to_delete;
var root = location.protocol + "//" + location.host;
$("#deleteItem").click(function (e) {
path_to_delete = $(this).data('path');
$('#myform').attr('action', root + path_to_delete);
});
</script>
<table class="table table-hover" id="list">
<thead class="bg-dark text-white">
<tr>
<th>
Edit
</th>
<th>
Employee
</th>
<th>
Effective Date
</th>
<th>
ST/OT/DT
</th>
<th>
Other Pay
</th>
<th>
Job
</th>
<th>
Pending?
</th>
<th>
Delete
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
<a class="btn btn-sm" href="~/Employees/TerminationEdit/#item.Employee_Termination_Info_Id">
<i class="fa fa-lg fa-pencil-alt text-dark"></i>
</a>
</td>
<td>
#Html.DisplayFor(modelItem => item.Employee_Name_Number)
</td>
<td>
#Html.DisplayFor(modelItem => item.Effective_Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.Employee_Time)
</td>
<td>
#Html.DisplayFor(modelItem => item.Employee_Other_Pay)
</td>
<td>
#Html.DisplayFor(modelItem => item.Job_Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Pending)
</td>
<td>
<a id="deleteItem" class="btn btn-sm" data-toggle="modal" data-target="#deleteModal"
data-path="/Employees/TerminationDelete/#item.Employee_Termination_Info_Id">
<i class="fa fa-lg fa-trash-alt text-danger"></i>
</a>
</td>
</tr>
}
</tbody>
</table>
<!-- Logout Modal-->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Are you sure?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">Are you sure you want to delete this termination record? <br /><span class="text-danger">This cannot be undone.</span></div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
#using (Html.BeginForm("TerminationDelete", "Employees", FormMethod.Post, new { id = "myform", #class = "" }))
{
#Html.AntiForgeryToken()
<input type="submit" value="Delete" id="submitButton" class="btn btn-danger" />
}
</div>
</div>
</div>
</div>
So what happens here, is that the page will cycle through the model and draw the delete button (using font awesome). Note that here is is setting the data-path attribute for later use. When the button is clicked, it sets the form action for the button on the modal popup immediately. This is important, since it has a form around the delete button, it will send it to a POST and not a GET, as Rasika and Vasil Valchev pointed out. Plus, it has the benefit of the anti-forgery token.

entityAspect.setDeleted doesn't fire the subscribed propertyChanged event

I am running into problem where i subscribe to propertyChanged event, the subscribed event does fire for entity Modification, but never fires for when setting entity to Deleted.
what might i be doing wrong.
The objective of what i am doing is that, whenever user modifies the row, i want to provide
button at row level to cancel the changes. similarly when user deletes a row, i want to provide a button to unDelete a row. The modification part works as expected, but for Delete it is not working.
I was Expecting that item.entityAspect.setDeleted(), would fire the propertyChanged Event
so that i can update the vlaue of observable IsDeleted,which in turn would update the visibility of the button.
ViewModel
/// <reference path="jquery-1.8.3.js" />
/// <reference path="../linq-vsdoc.js" />
/// <reference path="../linq.min.js" />
/// <reference path="../breeze.intellisense.js" />
/// <reference path="../breeze.debug.js" />
$(document).ready(function () {
//extend country type
var Country = function () {
console.log("Country initialized");
var self = this;
self.Country_ID = ko.observable(0); // default FirstName
self.Country_Code = ko.observable(""); // default LastName
self.Country_Name = ko.observable("");
self.entityState = ko.observable("");
self.hasValidationErrors = ko.observable(false);
self.IsDeleted = ko.observable(false);
self.IsModified = ko.observable(false);
self.templateName = ko.observable("AlwayEditable");
var onChange = function () {
var hasError = self.entityAspect.getValidationErrors().length > 0;
if (hasError)
self.hasValidationErrors(true);
else
self.hasValidationErrors(false);
};
//dummy property to wireup event
//should not be used for any other purpose
self.hasError = ko.computed(
{
read: function () {
self.entityAspect // ... and when errors collection changes
.validationErrorsChanged.subscribe(onChange);
},
// required because entityAspect property will not be available till Query
// return some data
deferEvaluation: true
});
//dummy property to wireupEvent and updated self.entityStateChanged property
self.entityStateChanged = ko.computed({
read: function () {
self.entityAspect.propertyChanged.subscribe(function (changeArgs) {
if (changeArgs.entity.entityAspect.entityState.name == "Deleted") {
self.IsDeleted(false);
}
else if (changeArgs.entity.entityAspect.entityState.name == "Modified")
self.IsModified(true);
}); //subscribe
},
deferEvaluation: true,
// self.entityStateChanged(false)
});
self.fullName = ko.computed(
function () {
return self.Country_Code() + " --- " + self.Country_Name();
});
};
manager.metadataStore.registerEntityTypeCtor("Country", Country);
var countryViewModel = function (manager) {
var self = this;
window.viewModel = self;
self.list = ko.observableArray([]);
self.pageSize = ko.observable(2);
self.pageIndex = ko.observable(0);
self.selectedItem = ko.observable();
self.hasChanges = ko.observable(false);
self.totalRows = ko.observable(0);
self.totalServerRows = ko.observable(0);
self.RowsModified = ko.observable(false);
self.RowsAdded = ko.observable(false);
self.RowsDeleted = ko.observable(false);
self.templateToUse = function (dataItem, context) {
var item = dataItem;
if (!_itemTemplate) {
_itemTemplate = ko.computed(function (item) {
//var x = this;
if (this.entityAspect == "undefined")
return this.templateName("AlwayEditable");
if (this.entityAspect.entityState.name == "Deleted") {
this.templateName("readOnlyTmpl");
return this.templateName();
}
else {
this.templateName("AlwayEditable");
return this.templateName();
}
}, item);
}
if (item.entityAspect.entityState.name == "Deleted") {
item.templateName("readOnlyTmpl");
return item.templateName();
}
else {
item.templateName("AlwayEditable");
return item.templateName();
}
// return _itemTemplate();
}
var _itemTemplate;
self.hasError = ko.computed(
{
read: function () {
self.entityAspect // ... and when errors collection changes
.validationErrorsChanged.subscribe(onChange);
},
// required because entityAspect property will not be available till Query
// return some data
deferEvaluation: true
});
self.acceptChanges = function (item) {
// self.selectedItem().entityAspect.acceptChanges();
self.selectedItem(null);
}
manager.hasChanges.subscribe(function (newvalue) {
self.hasChanges(newvalue.hasChanges);
});
self.edit = function (item, element) {
highlightRow(element.currentTarget, item);
self.selectedItem(item);
};
self.discardChanges = function () {
manager.rejectChanges();
manager.clear();
self.pageIndex(0);
self.loadData();
};
self.cancel = function (item, element) {
item.entityAspect.rejectChanges();
self.selectedItem(null);
};
self.add = function () {
var countryType = manager.metadataStore.getEntityType("Country"); // [1]
var newCountry = countryType.createEntity(); // [2]
//if not using this line, the table is not updated to show this newly added item
self.list.push(newCountry);
manager.addEntity(newCountry); // [3]
self.selectedItem(newCountry);
};
self.remove = function (item) {
item.entityAspect.rejectChanges();
item.entityAspect.setDeleted(); //was expecting that propertychaged subscribe event will fire, but it does not
item.templateName("readOnlyTmpl"); //if i don't do this the template is not changed/updated
item.IsDeleted(true); //have to use this
};
self.UndoDelete = function (item) {
item.entityAspect.rejectChanges();
item.templateName("AlwayEditable");
item.IsDeleted(false);
};
self.save = function () {
if (manager.hasChanges()) {
alertTimerId = setTimeout(function () {
//this works as well
$.blockUI({ message: '<img src="Images/360.gif" /> </p><h1>Please Saving Changes</h1>', css: { width: '275px' } });
}, 700);
manager.saveChanges()
.then(saveSucceeded(alertTimerId))
.fail(saveFailed);
} else {
$.pnotify({
title: 'Save Changes',
text: "Nothing to save"
});
// alert("Nothing to save");
};
};
manager.hasChanges.subscribe(function (newvalue) {
self.hasChanges(newvalue.hasChanges);
});
manager.entityChanged.subscribe(function (changeArg) {
self.RowsDeleted(manager.getEntities(null, [breeze.EntityState.Deleted]).length);
self.RowsModified(manager.getEntities(null, [breeze.EntityState.Modified]).length);
self.RowsAdded(manager.getEntities(null, [breeze.EntityState.Added]).length);
});
//we want maxPageIndex to be recalculated as soon as totalRows or pageSize changes
self.maxPageIndex = ko.dependentObservable(function () {
return Math.ceil(self.totalRows() / self.pageSize()) - 1;
//return Math.ceil(self.list().length / self.pageSize()) - 1;
});
self.previousPage = function () {
if (self.pageIndex() > 1) {
self.pageIndex(self.pageIndex() - 1);
//self.loadData();
getData();
}
};
self.nextPage = function () {
if (self.pageIndex() < self.maxPageIndex()) {
self.pageIndex(self.pageIndex() + 1);
// self.loadData();
getData();
}
};
self.allPages = ko.dependentObservable(function () {
var pages = [];
for (i = 0; i <= self.maxPageIndex() ; i++) {
pages.push({ pageNumber: (i + 1) });
}
return pages;
});
self.moveToPage = function (index) {
self.pageIndex(index);
//self.loadData();
getData();
};
};
// self.loadData
var vm = new countryViewModel(manager);
//ko.validation.group(vm);
ko.applyBindings(vm);
// ko.applyBindingsWithValidation(vm);
vm.loadData();
try {
} catch (e) {
displayModalMessage("Page Error :- Reload the Page", e.message);
}
}); //end document.ready
View
<p><a class="btn btn-primary" data-bind="click: $root.add" href="#" title="Add New Country"><i class="icon-plus"></i> Add Country</a></p>
<span> Search Country Code :</span><input id="txtSearch" type="text" /><input id="BtnSearch" type="button" value="Search" data-bind="click: $root.loadData" />
<!--<table class="table table-striped table-bordered " style="width: 700px">-->
<!--<table id="myTable" class="ui-widget" style="width: 800px">-->
<table id="myTable" class="table table-striped table-bordered " style="width: 1200px">
<caption> <div>
<span class="label label-info">Number of Rows Added </span> <span class="badge badge-info" data-bind="text: RowsAdded"></span> ,
<span class="label label-success">Number of Rows Modified</span> <span class="badge badge-success" data-bind="text: RowsModified"></span> ,
<span class="label label-important">Number of Rows Deleted</span> <span class="badge badge-important" data-bind="text: RowsDeleted"></span>
<p/>
</div></caption>
<thead class="ui-widget-header">
<tr>
<th>Code</th>
<th>Name</th>
<th>Full Name</th>
<th />
</tr>
</thead>
<!--<tbody data-bind=" title:ko.computed(function() { debugger; }), template:{name:templateToUse, foreach: list, afterRender: HighlightRows }" class="ui-widget-content"></tbody>-->
<tbody data-bind=" title:ko.computed(function() { debugger; }), template:{name:templateToUse, foreach: list}" ></tbody>
</table>
<div class="pagination">
<ul><li data-bind="css: { disabled: pageIndex() === 0 }">Previous</li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }">Next</li></ul>
</div>
<!--<input id="Button1" type="button" value="Save" data-bind="attr: { disabled: !hasChanges()}, click:saveChanges" />-->
<a class="btn btn-success btn-primary" data-bind="click: $root.save, css: { disabled: !$root.hasChanges()}" href="#" title="Save Changes"> Save Changes</a>
<!-- <input id="Button3" type="button" value="Create New" data-bind="click:AddNewCountry" />
<input id="Button4" type="button" value="Discard and reload data" data-bind="click:discardreload, attr: { disabled: !hasChanges()}" /> -->
<a class="btn btn-danger btn-primary" data-bind="click: $root.discardChanges, css: { disabled: !$root.hasChanges()}" href="#" title="Discard Changes"><i class="icon-refresh"></i> Discard & Reload</a>
<script id="readOnlyTmpl" type="text/html">
<tr >
<td>
<span class="label " data-bind="text: Country_Code "></span>
<div data-bind="if: hasValidationErrors">
<span class="label label-important" data-bind="text: $data.entityAspect.getValidationErrors('Country_Code')[0].errorMessage ">Important</span>
</div>
</td>
<td>
<span class="label " data-bind="text: Country_Name "></span>
<p data-bind="validationMessage: Country_Name"></p>
<span data-bind='visible: ko.computed(function() { debugger; }), text: Country_Name.validationMessage'> </span>
</td>
<td> <span class="label " data-bind="text: fullName "></span>
</td>
<td >
<a class="btn btn-danger" data-bind="click: $root.cancel, visible: $data.IsModified" href="#" title="cancel/undo changes">Undo Changes<i class="icon-trash"></i></a>
<a class="btn btn-danger" data-bind="click: $root.remove, visible: !$data.IsDeleted() " href="#" title="Delete this Row">Delete<i class="icon-remove"></i></a>
<a class="btn btn-danger" data-bind="click: $root.UndoDelete, visible: $data.IsDeleted() " href="#" title="Undo Delete">Un Delete<i class="icon-remove"></i></a>
</td>
</tr>
</script>
<script id="AlwayEditable" type="text/html">
<tr >
<td><input type="text" placeholder="Country Code" data-bind="value: Country_Code , uniqueName: true, css: { error: hasValidationErrors }, valueUpdate: 'afterkeydown'"/>
<!-- <div data-bind="if: $data.entityAspect.getValidationErrors().length>0">
<pre data-bind="text: $data.entityAspect.getValidationErrors('Country_Code')[0].errorMessage "></pre>
</div>-->
<div data-bind="if: hasValidationErrors">
<span class="label label-important" data-bind="text: $data.entityAspect.getValidationErrors('Country_Code')[0].errorMessage ">Important</span>
</div>
</td>
<td><input type="text" placeholder="Country Name" data-bind="value: Country_Name, uniqueName: true, valueUpdate: 'afterkeydown'"/>
<p data-bind="validationMessage: Country_Name"></p>
<span data-bind='visible: ko.computed(function() { debugger; }), text: Country_Name.validationMessage'> </span>
</td>
<td>
<span data-bind=' text: fullName'> </span>
</td>
<td >
<a class="btn btn-danger" data-bind="click: $root.cancel, visible: $data.IsModified" href="#" title="cancel/undo changes">Undo Changes<i class="icon-trash"></i></a>
<a class="btn btn-danger" data-bind="click: $root.remove, visible: !$data.IsDeleted() " href="#" title="Delete this Row">Delete<i class="icon-remove"></i></a>
<a class="btn btn-danger" data-bind="click: $root.UndoDelete, visible: $data.IsDeleted() " href="#" title="Undo Delete">Un Delete<i class="icon-remove"></i></a>
</td>
</tr>
</script>
Analysis
The propertyChanged event is raised when ... a property changes. But that's not what you want to watch. You want to monitor the entityAspect.entityState
When you set a property to a new value (for example person.FirstName("Naunihal")), you get both a propertyChanged event and a change to the entity's EntityState.
When you delete the entity, the entity's EntityState changes ... to "Deleted". But deleting doesn't change a property of the entity. Breeze does not consider the EntityState itself to be a property of the entity. Therefore, there is no propertyChanged notification.
Solution
Update Jan 12, 2013
I think more people will discover this solution if I rephrase the question that you asked so people understand that you want to listen for changes to EntityState.
So I moved my answer to a new SO question: "How can I detect a change to an entity's EntityState?". Hope you don't mind following that link.

Resources