【问题标题】:Knockout.js modifying an item with multiple locations in the viewmodel (aka: a 1-many relationship)Knockout.js 在视图模型中修改具有多个位置的项目(又名:一对多关系)
【发布时间】:2013-04-22 00:52:52
【问题描述】:

使用 Knockout.js:

如果一个项目/对象位于多个位置的视图模型中(相同的确切对象),是否可以将两个项目绑定在一起,以便如果用户在一个位置编辑项目,另一个项目是也自动更新?

示例:http://jsfiddle.net/77Jc5/1/

var model = {
    Foo : [
        {
            ID: 99,
            Title: 'This is item number ninety-nine',
            Description: 'This is an item included in more than one object'
        },
        {
            ID: 100,
            Title: 'This is item number one-hundred',
            Description: 'This is an item included in only one object'
        }
    ],
        Poo : [
        {
            ID: 99,
            Title: 'This is item number ninety-nine',
            Description: 'This is an item included in more than one object'
        }
    ]
};

在示例中,当用户在 Foo 或 Poo 中编辑 Item #99 时,我希望它在两者中都进行更改...最好是在用户更改它时(按键事件?)。

解决方案

你的模型应该是这样的:

var model = {
    AllMembers: [
        { ID: 100, Title: 'This is item number 100' },
        { ID: 776, Title: 'This is item number 776' },
        { ID: 456, Title: 'This is item number 456' },
        { ID: 999, Title: 'This is item number 999' }
    ],
    Foo: [],
    Poo: []
};

解决方案 1:(感谢@Joseph Gabriel)

http://jsfiddle.net/lamarant/hMutz/8/

在此解决方案中,Foo 和 Poo 数组将包含对象的重复实例。 Knockout 负责对象之间的同步。这可行,但从架构的角度来看,它并不理想,因为数据在整个视图模型中都是重复的。

解决方案 2:(感谢 @Jeff Mercado)

http://jsfiddle.net/jeff1203/rndM9/2/

在这个解决方案中,Foo 和 Poo 数组将只包含被引用对象的 ID。在架构上,我认为这是正确的方法,但它也以更多代码为代价。

如果您正在阅读本文,最终您可能会将视图模型发布到服务器进行处理,这可能需要将模型保存回您的数据库。这两种解决方案都能充分处理数据库中存在的一对多关系。在我的要求中,我还使用连续负数作为 ID 号(ID:-1、ID:-2 等)向 AllMembers 数组添加新元素。服务器端的处理顺序应该是这样的:

  1. 创建一个数组,将负 ID 映射到数据库中相应的数据库新创建的 ID
  2. 遍历 AllMembers 数组中的成员并更新/创建数据库中的每个成员。如果创建新成员,请将其负数和新 ID 添加到您在步骤 1 中创建的数组中。
  3. 删除数据库中 Foo/Poo 之间所有现有的 1 对多关系,因为您的模型现在是该信息的主人
  4. 循环遍历 Poo 和 Foo,并将模型中与 Foo/Poo 的所有关系添加到 1-many 表中(使用 ID)

呼……

淘汰赛很棒。

