【问题标题】:Knockout JS ObservableArray with many-to-many relationships具有多对多关系的 Knockout JS ObservableArray
【发布时间】:2012-07-09 09:39:42
【问题描述】:

我正在使用 Knockout.js 创建一个客人列表应用程序,到目前为止一切进展顺利。但是,我有一个最佳实践问题。我的应用程序有几种不同类型的对象:客人和标签。客人可以有多个标签,标签可以有多个客人。在应用程序的不同点,我需要分别显示两个数组。例如,我有一个“客人”视图,可以看到所有客人及其关联的标签,我还有一个“标签”视图,可以看到所有标签及其关联的客人。目前,我为客人添加标签的代码如下所示:

var tag = function(opts) {
  this.guests = ko.observableArray()

  // Other tag code here...
}

var guest = function(opts) {
  this.tags = ko.observableArray()
  // Other guest code here...

  var self = this

  this.addTag = function(tag) {
    self.tags.push(tag)
    tag.guests.push(self)
  }
}

我知道除了独立更新每个 observableArray 之外,在 Knockout 中必须有更好的方法来处理这种多对多关系。这也导致了一种递归应用程序结构,其中客人有一个标签属性/数组,其中包含一个标签,它有一个客人属性/数组,其中包含一个客人,它有一个标签属性......你明白了。

现在,ViewModel 结构是这样的:

- Parent Object
  - Guests ObservableArray
    - Guest Object
      - Tag Object as property of Guest
  - Tags ObservableArray
    - Tag Object
      - Guest Object as property of Tag

所以我想我的问题是双重的:1) 有没有更好的方法来构造我的 ViewModel 以避免递归数组?和 2) 如何更好地使用 Knockout.js 以 DRY 方式更新我的 ViewModel,而不是单独更新 tags 数组和 guest 数组?谢谢!

【问题讨论】:

  • 我的回答有问题吗?如果您让我知道出了什么问题,我可以尝试改进它。
  • 嘿 Tyrsius,我喜欢这个答案,这就是我投票的原因,但我觉得它并没有完全解决结构问题。我同意保存 ID 并使用计算函数进行查找比保存完整对象更好,但是如果我使用许多不同的 Guest 和 Tags 实例,每个实例都需要能够遍历它们各自的关联,我该怎么做在你的模型中这样做?目前,您的答案似乎只有在我想一次遍历一个对象时才有效。对不起,如果我太挑剔了,这有点新。
  • 所以,我不完全确定你的意思。您想一次查看多个“selectedTags”吗?顺便说一句,挑剔很好。你有一个特定的问题,你需要帮助。挑剔=)
  • 是的,你可以这样说。我需要同时为每位客人提供“selectedTags”视图。不同之处在于,在您的模型中,我一次选择一个客人或标签,因此您可以在父 ViewModel 上创建“selectedGuests”和“selectedTags”。但是,如果我想一次显示多个客人,每个客人都有自己的标签列表,并且一次显示多个标签,每个标签都有自己的客人列表,我应该如何构建内容以便我可以轻松更新两者?谢谢!
  • 是的,这确实需要不同的视图模型。我给你煮点东西。

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


【解决方案1】:

可能还有其他方法可以做到这一点,但这种方法的重复性非常小,而且不会牺牲适当的建模。服务器生成这种格式的数据应该没有问题。

这里是(粗略风格的)fiddle。请注意,单击标签或访客将导致其下方的选择更新(默认选择第一个)。

基本上,通过 id 存储关系,并使用computed 数组来表示关联。这是一个基本的视图模型:

var ViewModel = function(guests, tags) {
    var self = this;
    self.guests = ko.observableArray(
        ko.utils.arrayMap(guests, function(i){ return new Guest(i); }
    ));
    self.tags= ko.observableArray(
        ko.utils.arrayMap(tags, function(i){ return new Tag(i); }
    ));

    self.selectedGuest = ko.observable(self.guests()[0]);
    self.selectedTag = ko.observable(self.tags()[0]);

    self.guestTags = ko.computed(function() {
        return ko.utils.arrayFilter(self.tags(), function(t) {
            return self.selectedGuest().tags().indexOf(t.id()) > -1;
        });        
    });

    self.tagGuests = ko.computed(function() {
        return ko.utils.arrayFilter(self.guests (), function(g) {
            return self.selectedTag().guests().indexOf(g.id()) > -1;
        });        
    });
};

