1

我在订阅 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>
4

1 回答 1

3

分析

propertyChanged当 ... 属性更改时引发该事件。但这不是你想看的。你想监控entityAspect.entityState

当您将属性设置为新值(例如person.FirstName("Naunihal"))时,您会同时获得 propertyChanged 事件和实体的EntityState.

当您删除实体时,实体的EntityState更改 ... 为“已删除”。但是删除不会改变实体的属性。Breeze 不认为EntityState自身是实体的属性。因此,没有propertyChanged通知。

解决方案

2013 年 1 月 12 日更新 我认为如果我重新表述您提出的问题以便人们了解您想听取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.

于 2013-01-12T08:48:59.643 回答