【问题讨论】:

    标签: javascript mvvm knockout.js


    【解决方案1】:

    要记住的主要事情是,淘汰赛将更新它绑定到的任何对象,但为了使同一个对象可更新并反映多个位置的更改,它确实必须是 相同的对象,而不仅仅是一个克隆

    两个具有相同名称和/或属性的对象是不够的。它实际上必须是一个对象,指向同一个内存地址,然后可以在多个位置显示和绑定。

    我会考虑将您的数据从您的 viewModel 中分离出来,以便您可以根据需要从尽可能多的地方引用相同的项目。

    var items = [{
        ID: 99,
        Title: 'This is item number ninety-nine',
        Description: 'This is an item included in more than one object'
    }, {
        ID: 100,
        Title: 'This is item number one-hundred',
        Description: 'This is an item included in only one object'
    }];
    
    var model = {
        Foo : [
            items[0],
            items[1]
        ],
            Poo : [
            items[0]
        ]
    };
    var viewModel = ko.mapping.fromJS(model);
    ko.applyBindings(viewModel);
    

    也就是说,直接在视图模型中包含重复引用似乎有点尴尬。我不知道您的情况是什么,但是可以使用 Ko。计算给你“便便”值,反之亦然?

    【讨论】:

    • 谢谢。看看我的问题的更新。我正在使用这种方法,但我现在需要能够将“items”数组中的项目动态添加到 Foo 和 Poo 中,并且仍然保留所有项目之间的链接......接近:jsfiddle.net/77Jc5/12。 KO 新手,但 KO.computed 听起来可能是正确的方法?
    • 您开始遇到问题,因为您将模型定义为纯 JS 对象,然后将其转换为可观察对象。在addMember 内部,被引用的 AllMembers 持有对普通 JS 版本的引用。你所拥有的很接近,但是很难可靠地工作,因为参考不是你所期望的。最好从一开始就按照您想要的方式设置可观察对象。我还建议您重新构建视图模型。也许像这样: var vm = {}; vm.allMembers = ...; vm.addMember = function();/ ... return vm;
    • 导致问题的一个主要原因是模板中的 $data 不是可观察的。为了获得 observable,您可以在模板上设置 data 属性之前将值包装在对象中。像这样:stackoverflow.com/a/8989907/91189
    • 这个怎么样:jsfiddle.net/77Jc5/15。这个有效。我在这里打破了任何惯例吗?
    • 不幸的是,由于映射插件的工作方式,最终的映射视图模型将包含完全不同的实例。您需要将项目添加到FooPoo模型被映射之后。
    【解决方案2】:

    理想情况下,同一视图模型中的任何对象最多只能有一个副本。如果你能帮上忙,重构你的视图模型,这样你的对象就可以引用一个包含所有对象的数组/映射来最小化复制。然后你的实际对象只需要获取引用的对象。

    var model = {
        _FooRefs: [99, 100],
        _PooRefs: [99],
        _AllObjects: [
            {
                ID: 99,
                Title: 'This is item number ninety-nine',
                Description: 'This is an item included in more than one object'
            },
            {
                ID: 100,
                Title: 'This is item number one-hundred',
                Description: 'This is an item included in only one object'
            }
        ]
    };
    
    function ViewModel(data) {
        var self = this;
        ko.mapping.fromJS(data, {
            _AllObjects: {
                key: function (obj) {
                    return obj.ID;
                }
            }
        }, self);
    
        self.Foo = ko.computed(function () {
            var fooRefs = ko.utils.unwrapObservable(self._FooRefs);
            return ko.utils.arrayMap(fooRefs, function (id) {
                // mappedGet was added to mapping plugin version 2.4.1
                return self._AllObjects.mappedGet({ ID: id });
            });
        });
    
        self.Poo = ko.computed(function () {
            var pooRefs = ko.utils.unwrapObservable(self._PooRefs);
            return ko.utils.arrayMap(pooRefs, function (id) {
                return self._AllObjects.mappedGet({ ID: id });
            });
        });
    }
    

    fiddle

    要将对象添加到数组中,您需要确保该对象存在于您的数组/映射中,然后将对象的键添加到引用数组中。要删除项目,请删除引用并可选择删除对象。


    如果这样做不是一个选项,并且您需要保持重复,您可以使用postbox 插件来帮助保持对象同步。如果对象存在于不同的视图模型中,这种方法将是最好的。

    【讨论】:

    • 谢谢。我正在重构我的代码,看看这是否可行。动态添加和删除项目时遇到问题。看看这里:jsfiddle.net/hMutz/5。 self.addMember 和 self.removeMember 方法难倒我。帮助将不胜感激。
    • 我已经更新了小提琴来演示:jsfiddle.net/jeff1203/XZGek/3。它可能设计得更好,当你必须处理可观察对象时它会变得相当混乱。
    • 这太棒了。我认为这两个版本都有效,但在架构上这是在视图模型中表示数据的正确方法。我在小提琴中添加了一些调试信息:jsfiddle.net/XZGek/5。感谢您的帮助!
    • 为了正确起见,您应该进行一个小修复。否则,如果您不这样做,您的应用程序可能会在某些情况下崩溃。在扩展程序的addObject() 方法中,target.push(id) 行应该在对象被添加到对象的完整映射之后出现。在添加引用之前,该对象必须存在于 mappedObjects 数组中。 jsfiddle.net/jeff1203/rndM9/2
    【解决方案3】:

    如果你写了这样的代码:

    var num99 = {
                ID: 99,
                Title: 'This is item number ninety-nine',
                Description: 'This is an item included in more than one object'
            };
    
    var model = {
        Foo : [
            num99,
            {
                ID: 100,
                Title: 'This is item number one-hundred',
                Description: 'This is an item included in only one object'
            }
        ],
            Poo : [
            num99
        ]
    };
    

    那么FooPoo 确实指向同一个对象,因此更改一个对象会更改另一个对象。

    jsFiddle example

    【讨论】:

    • -1:这样做并不能解决问题。想象一下有数千个来自服务器的 JSON 项目。你不应该像那样复制每个项目。
    • 它解决了所描述的问题。 OP 没有提到需要支持对返回 JSON 的服务器的请求。
    • 使用来自服务器的 JSON 只是说明它在现实环境中的低效率的一个例子。您的解决方案不可扩展。在良好的 MVVM 架构中,您不应该有一个模型对象 'Poo' 并引用 'Foo'。您应该只对每个视图或视图部分使用一次“Foo”。
    • 您假设 Poo 在 OP 的帖子中是多余的。这不是一个安全的假设。例如,FooPoo 可以指代两个不同的集合,它们的成员资格会随着时间而改变,但可能包含共同的成员。
    • 关于我的解决方案在FooPoo 不是多余的情况下效率低下,你是不正确的。对象num99 的单个定义和对每个集合中该对象的引用 有效的方法。相同对象的重复实例是低效的方法。
    【解决方案4】:

    架构的角度来看,我相信你不应该像这样复制你的模型。而是只使用 'Foo' 并在多个视图上使用它。您的代码重复更少,一切都会自动发生,而且更不容易出错。

    为此,您可以对多个元素使用函数ko.applyBindings()

    示例这里也有按键事件:http://jsfiddle.net/Z5yGf/2/

    【讨论】:

    • 这并没有解决 OP 在 FooPoo 中都存在项目的需要。
    • 使用此解决方案,您不需要对象存在于 Foo 和 Poo 中,只需要存在于 Foo 中。我正在从架构层面解决这个问题。
    • @Alerty,你不知道 OP 的架构。 FooPoo 可能不是多余的。由于它们是数组,因此它们的成员列表可能会随着时间而改变,但 OP 的问题中没有任何内容表明 Poo 在某些时候不会包含 Foo 不包含的成员。
    • Foo 和Poo 可以有自己不同的成员,也可以共享成员。我在这里真正想做的就是为一对多关系建模,并让“1”可以在多个位置进行编辑。
    猜你喜欢
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 2015-07-16
    • 1970-01-01
    • 2014-05-16
    • 1970-01-01
    • 2014-10-04
    • 2014-05-20
    相关资源
    最近更新 更多