Knockout Paging in MVC with Web API - asp.net-mvc

I can't get this code to page. The table's page size is always the same - the number of items returned from the Web API call. The parameter that is passed-in is ignored (3). Also, the back and forward button elements don't have icons (icon-step-backward and icon-step-forward), but this is secondary to the page size.
HTML
<table class="table">
<thead>
<tr>
<th data-column="LastName">Last Name</th>
<th data-column="FirstName">First Name</th>
<th data-column="EnrollmentDate">Enrollment Date</th>
</tr>
</thead>
<tbody data-bind="foreach: students">
<tr>
<td data-bind="text: LastName" />
<td data-bind="text: FirstName" />
<td data-bind="text: EnrollmentDate" />
</tr>
</tbody>
<tfoot>
<tr>
<td>
Number of items per page:
<select id="pageSizeSelector" data-bind="value: pageSize">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
</select>
</td>
<td colspan="3">
<button data-bind="click: previousPage" class="btn"><i class="icon-step-backward"></i></button>
Page<label data-bind="text: currentPageIndex() + 1" class="badge"></label>
<button data-bind="click: nextPage" class="btn"><i class="icon-step-forward"></i></button>
</td>
</tr>
</tfoot>
</table>
KNOCKOUT and JQUERY
<script type="text/javascript">
var viewModel = function () {
self = this;
self.currentPage = ko.observable();
self.pageSize = ko.observable(3);
self.currentPageIndex = ko.observable(0);
self.students = ko.observableArray();
self.currentPage = ko.computed(function () {
var pagesize = parseInt(self.pageSize(), 3),
startIndex = pagesize * self.currentPageIndex(),
endIndex = startIndex + pagesize;
return self.students.slice(startIndex, endIndex);
});
self.nextPage = function () {
if (((self.currentPageIndex() + 1) * self.pageSize()) < self.students().length) {
self.currentPageIndex(self.currentPageIndex() + 1);
}
else {
self.currentPageIndex(0);
}
}
self.previousPage = function () {
if (self.currentPageIndex() > 0) {
self.currentPageIndex(self.currentPageIndex() - 1);
}
else {
self.currentPageIndex((Math.ceil(self.students().length / self.pageSize())) - 1);
}
}
}
$(document).ready(function () {
$.ajax({
url: "http://localhost:12769/api/student",
type: "GET"
}).done(function (data) {
var vm = new viewModel();
vm.students(data);
ko.applyBindings(vm);
}).error(function (jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText || textStatus);
});
});
</script>
JSON Returned From Web API
[{"$id":"1","ID":1,"LastName":"Alexander","FirstMidName":"Carson","EnrollmentDate":"2005-09-01T00:00:00","Enrollments":[{"$id":"2","EnrollmentID":1,"CourseID":1050,"StudentID":1,"Grade":0,"Course":{"$id":"3","CourseID":1050,"Title":"Chemistry","Credits":3},"Student":{"$ref":"1"}},{"$id":"4","EnrollmentID":2,"CourseID":4022,"StudentID":1,"Grade":2,"Course":{"$id":"5","CourseID":4022,"Title":"Microeconomics","Credits":3},"Student":{"$ref":"1"}},{"$id":"6","EnrollmentID":3,"CourseID":4041,"StudentID":1,"Grade":1,"Course":{"$id":"7","CourseID":4041,"Title":"Macroeconomics","Credits":3},"Student":{"$ref":"1"}}]},{"$id":"8","ID":2,"LastName":"Alonso","FirstMidName":"Meredith","EnrollmentDate":"2002-09-01T00:00:00","Enrollments":[{"$id":"9","EnrollmentID":4,"CourseID":1045,"StudentID":2,"Grade":1,"Course":{"$id":"10","CourseID":1045,"Title":"Calculus","Credits":4},"Student":{"$ref":"8"}},{"$id":"11","EnrollmentID":5,"CourseID":3141,"StudentID":2,"Grade":4,"Course":{"$id":"12","CourseID":3141,"Title":"Trigonometry","Credits":4},"Student":{"$ref":"8"}},{"$id":"13","EnrollmentID":6,"CourseID":2021,"StudentID":2,"Grade":4,"Course":{"$id":"14","CourseID":2021,"Title":"Composition","Credits":3},"Student":{"$ref":"8"}}]},{"$id":"15","ID":3,"LastName":"Anand","FirstMidName":"Arturo","EnrollmentDate":"2003-09-01T00:00:00","Enrollments":[{"$id":"16","EnrollmentID":7,"CourseID":1050,"StudentID":3,"Grade":null,"Course":{"$ref":"3"},"Student":{"$ref":"15"}}]},{"$id":"17","ID":4,"LastName":"Barzdukas","FirstMidName":"Gytis","EnrollmentDate":"2002-09-01T00:00:00","Enrollments":[{"$id":"18","EnrollmentID":8,"CourseID":1050,"StudentID":4,"Grade":null,"Course":{"$ref":"3"},"Student":{"$ref":"17"}},{"$id":"19","EnrollmentID":9,"CourseID":4022,"StudentID":4,"Grade":4,"Course":{"$ref":"5"},"Student":{"$ref":"17"}}]},{"$id":"20","ID":5,"LastName":"Li","FirstMidName":"Yan","EnrollmentDate":"2002-09-01T00:00:00","Enrollments":[{"$id":"21","EnrollmentID":10,"CourseID":4041,"StudentID":5,"Grade":2,"Course":{"$ref":"7"},"Student":{"$ref":"20"}}]},{"$id":"22","ID":6,"LastName":"Justice","FirstMidName":"Peggy","EnrollmentDate":"2001-09-01T00:00:00","Enrollments":[{"$id":"23","EnrollmentID":11,"CourseID":1045,"StudentID":6,"Grade":null,"Course":{"$ref":"10"},"Student":{"$ref":"22"}}]},{"$id":"24","ID":7,"LastName":"Norman","FirstMidName":"Laura","EnrollmentDate":"2003-09-01T00:00:00","Enrollments":[{"$id":"25","EnrollmentID":12,"CourseID":3141,"StudentID":7,"Grade":0,"Course":{"$ref":"12"},"Student":{"$ref":"24"}}]},{"$id":"26","ID":8,"LastName":"Olivetto","FirstMidName":"Nino","EnrollmentDate":"2005-09-01T00:00:00","Enrollments":[]}]