更新

所以我做了一个new fiddle 来演示一种不同的映射,但是这段代码可以很容易地与上面的视图模型共存;它仅用于演示。它不是处理选择,而是提供一般查找,以便任何代码都可以使用它。下面是来自标签的 HTML(来宾是对称的),以及添加到视图模型的 guestMap 函数。

您会注意到名称现在是inputs,因此您可以更改名称并观察所有绑定保持最新状态。让我知道你的想法:

<div>Tags
    <ul data-bind="foreach: tags">
        <li>
            <input data-bind="value: name, valueUpdate: 'afterkeydown'" />
            </br><span>Tags</span>
            <ul data-bind="foreach: guests">
                <li><span data-bind="text: $parents[1].guestMap($data).name()"></span></li>
            </ul>
        </li>
    </ul>
</div>

self.guestMap = function(id) {
        return ko.utils.arrayFirst(self.guests(), function(g) {
            return id == g.id();
        });
    };   

【讨论】:

  • 谢谢提瑞斯!最后一个问题:有没有办法存储数据以避免冗余?我的意思是,现在你在每个客人上列出他们的每个标签,在每个标签上你列出他们的每个客人。但是这个数据是相同的;如果我想为其中一位客人添加标签,我需要同时更新 Guest 数组和 Tags 数组。有什么办法可以避免这种情况,所以我可以只更新其中一个数组?再次感谢!
  • 嗯。这是我早该想到的。我在想这些关系是分开的,所以客人可以有一个标签,而该标签不需要客人。关系数据库解决方案在这里可能很好,实际上有一个 tagGuest 对象,它只存储了一组 Id 对。我看看能不能想出办法
  • 肯定这个解决方案有效,但看起来效率很低,因为每次更改都会触发重新计算相关元素的搜索。更好(但更复杂的解决方案)应该将找到的关系缓存到索引结构中,因此每次更改只需要更有效的索引更新
  • @FrancescoAbbruzzese 确实如此,这一切都是非常即兴的,我确信存在许多优化(或直接更好的方法)。我很想看看你的解决方案。
  • 有各种可能性,每种可能性都有优点和缺点,例如:1)每个对象都有一个数组,其中包含与其相关的所有节点(在关系的两侧)。有两个方法 addRelation(node1, node2) 和 deleteRelation(node1, node2)。添加,新节点和关系非常快速地完成,删除关系只需要扫描两个数组......但是删除一个节点的成本非常高:扫描与相关节点一样多的数组,成本是二次方的。逻辑删除可能会改善,因为消除了处理缓存在对象中的关系的需要
【解决方案2】:

我遇到了同样的问题。显示多对多相关的东西。我必须进行“网格”样式显示并在其中包含更新机制。

我最终复制了我在后端数据库中的结构。两个项目表,中间有连接表。从三个数组中提取数据并不断更新“加入”数组。测试数据和我的测试用它在下面。

var items = [{name: "T1",id: 1}, {name: "T2",id: 2}, {name: "T3",id: 3}, {name: "T4",id: 4}];
var cats = [{catname: 'C1', catid: 1}, {catname: 'C2',catid: 2}, {catname: 'C3',catid: 3}, {catname: 'C4',catid: 4}];
var catsItems = [{catid:1,itemid:2},{catid:2,itemid:1},{catid:4,itemid:2},{catid:3,itemid:4},{catid:3,itemid:1},{catid:1,itemid:3},{catid:2,itemid:4}];

Join table fiddle

我不太确定这种方法在处理大量数据时的效率如何,但它完成了我需要它做的事情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多