【问题标题】:Knockout.js: Using nested observables with mapping pluginKnockout.js:使用带有映射插件的嵌套 observables
【发布时间】:2013-01-07 04:08:06
【问题描述】:

我正在构建一个侧边栏来过滤主视图,例如John Lewis 中的那个。我的代码可以运行,但它并不漂亮。

我知道有几个类似的问题,但我不能完全理解我自己的用例。

我需要从服务器(例如通过 JSON)获取复选框的名称,以便在我的 ShopView 上动态创建 observableArrays。

它是这样的:

var data = {
    'gender' : [ ],
    'color' : [ ]
};

var filterMapping = {
    create: function( obj ) {
        return ko.observableArray( obj.data );
    }
}

var ShopView = new function() {

    var self = this;

    ko.mapping.fromJS( { filters: data }, filterMapping, self );

    // this is the bit I don't like
    this.filterChange = ko.computed(function () {
        for( var key in self.filters )  {
            var obj = self.filters[key]; 
             if( ko.isObservable(obj)){
                obj();                 
             }             
        }
    });

    this.filterChange.subscribe( function( ) {
        //make AJAX request for products using filter state
    });

}

我的 HTML 看起来和你期望的一样:

性别

    <ul>
        <li><input type="checkbox" value="male" data-bind="checked: filters.gender" />Male</li>
        <li><input type="checkbox" value="female" data-bind="checked: filters.gender" />Female</li>
    </ul>

正如我所说,它有效,但并不好。在理想的世界中,我可以订阅 this.filters,例如

this.filters.subscribe( function() { 
    //make AJAX request for products using filter state
});

注意我不想在客户端进行过滤 - 只是在动态绑定的复选框更改时更新我的​​视图模型。

有什么想法吗?谢谢!

【问题讨论】:

  • 是什么阻止您订阅this.filters?只要您在ko.mapping.fromJS 通话后订阅就可以了。
  • this.filters 不是可观察的 - 它只是一个对象属性
  • 您是否同时控制了客户端和服务器代码?你愿意改变你的视图模型吗?
  • @Tyrsius 是的,我愿意。是的,自然 :-)

标签: javascript knockout.js knockout-mapping-plugin


【解决方案1】:

首先,映射插件应被视为代码重复的辅助工具。我认为将映射插件本身视为解决方案并不是一个好主意。至少不是直接的。它还掩盖了您在 SO 上发布代码时发生的情况,因为我们看不到您正在使用的模型。只是一个想法。

现在,如果您想从服务器获取动态过滤器,并使用它们来过滤项目列表(就像您在商店中一样),我会这样做(这里是 the fiddle):

var FilterOption = function(name) {
    this.name = name;
    this.value = ko.observable(false);
};

var Filter = function(data) {
    var self = this;
    self.name = data.name;
    options = ko.utils.arrayMap(data.options, function(o) {
        return new FilterOption(o);
    });
    self.options = ko.observableArray(options);
    self.filteredOptions = ko.computed(function() {
        var options = []
        ko.utils.arrayForEach(self.options(), function(o) {
            if (o.value()) options.push(o.name);
        });
        //If no options, false represents no filtering for this type
        return options.length ? options : false;
    });
};

var ViewModel = function(data) {
    var self = this;
    self.items = ko.observableArray(data.items);
    filters = ko.utils.arrayMap(data.filters, function(i) {
        return new Filter(i);
    });
    self.filters = ko.observableArray(filters);
    self.filteredItems = ko.computed(function() {
        //Get the filters that are actually active
        var filters = ko.utils.arrayFilter(self.filters(), function(o) {
            return o.filteredOptions();
        });
        //Remove items that don't pass all active filter
        return ko.utils.arrayFilter(self.items(), function(item) {
            var result = true;
            ko.utils.arrayForEach(filters, function(filter) {
                var val = item[filter.name.toLowerCase()];
                result = filter.filteredOptions().indexOf(val) > -1;
            });
            return result;
        });
    });
};

下一个明显的步骤是添加对具有多个属性或选项属性的项目的支持,但这应该会给您基本的想法。您有一个过滤器列表,每个过滤器都有任意数量的选项(可叠加),并且您使用计算项数组来存储过滤项的结果。


编辑:要使用 ajax 订阅获取项目,您可以将 FilteredItems 属性替换为获取选定过滤器的计算属性,然后订阅它,如下所示:

var ViewModel = function(data) {
    var self = this;
    self.items = ko.observableArray(data.items);
    filters = ko.utils.arrayMap(data.filters, function(i) {
        return new Filter(i);
    });
    self.filters = ko.observableArray(filters);
    self.selectedFilters = ko.computed(function() {
        ko.utils.arrayFilter(self.filters(), function(o) {
            return o.filteredOptions();
        });
    });
    self.selectedFilters.subscribe(function() {
        //Ajax request that updates self.items()
    });
};

【讨论】:

  • +1。干得好!谢谢。这种方式超越了我的尝试,因为您在客户端非常优雅地进行了实际过滤。但是在我的应用程序中,所发生的只是 JS 在 ajax 请求中将过滤器发送到服务器并取回经过适当过滤的产品。对不起,我没有在问题中更清楚地说明这一点......不幸的是,它仍然存在。我是否需要使用那个丑陋的 ko.computed 函数来使我的 ShopView.filters '可观察'?
  • @djb 这取决于很多事情。如果您在服务器上进行所有过滤,那么不会。如果您在客户端进行过滤,则需要一个计算函数,但有一种更短的编写方法。我写了长版本,所以你可以看到发生了什么,但filters 过滤器和items 过滤器可以组合(它更丑,IMO,更难理解)。 Computed observables 是一个非常强大的 Knockout 工具,不应避免使用。这些文档非常有用,甚至在他们到达ObservableArrays 之前就已经触及它们。我会给出一个带有服务器响应的答案。
  • @djb 在我写另一个您不想要的答案之前,您能否更具体地说明您所追求的是什么。您是否试图完全避免 computed observables? (我不认为这是明智的)。您只是想弄清楚订阅的样子吗?
  • 嘿@Tyrsius - 感谢您的耐心等待。我完全不介意computeds,我很乐意使用它。对我来说似乎很奇怪,我需要遍历 self.filters 的整个集合,在每个集合上调用 observable,只是为了订阅 self.filters 成员的变化。我宁愿首先声明它是可观察的,并将我的回调附加到 self.filters.subscribe(),但这似乎不起作用......
  • @djb 简而言之:没有。 self 上的 filters 数组是 filters 的可观察列表(可能名称混淆),但这些对象每个都有一个选择列表(颜色可以是“白色”和“红色”)。这是我们实际想要订阅其更改的数组,因为当您选择过滤器的选项时它会更改。拥有订阅的唯一方法是将选定过滤器列表作为其自己的ObservableArray 在主视图模型上,我想不出一个好的方法来做到这一点。使 self.filters 可观察并不能使其所有属性都可观察。
猜你喜欢
  • 2016-01-25
  • 2013-08-05
  • 1970-01-01
  • 2022-06-15
  • 2021-07-06
  • 2019-12-27
  • 2021-03-01
  • 1970-01-01
相关资源
最近更新 更多