One reason it might not be working for you, is that you are using parseInt with a radix of 3. So in your currentPage ko computed, the line var pagesize = parseInt(self.pageSize(), 3) is going to result in a NaN. See the MDN docs for more info.
Here's a helpful paging extension for a KO observable array, and it will clean up your view model as well. See this fiddle for a working example. In your case, your viewmodel would simply be
var viewmodel = function(){
var self = this;
self.students = ko.observableArray().paged(3);
}
Your HTML would be this:
<table data-bind="foreach: students.pagedItems">
<!-- normal data binding syntax here -->
</table>
And some pagination controls. These are using Bootstrap classes, but gives you an idea of how to use them:
<ul class="pagination">
<li data-bind="css: { disabled: students.currentPageIndex() === 0 }">«</li>
</ul>
<ul class="pagination" data-bind="foreach: students.allPages">
<li data-bind="css: { active: pageNumber === ($root.students.currentPageIndex() + 1) }">
</li>
</ul>
<ul class="pagination">
<li data-bind="css: { disabled: students.currentPageIndex() === students.maxPageIndex() }">»</li>
</ul>
<br />
<span data-bind="text: students.currentStatus"></span>
And here's the extension. I have a second option to pass a sorting function in, but it is completely optional.
ko.observableArray.fn.paged = function (perPage, sortComparator) {
var items = this;
items.currentPage = ko.observable();
items.pageSize = ko.observable(perPage);
items.currentPageIndex = ko.observable(0);
items.currentItemPage = ko.computed(function () {
var pagesize = parseInt(items.pageSize(), 10),
startIndex = pagesize * items.currentPageIndex(),
endIndex = startIndex + pagesize;
return this().slice(startIndex, endIndex);
}, items);
items.pagedItems = ko.computed(function () {
var size = parseInt(items.pageSize(), 10),
start = items.currentPageIndex() * size;
if (typeof (sortComparator) === "function") {
var sorted = this().sort(sortComparator);
return sorted.slice(start, start + size);
} else {
return this().slice(start, start + size);
}
}, items);
items.maxPageIndex = ko.computed(function () {
return Math.ceil(this().length / items.pageSize()) - 1;
}, items);
items.allPages = ko.computed(function () {
var pages = [];
for (var i = 0; i <= items.maxPageIndex() ; i++) {
pages.push({ pageNumber: (i + 1) });
}
return pages;
}, items);
items.currentStatus = ko.computed(function () {
var pagesize = parseInt(items.pageSize(), 10),
start = pagesize * items.currentPageIndex(),
end = start + pagesize;
if (items.currentPageIndex() === items.maxPageIndex()) end = this().length;
return 'Showing ' + (start + 1) + ' to ' + end + ' of ' + this().length;
}, items);
items.nextPage = function () {
if (((items.currentPageIndex() + 1) * items.pageSize()) < items().length) {
items.currentPageIndex(items.currentPageIndex() + 1);
} else {
items.currentPageIndex(0);
}
};
items.previousPage = function () {
if (items.currentPageIndex() > 0) {
items.currentPageIndex(items.currentPageIndex() - 1);
} else {
items.currentPageIndex((Math.ceil(items().length / items.pageSize())) - 1);
}
};
items.moveToPage = function (index) {
items.currentPageIndex(index);
};
return items;
};

Related

jquery goes on the first page instead mine

