我在订阅 propertyChanged 事件时遇到问题,订阅的事件确实会为实体修改触发,但在将实体设置为已删除时从未触发。我可能做错了什么。
我所做的目标是,每当用户修改行时,我想在行级别提供按钮以取消更改。同样,当用户删除一行时,我想提供一个按钮来取消删除一行。修改部分按预期工作,但对于删除它不起作用。
我期待 item.entityAspect.setDeleted() 会触发 propertyChanged 事件,以便我可以更新 observable IsDeleted 的值,这反过来会更新按钮的可见性。
视图模型
/// <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
看法
<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 }"><a href="#" data-bind="click: previousPage">Previous</a></li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.moveToPage($data.pageNumber-1); }"></a></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }"><a href="#" data-bind="click: nextPage">Next</a></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>