1

我的视图模型如下,使用 knockout.js 绑定到我的页面。Episodes 是一个实体数组,每个实体都有一个布尔属性“ListenedTo”,绑定到我页面上的复选框。

function episodesViewModel() {

    this.episodes = ko.observableArray();

    this.sync = function () {
        repository.populateEpisodes(this);
    }

    this.save = function () {
        repository.saveChanges();
    }

    //this.episodes.subscribe(function (episodes) {
    //    ko.utils.arrayForEach(episodes, function (episode) {
    //        episode.ListenedTo.subscribe(function () {
    //            window.repository.saveChanges();
    //        });
    //    });
    //});

    // perform an initial sync
    this.sync();   
}

存储库对象只是底层 Breeze 调用的包装器。

如果我将 save() 绑定到单击按钮,一切都会按预期工作,微风看到有更改并回调服务器。如果我取消注释创建对 ListenedTo 更改的订阅的行(并且无需单击按钮即可保存),则会发生奇怪的事情。

页面加载并填充后,第一次单击复选框会按预期自动调用微风 saveChanges()。即使 viewmodel 中的值符合预期并且与复选框匹配,但微风 hasChanges() 返回 false 并且不进行服务器调用。再次单击它,hasChanges 现在为 true,并进行了服务器调用!?就像 Breeze 落后并且没有将更改注册到视图模型上的实体。

有任何想法吗?

4

2 回答 2

4

天啊。你的代码有大麻烦!在深入研究细节之前,跳出页面的是您可能会在episodes每次数组更改时重复地向数组中的每一集添加订阅。哎呀!

事件传播延迟

在我继续我的疑虑之前,我想承认您的具体经验,即您的听众何时触发与manager.hasChanges. 那是真的。KO 订阅在EntityManager听说更改之前就被触发了……这就是它报告错误的原因。变化的消息还没有到经理听到。

我也注意到了这一点。事实上,如果您查看TodoSample中的viewModel.js,您会发现我们通过添加一个setTimeout来解决它,让 Breeze 有机会听到更改:

// 使用 Breeze PropertyChanged 事件监听变化
item.entityAspect.propertyChanged.subscribe(function () {
    if (suspendItemSave) { return; }
    // 给 EntityManager 时间来听到变化
    setTimeout(saveIfModified, 0);

    函数 saveIfModified() {
        if (item.entityAspect.entityState.isModified()) {
                数据服务.saveChanges();
        }
    }
});

我希望我知道另一种方式。那只是KO和Breeze之间的时间问题。我们还没有弄清楚如何巧妙地处理它。不确定我们能否做到。

模型规则还是视图规则?

让我们备份。无论它出现在什么视图中,都应该episode在其listenedTo属性更改时保存吗? 如果您的回答是“是”,那么您有一个真正属于该模型的应用程序业务规则,并且您确实需要监听情节变化的东西。

但是如果答案是“否”......如果保存应该由“复选框”触发,而不是对对象的更改,那么这是一个 UI 规则......一个视图规则......你应该使用 KO听checkbox,而不是 episode 属性。

实体更改时保存

让我们来看第一种情况,并断言您的应用程序规则如下:“总是在listenedTo更改时保存,无论是什么原因导致它更改”。

我知道在TodoSample中,我们演示了一种直接监听实体以更改其一个属性(其任何属性)的方法。这很强大,而且很直观。

我开始相信这不是最安全的方法。在具有共享同一实体的多个 ViewModel 的应用程序中,它可能会导致内存泄漏。这个“待办事项”应用程序只有一个屏幕,因此不必担心。但在更大的应用程序中......我会担心。

所以我建议不要监听Episode属性的变化。而是EntityManager 查看最近发布的Breeze SPA 模板中datacontext.js的这个片段。

功能 configureManagerToSaveModifiedItemImmediately() {
    manager.entityChanged.subscribe(entityStateChanged);
    
    功能实体状态改变(参数){
        如果(args.entityAction === 微风.EntityAction.EntityStateChange){
            var 实体 = args.entity;
            if (entity.entityAspect.entityState.isModified()) {
                保存实体(实体);
            }
        }
    }
}   

注意它如何监听 EntityManager 缓存的任何实体的状态变化。它只对转换到“已修改”状态感兴趣。检测到时,它会保存实体。

现在这对你来说可能太宽泛了。但是您可以想象注册额外的过滤逻辑来满足您的应用程序的特定需求。

保存队列

与自动保存有关的新问题。无论您如何触发保存,这个问题都很重要。

用户可以很快点击。她一定会比处理请求更快地触发保存请求。Breeze(默认情况下)EntityManager在等待服务器从挂起的保存操作返回结果时不会再次保存。它会抛出异常。

它必须等待,因为它无法将实体的状态从已更改更改为未更改,直到它知道保存是否成功。如果保存失败,您希望将实体保持在“未保存”状态。

查看datacontext.js顶部的manager.enableSaveQueuing(true). 这不是微风的原生功能。这是插件的一个功能,Scripts/breeze.savequeuing.js 你会想要加载这个插件。在Breeze SPA 模板的文章中阅读它

为什么这么复杂

你可能认为Breeze这个东西非常复杂。实际上,引入复杂功能的不是 Breeze。您希望在实体更改其状态时触发保存,这会增加复杂性。

我并不是说你这样做是错误的。我的意思是,这种方法是 Breeze 提供的一个机会,在实施过程中需要小心谨慎。

如果没有 Breeze,您将完全无法跟踪实体状态。因此,您唯一真正安全的选择是根据对复选框的更改来触发保存……在这种情况下,所做的工作并不比将 KO 绑定到复选框更复杂。

嗯......好吧......由于您绊倒的Breeze / KO时间问题,它有点复杂,这需要setTimeout开局。但我希望你能明白我的意思。

于 2013-03-04T03:29:43.843 回答
0

没有更多信息很难知道(例如,使用了哪些其他框架,什么触发了 VM 实例化代码,KO.ApplyBind 在哪里) 在创建 VM 时您的视图是否正确构建?

我现在使用 Durandal,它首先创建 VM,然后是视图。尝试在 Durandal 的激活事件期间创建额外的订阅(或更早,如您的实例化代码),导致 KO 视图/viewModel 订阅被处置,因为它们被发现是无效的。我必须在 Durandal 之后的 viewAttached 事件中创建订阅

稍后尝试将您的订阅放在工作流程中。

于 2013-03-04T02:48:50.410 回答