I have 2 pages (data-role="page") in my HTML page, in the list page if you go to one detail, save, and try to go into another detail again, it goes to list instead of the detail page.
I tried to follow the code using chrome but the javascript is correct
To change page i use: $.mobile.changePage("#PageAddCustomer");
but it seems working for just 1/2 second, then it goes to the list (who is the first page in the code)
Chrome does not return any javascript error,
just here in stackoverflow preview it returns :"error loading page", with no clues on it
what is wrong ?
I am thinking to use 2 different HTML files, probably it could be an idea
var lat = 0;
var lng = 0;
var map;
var url = "https:////www.suale.it"; //"http://localhost:65386/";//
var urlSito = url+"//GestionePersonale";// "http://localhost:65386/";//
var urlCartellaIMG =url + "//IMGAnnunci";// url+"//public//IMGAnnunci";//
//tiene in memoria
var Categorie;
var Tipologie;
var TipoAnnunci;
var Razze;
$(document).ready(function () {
$("div[data-role='panel']").panel().enhanceWithin();
VaiCustomers();
});
//target the entire page, and listen for touch events
$('html, body').on('touchstart touchmove', function(e){
//prevent native touch activity like scrolling
e.preventDefault();
});
function caricaCustomer(j) {
var $select = $('#cbbInsAdsCustomer');
$select.find('option').remove();
$select.append("<option ></option>");
$.each(j, function (key, value) {
$select.append('<option value=' + value.CodTipologia + '>' + value.DescTipologia + '</option>');
});
}
function GoPage(page) {
//$.mobile.changePage(page);
$(':mobile-pagecontainer').pagecontainer('change', page, {
transition: 'flip',
changeHash: false,
reverse: true,
showLoadMsg: true
});
}
function DoNothing() { }
function CaricaDettagliClienteDetail(J) {
// J = JSON.stringify(J);
J = JSON.parse(J);
$("#txtDetailCliCognome").text(J.Cognome);
$("#txtDetailCliNominativo").text(J.Nominativo);
$("#txtDetailCliIndirizzo").text(J.Indirizzo);
$("#txtDetailCliEmail").text(J.Email);
$("#txtDetailCliCitta").text(J.Citta);
$("#txtDetailCliCellulare").text(J.Cellulare);
$("#txtDetailCliTelefono").text(J.Telefono);
}
function CaricaDatiDetailIMG(data) {
var codAnn = $("#hidCodRapportino").val();
var ico;
var img;
$("#divDetailIMG").empty('');
//img = img.replace('\\', '//').replace('ICO', '');
var x, txt = "";//aggiungo IMG
for (x in data) {
var ico = urlCartellaIMG + "//" + codAnn + "//" + data[x];
var img = ico.replace('ICO', '');
txt += "<img onclick='OpenPopIMG(`" + img + "`)' src='" + ico + "'>";
}
$("#divDetailIMG").html(txt);
}
function GoHEad() {
$.mobile.changePage("#page1");
}
function getToNextPage() {
$.mobile.changePage("#page2");
}
function ScaricaDatiUser() {
var codAnag = $("#hidCodAnagrafica").val();
if (codAnag == '')
{
Messaggio('you must login to see your ads');
return;
}
s = document.createElement("script");
//var Category = $('#cbbSearchCategory option:selected').val();
s.src = urlSito + "/scriptWS.aspx?func=CaricaDatiListaUser&OnErr=Messaggio&modo=ListaUser&codAnag=" + codAnag
//alert(s.src);
document.body.appendChild(s);
}
//#region Customer
function VaiCustomers()
{
if (ControlloLoggato()==false){return;};
GoPage('#PageListCustomer');
CaricaCustomers();
}
function CaricaCustomers()
{
s = document.createElement("script");
var RCodDitta = $('#hidCodDitta').val();
s.src = urlSito + "/scriptWS.aspx?func=CustomersList&OnErr=Messaggio&modo=GetCustomers&CodDitta=" + RCodDitta;
//alert(s.src);
document.body.appendChild(s);
}
function CustomersList(data) {
$('#DTCustomersList').DataTable({
destroy: true,
data: data,
"columns": [
{ "data": "Cognome" },
{ "data": "Nominativo" },
{
"data": "CodAnagrafica",
"mData": function showIMGLista(data, type, dataToSet) {
var J = JSON.stringify(data);
var CodAnagrafica = data.CodAnagrafica;
return "<img src='IMG\\Detail.jpg' onclick='OpenDetailCustomer(" + CodAnagrafica + ");'>";
}
},
]
});
}
function OpenDetailCustomer(CodAnagrafica)//dal principale annuncio carico tutto
{
s = document.createElement("script");
s.src = urlSito + "/scriptWS.aspx?func=CaricaCurstomerDetail&OnErr=Messaggio&modo=GetDetailAnag&CodAnagrafica=" + CodAnagrafica;
document.body.appendChild(s);
//$.mobile.changePage("#PageAddCustomer");
$(':mobile-pagecontainer').pagecontainer('change', '#PageAddCustomer', {
transition: 'flip',
changeHash: false,
reverse: true,
showLoadMsg: true
});
}
function CaricaCurstomerDetail(J) {
// J = JSON.stringify(J);
//J = JSON.parse(J);
$("#txtInsCliCognome").val(J.Cognome);
$("#txtInsCliNominativo").val(J.Nominativo);
$("#txtInsCliIndirizzo").val(J.Indirizzo);
$("#txtInsCliEmail").val(J.Email);
$("#txtInsCliCitta").val(J.Citta);
$("#txtInsCliTelefono").val(J.Cellulare);
$("#txtInsCliCellulare").val(J.Telefono);
$("#hidCodCustomer").val(J.CodAnagrafica);
}
function ConfirmDeleteCustomer()
{
bootbox.confirm("Are you sure?", function (result)
{
if (result) {
DeleteCustomer();
}
});
}
function DeleteCustomer()
{
var CodAnagrafica= $("#hidCodCustomer").val();
s = document.createElement("script");
s.src = urlSito + "/scriptWS.aspx?func=CaricaCustomers&OnErr=Messaggio&modo=DeleteCustomer&CodAnagrafica=" + CodAnagrafica;
document.body.appendChild(s);
$.mobile.changePage("#PageListCustomer");
}
function SaveCustomer()
{
if (ControlloSaveCustomer() == true)
{
var Cognome= $("#txtInsCliCognome").val();
var Nominativo= $("#txtInsCliNominativo").val();
var Indirizzo= $("#txtInsCliIndirizzo").val();
var Email= $("#txtInsCliEmail").val();
var Citta= $("#txtInsCliCitta").val();
var Cellulare= $("#txtInsCliTelefono").val();
var Telefono= $("#txtInsCliCellulare").val();
var CodAnagrafica= $("#hidCodCustomer").val();
var RCodDitta = $('#hidCodDitta').val();
s = document.createElement("script");
s.src = urlSito + "/scriptWS.aspx?func=DoNothing&OnErr=Messaggio&modo=SaveAnagrafica&CodAnagrafica=" + CodAnagrafica + "&Cognome=" + Cognome + "&Nominativo=" + Nominativo
+ "&Indirizzo=" + Indirizzo + "&Email=" + Email + "&Citta=" + Citta + "&Cellulare=" + Cellulare + "&Telefono=" + Telefono + "&RCodDitta=" + RCodDitta+ "&Cliente=1";
document.body.appendChild(s);
VaiCustomers();
GoPage('#PageListCustomer');
}
// $.mobile.changePage("#PageListCustomer");
}
function ControlloSaveCustomer() {
$("#txtInsCliCognome").removeClass('inputObbligatorio');
$("#txtInsCliCellulare").removeClass('inputObbligatorio')
$("#txtInsCliNominativo").removeClass('inputObbligatorio')
if ($("#txtInsCliCognome").val() == '') { $("#txtInsCliCognome").addClass('inputObbligatorio'); return false; }
if ($("#txtInsCliCellulare").val() == '') { $("#txtInsCliCellulare").addClass('inputObbligatorio'); return false; }
if ($("#txtInsCliNominativo").val() == '') { $("#txtInsCliNominativo").addClass('inputObbligatorio'); return false; }
return true;
}
//#endregion
function CaricaDatiListaUser(data) {
caricaUserDatatable(data);
}
function ControlloLoggato() {
var codAnag = $("#hidCodAnagrafica").val();
if (codAnag == '') {
Messaggio('you must login to manage ads');
return false;
}
}
.img-container { position: relative; }
.img-container .top {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.lblDesc {
font-weight: bold !important;
}
.inputObbligatorio {
border: 2px solid #ff0000 !important;
}
.auto-style1 {
height: 25px;
}
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" />
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<input type="hidden" id="hidCodAnagrafica" value="1"/>
<input type="hidden" id="hidCodDitta" value="1"/>
<div data-role="panel" id="menuPanel" data-mini="true" data-rel="popup" style=" z-index: 1;" class="ui-btn ui-corner-all ui-shadow ui-btn-inline ui-icon-bars ui-btn-icon-left ui-btn-b" data-transition="pop">
<h4>Menu</h4>
<ul data-role="listview">
<!-- <li>List Admin</li>
<li>Your Reports</li>-->
<!-- <li>New Report</li>-->
<li>Customers</li>
<!-- <li>Jobs</li>
<li>Employees</li>-->
<!-- <li>Register</li>
<li>Login</li>
<li><a href="#" onclick='Website2APK.rateUs();'>Rate us</a></li>-->
<!--<img src="img/head2.jpg" />-->
</ul>
</div>
<!--panel-->
<div data-role="page" id="PageListCustomer" style="background-color: #E5E7E9;">
<h2>Customers</h2>
Menu
<table id="DTCustomersList" class="display" style="width: 100%">
<thead>
<tr>
<th>Surname</th>
<th>Name</th>
<th></th>
</tr>
</thead>
</table>
<input class="btn btn-primary" type="submit" onclick="GoPage('#PageAddCustomer');" value="Add" />
</div> <!--- PageListCustomer --->
<div data-role="page" id="PageAddCustomer" style="background-color: #E5E7E9;">
<h2>Customer</h2>
Menu
<input type="hidden" id="hidCodCustomer" />
<form id="frmAddCustomer">
<table style='border-collapse:collapse;' >
<tr>
<td>
<label class="lblDesc">Surname</label>
</td>
<td>
<input type="text" id="txtInsCliCognome" />
</td>
</tr>
<tr>
<td>
<label class="lblDesc">Name</label></td>
<td>
<input type="text" id="txtInsCliNominativo" />
</td>
</tr>
<tr>
<td>
<label class="lblDesc">Address</label></td>
<td>
<input type="text" id="txtInsCliIndirizzo" />
</td>
</tr>
<tr>
<td>
<label class="lblDesc">Email</label></td>
<td>
<input type="text" id="txtInsCliEmail" />
</td>
</tr>
<tr>
<td>
<label class="lblDesc">City</label></td>
<td>
<input type="text" id="txtInsCliCitta" />
</td>
</tr>
<tr>
<td class="auto-style1">
<label class="lblDesc">Mobile Ph.</label></td>
<td class="auto-style1">
<input type="text" id="txtInsCliTelefono" />
</td>
</tr>
<tr>
<td>
<label class="lblDesc">Phone</label></td>
<td>
<input type="text" id="txtInsCliCellulare" />
</td>
</tr>
</table>
<input class="btn btn-primary" type="submit" onclick="SaveCustomer();" value="Save" />
<input class="btn btn-primary" type="submit" onclick=" VaiCustomers();" value="List" />
</form>
</div> <!--- PageAddCustomer --->

Foreach Checked and click event knockout

I would like to add the values of 'Cost' based on checkbox checked event on knock out.
<tbody data-bind="foreach: $root.searchedResults">
<!-- ko if: Id-->
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: Cost"></td>
<td><input type="checkbox" data-bind="checked: $root.searchedResults().length > 0, click: $root.Hospital_click"></td>
</tr>
<!-- /ko -->
</tbody>
And the sum of cost should be displayed in
<div data-bind="text: ko.toJSON(SumofItems)"></div>
The click event is not updating my sum based selection. Can someone help?
self.Hospital_click = function () {
//Suggest
}
My View model is:
var SearchModel = function () {
var self = this;
self.searchFromDate = ko.observable('01/01/2014');
self.searchToDate = ko.observable('01/01/2016');
self.searchedResults = ko.observableArray([]);
self.search = function () {
var model = {
BeginDate: self.searchFromDate(),
EndDate: self.searchToDate()
};
$.ajax({
url: '#Url.Action("SearchSpend", "Analysis", new { Area = "Portal" })',
type: 'POST',
contentType: 'application/json; charset=utf-8',
dataType: "json",
data: ko.toJSON(model),
success: function (response) {
self.searchedResults(response);
}
});
};
You could add a new observableArray with the id's of the selected results (selectedResults). The viewmodel could be:
function Result(id,cost) {
this.Id = id;
this.Cost = cost;
}
function viewModel() {
var self = this;
self.searchedResults = ko.observableArray([]);
self.selectedResults = ko.observableArray([])
self.totalCost = ko.computed(function() {
let total = 0;
for(let p = 0; p < self.searchedResults().length; ++p)
{
if (self.selectedResults.indexOf(self.searchedResults()[p].Id)>=0){
total += self.searchedResults()[p].Cost;
}
}
return total;
});
};
totalCost will return the sum of the Cost field for all selected results.
And the view could be something like this:
<table data-bind="foreach: searchedResults">
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: Cost"></td>
<td><input type="checkbox" value="" data-bind="checkedValue: Id, checked: $parent.selectedResults"></td>
</tr>
</table>
<div data-bind="text: totalCost"></div>
totalCost is a computed value that returns de sum of the Cost for only the selected results.
Here is a fiddle.
In the fiddle, the data comes from the array listOfResults. In your case, this comes from the success function (Ajax request); in this function you also needs to clean the selectedResults.

Jquery Mobile val() returns undefined after changePage

I have 2 pages that I'm working with: first being the page where the values are being fetched from php server and populating the selects/inputs and the second page being a dialog box that fetches the value from the hidden inputs in the first page. The first transition opens the dialog box and fetches the values properly. After which I save the values in php session and reload the first page. After this process when I open the dialog box again the jquery is not able to fetch val() and shows undefined. I'm not sure if this is due to some reloading of the page issue or something else. If I refresh the page then it will work fine again.
<div data-role="page" id="page1">
<div data-theme="a" data-role="header">
.....
<div data-role="navbar" data-iconpos="top">
.....
</div>
<div data-theme="c" id="cashtab" data-role="content">
<div style="display:none" id="proddata" data=""></div>
<div style="display:none" id="prodstock" data=""></div>
<form id="mainsubmit" action="form.php" method="post" data-ajax="false">
<input id="formproduct" type="hidden" name="product" value=""/>
<div id="productsearch" style="width:48%; float:left; margin-right:2%;">
<label for="search">Search Product:</label><br/><br/>
<ul id="productautocomplete" data-role="listview" data-inset="true" data-filter="true" data-filter-placeholder="Select a product... (type at least 3 letters)" data-filter-theme="d"></ul>
</div>
<div id="packingselect" style=" width:23%; float:left; margin-right:2%;">
<label for="packing">Select Packing:</label>
<select name="packing" id="packing" data-iconpos="left">
</select>
</div>
<div id="qtyenter" style=" width:23%; float:left; margin-right:2%;">
<label for="quantity">Select Qty:</label>
<input type="number" data-clear-btn="true" name="quantity" id="qty" value=""/>
</div><br/><br/><br/><br/><br/><br/><br/><br/>
<div style="display:inline-block; width:33%; margin-left:33%; margin-right:33%;">
<a href="#page3" data-rel="dialog" data-role="button" >ADD</a>
</div>
</form>
</div>
</div>
<div data-role="page" id="page3" data-url="dialog.html" data-close-btn="right">
<div data-role="header">
<h1>Batch Selection</h1>
</div>
<div data-role="content">
<div style="overflow:auto;">
<table id="batchsel" style="border:1px;">
<thead>
<tr>
<th></th>
<th>Batch No</th>
<th>Exp Date</th>
<th>Brate</th>
<th>Srate</th>
<th>Packing</th>
<th>Stock</th>
<th>Supplier</th>
<th>ST%</th>
<th>Bill Date</th>
<th>Bill No</th>
<th>btax</th>
</tr>
</thead>
<!--data populated from server once the values from first page is read properly.
<!-- currently not loading the second time as unable to fetch val() -- >
<tbody>
</tbody>
</table>
</div>
<div id="remainingdata">
<p1 id="changeable_requirements"></p1>
<!-- function the send the checked checkboxes relavent info to store in session -->
<button id="saveprod" onclick="addProduct(); return false;">Add Product</button>
</div>
</div>
</div>
<script>
$( document ).on( "pageinit", "#page1", function() {
//for product select autopopulate -- working //
$("#productautocomplete").live( "listviewbeforefilter", function ( e, data ) {
var $ul = $( this ),$input = $( data.input ),value = $input.val(),html = "";
$ul.html( "" );
if ( value && value.length > 2 ) {
$ul.html( "<li><div class='ui-loader'><span class='ui-icon ui-icon-loading'></span></div></li>" );
$ul.listview( "refresh" );
$.getJSON('ajax/getProductList.php', {term:$input.val()}, function(data) {
var items = [];
var str = "";
for (var key in data) {
if (data.hasOwnProperty(key)) {
var value = data[key].value;
var label = data[key].label;
var stock = data[key].stock;
var proddata = data[key].data;
str += '<li code="'+value+'" name="'+label+'" stock="'+stock+'" data="'+proddata+'">';
str += '<a data-ajax="false" rel="external">'+label+' [ '+stock+' ]</a>';
str += '</li>';
}
}
$ul.html( str );
$ul.listview( "refresh" );
$ul.trigger( "updatelayout" );
});
}
});
//end search
//on click set hidden input fields to be used in dialog box. -- working
$('#productautocomplete li').live('click', function(e) {
//--------------------fetch data ------------------------
var id = $(this).attr('code');
var name = $(this).attr('name');
var data = $(this).attr('data');
var stock = $(this).attr('stock');
//add packaging type and unit info to div data
$('#proddata').attr('data',data);
//add currstock info to div
$('#prodstock').attr('data',stock);
//----------------------hide list
$('#productautocomplete li').hide();
//----------------------place name in visible input box
$('#productsearch input').attr('value',name);
//----------------------place id in hidden input box for the actual form.
$('#formproduct').val(id);
//----------------------fill options for package + show select package div
var filteroptions = data.split(",");
$('#packing option').remove();
for (var x=0; x<3 ; x++) {
var eachoption = filteroptions[x].split(":");
//if unit wise option is less than that of stock show as option.
if (eachoption[0]!="0" && eachoption[0] <= stock.valueOf()) {
$('#packing').append($('<option>', {
value: eachoption[0]+':'+eachoption[1],
text : eachoption[1]+' [ '+eachoption[0]+' ] '
}));
}
}
});
});
//this is where the problem lies ..
//have tried with pageinit.. but that only calls it once.
$( document ).on( "pageshow", "#page3", function() {
$('#batchsel tbody').empty();
// !!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!! //
//doesnt fetch any of 4 following values after pageChange back to page1.
//not sure if this is due to how i'm reloading the page1.
//see function addProduct below.
var prodcode = $('#formproduct').val(); //
var prodstock = $('#prodstock').attr('data');
var prodqty = $('#qty').val();
var packing = $('#packing').find(":selected").val();
//returns undefined
alert(prodcode); alert(packing); alert(prodqty);
//always ends here when dialog opens second time.
if (!prodcode || !packing || !prodqty) {
alert("Please give all required information");
//does not close also when opens the second time.
$('#page3').dialog('close');
}
var packinginfo = packing.split(":");
var totalrequired = prodqty * packinginfo[0];
//alert(packinginfo[1]);alert(totalrequired);
if (totalrequired > prodstock ) {
alert("Not enough Stock");
$('#page3').dialog('close');
} else {
//------------------------------ Getting Batch Info ---------------------------------------------------
var rows = '';
$.getJSON('ajax/getBatchDetails.php', {code:prodcode,pack:packinginfo[1],qty:totalrequired}, function(data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
//alert (data[key].Batch);
rows += '<tr><td><input type="checkbox" class="batchcheckbox" id="batchcheckbox_'+data[key].BatchId+'" value="'+data[key].BatchId+':'+data[key].Stock+'" onchange="resetRemainingQty(this.value);""/></td><td>' + data[key].Batch + '</td><td>' + data[key].ExDt +'</td><td>' + data[key].BRate + '</td><td>' + data[key].SRate + '</td><td>' + data[key].Pack + '</td><td>' + data[key].Stock + '</td><td>' + data[key].Supname + '</td><td>' + data[key].Stax + '</td><td>' + data[key].BillDt + '</td><td>' + data[key].BillNo + '</td><td>' + data[key].btax + '</td><tr>';
}
}
$('#batchsel tbody').append(rows);
//add remaining amount in the data field of p1.
$('#remainingdata p1').attr('data',totalrequired);
$('#remainingdata p2').attr('data',totalrequired);
$('#remainingdata p1').html("<h4>Remaining Amount : "+totalrequired+"</h4>");
});
//---------------------------------------------end batch info display: -----------------------------------
}
});
function addProduct() {
//--------code info---------
var prodcode = $("#formproduct").val(); // to send
//--------packing info---------------
var packing = $('#packing').find(":selected").val();
var packinginfo = packing.split(":");
//-----------qty req ---------------------
var prodqty = $('#qty').val();
var totalrequired = prodqty * packinginfo[0]; // to send
//-------------batch info -----------
var allbatchids = "";
$('.batchcheckbox').each(function() {
if($(this).is(':checked')){
var data = $(this).val();
var datasplit = data.split(":");
var batchid = datasplit[0];
allbatchids += batchid+":";
}
});
allbatchids = allbatchids.substring(0, allbatchids.length - 1); // to send
alert(prodcode+",,"+packinginfo[1]+",,"+totalrequired+",,"+allbatchids);
//-------------- send to server to save to session ---------
$.getJSON('ajax/saveProductSession.php', {code:prodcode,pack:packinginfo[1],qty:totalrequired,batch:allbatchids}, function(data) {
if (data.error == "1") {
alert(data.message);
} else {
/// !!!!!!!! !!!!!!!!!!!!!!! !!!!!!!!!!!!!!!
///
/// the loads the page1. but jquery doesnt take val() after this.
///tried multiple variations of this but to no effect.
///removed all options.. redirect to main.php.. reloadpage:false.. etc.
///Any other way to reload the page so that the dialog once open again can
///get the values from the page1 again.
$.mobile.changePage("#page1", { reloadPage: true , dataUrl : "page1", reverse : true, changeHash: true } );
}
});
//
// $.ajax({
// type: "POST",
// url: "ajax/saveProductSession.php",
// data: { code:prodcode,pack:packinginfo[1],qty:totalrequired,batch:allbatchids }
// }).done(function() {});
}
</script>
Ok ! I got it to work ! thanks anyway #Gajotres. Steps :
1a. Send out the variables from main.php through changePage :
var prodcode = $('#formproduct').val();
var prodstock = $('#prodstock').attr('data');
var prodqty = $('#qty').val();
var packing = $('#packing').find(":selected").val();
$.mobile.changePage('batch.php', {
role: 'dialog',
data: {'prodcode': prodcode,'prodstock': prodstock, 'prodqty' : prodqty , 'packing' : packing},
type: 'get'
});
2a. Moved the entire div id 'page3' to a new php page named 'batch.php' where I get the variables from php and set it to the html divs.
<?php
extract($_GET);
if (!$prodcode && !$prodstock && !$packing && !$prodqty) {
header('Location: '.DEF_SITEURL."main.php");
exit;
}
?>
<div data-role="page" id="batchpage" data-url="batch.php" data-close-btn="right">
<div data-role="header">
<h1>Batch Selection</h1>
</div>
<div data-role="content">
<div style="display:none;" id="batchprodcode" data="<?php echo $prodcode; ?>"></div>
<div style="display:none;" id="batchprodstock" data="<?php echo $prodstock; ?>"></div>
<div style="display:none;" id="batchpacking" data="<?php echo $packing; ?>"></div>
<div style="display:none;" id="batchqty" data="<?php echo $prodqty; ?>"></div>
<div style="overflow:auto;">
<table id="batchsel" style="border:1px;">
<thead>
<tr>
<th></th>
<th>Batch No</th>
<th>Exp Date</th>
<th>Brate</th>
<th>Srate</th>
<th>Packing</th>
<th>Stock</th>
<th>Supplier</th>
<th>ST%</th>
<th>Bill Date</th>
<th>Bill No</th>
<th>btax</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="remainingdata">
<p1 id="changeable_requirements"></p1>
<button id="saveprod" onclick="addProduct(); return false;">Add Product</button>
</div>
</div>
</div>
3a. Then I just change the pageshow that i was using for page3 to the new div that is created on batch.php. The script still runs on main.php.
$( document ).on( "pageshow", "#batchpage", function() {
$('#batchsel tbody').empty();
var prodcode = $('#batchprodcode').attr('data');
var prodstock = $('#batchprodstock').attr('data');
var prodqty = $('#batchqty').attr('data');
var packing = $('#batchpacking').attr('data');
...
});

MVC Html helper to make a label editable

I have a html table generated in my view, does anyone know of any helpers available that I could use so that one of the fields could be edited in-line.
View:
<table>
<caption>Configuration values for current management group</caption>
<thead>
<tr>
<th scope="col">Device Type</th>
<th scope="col">Section</th>
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col">Operation</th>
</tr>
</thead>
<tbody>
#foreach (var param in Model.ParamData)
{
<tr>
<td>#param.DeviceType</td>
<td>#param.Group</td>
<td>#param.Name</td>
<td>#param.Value</td>
<td>#(param.IsMerge ? "Merge" : "Delete")</td>
</tr>
}
</tbody>
</table>
As you can see there is nothing special here, I would like an edit column that would work in a similar way to a web forms gridview. The only field to be edited would be value, and it would always be a textbox.
Im sure people must have done this before but the only example Ive seen on line was for mvc 1.
I could knock something up using jquery but am sure there must be loads of examples already and dont want to re-invent the wheel.
Ive done it myself, if anyones interested:
#foreach (var param in Model.ParamData)
{
<tr>
<td>#param.DeviceType</td>
<td>#param.Group</td>
<td>#param.Name</td>
<td>
<div class="#("ViewValueDiv_" + param.ParamaterValueId)">
#param.Value
</div>
<div class="#("EditValueDiv_" + param.ParamaterValueId)" style="display:none;">
<input type="text" name="#("EditValue_" + param.ParamaterValueId)" value="#param.Value" class="#("Input_" + param.ParamaterValueId)" />
</div>
</td>
<td>#(param.IsMerge ? "Merge" : "Delete")</td>
<td>
<div class="#("EditButtonDiv_" + param.ParamaterValueId)">
<input type="button" value="Edit" class="EditButton" Id="#param.ParamaterValueId" />
</div>
<div class="#("UpdateCancelButtonDiv_" + param.ParamaterValueId)" style="display:none;">
<input type="button" value="Update" class="UpdateButton" id="#("U" + param.ParamaterValueId)" />
<input type="button" value="Cancel" class="CancelButton" id="#("C" + param.ParamaterValueId)" />
</div>
</td>
</tr>
}
$(document).ready(function () {
$(".EditButton").click(function () {
var id = $(this).attr('id');
$(".ViewValueDiv_" + id).hide();
$(".EditValueDiv_" + id).show();
$(".EditButtonDiv_" + id).hide();
$(".UpdateCancelButtonDiv_" + id).show();
oldvalue = $(".Input_" + id).val();
});
$(".CancelButton").click(function () {
var id = $(this).attr('id').substr($(this).attr('id').indexOf("C") + 1);
$(".ViewValueDiv_" + id).show();
$(".EditValueDiv_" + id).hide();
$(".EditButtonDiv_" + id).show();
$(".UpdateCancelButtonDiv_" + id).hide();
$(".Input_" + id).val(oldvalue);
});
$(".UpdateButton").click(function () {
var id = $(this).attr('id').substr($(this).attr('id').indexOf("U") + 1);
NewValue = $(".Input_" + id).val();
if (NewValue) {
$.ajax({
url: "/Terminals_configuration/UpdateConfigValue",
data: { valueId: id, newValue: NewValue },
dataType: "json",
type: "POST",
error: function () {
alert("An error occurred.");
},
success: function (data) {
$(".ViewValueDiv_" + id).show();
$(".EditValueDiv_" + id).hide();
$(".EditButtonDiv_" + id).show();
$(".UpdateCancelButtonDiv_" + id).hide();
$(".ViewValueDiv_" + id).html(NewValue);
}
});
} else {
alert("You didn't supply a new value");
}
});
});

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