Knockout.js 使每个嵌套对象都成为 Observable

Posted

技术标签:

【中文标题】Knockout.js 使每个嵌套对象都成为 Observable【英文标题】:Knockout.js Make every nested object an Observable 【发布时间】:2012-05-20 06:31:34 【问题描述】:

我使用 Knockout.js 作为 MVVM 库来将我的数据绑定到某些页面。我目前正在构建一个库来对 Web 服务进行 REST 调用。 我的 RESTful Web 服务返回一个简单的结构:


    id : 1,
    details: 
        name: "Johnny",
        surname: "Boy"
    

我有一个可观察的主要父母,myObject。 当我这样做时

myObject(ko.mapping.fromJS(data))

myObject 中的 observables 是:

id name surname

我怎样才能使details(理论上结构中的任何对象都是可观察的)?我需要这种行为,以便我可以在细节上设置一个计算出的 observable,并在任何内部数据发生变化时立即引起注意。

我已经设置了一个基本的递归函数,它应该可以解决问题。当然,myObject.details 不会变成 observable。

// Makes every object in the tree an observable.
var makeAllObservables = function () 
    makeChildrenObservables(myObject);
;
var makeChildrenObservables = function (object) 
    // Make the parent an observable if it's not already
    if (!ko.isObservable(object)) 
        if ($.isArray(object))
            object = ko.observableArray(object);
        else
            object = ko.observable(object);
    
    // Loop through its children
    for (var child in object()) 
        makeChildrenObservables(object()[child]);
    
;

我很确定这与不正确的引用有关,但我该如何解决这个问题?谢谢。

【问题讨论】:

不是一个直接的答案,但是 CanJS 中的 can.Observe 完全按照您的描述构建了一个嵌套的 observable。 【参考方案1】:

我会使用knockout mapping plugin。

var jsonData = 
    id : 1,
    details: 
        name: "Johnny",
        surname: "Boy"
    


var yourMapping = 
    'details': 
        create: function(options) 
            return Details(options.data);
        
    


function Details(data) 
    ko.mapping.fromJS(data, , this);


function YourObjectName() 
    ko.mapping.fromJS(jsonData, yourMapping, this);

这将创建您的对象层次结构,其中所有子对象都作为可观察对象。

【讨论】:

是的,我已经考虑过这个解决方案,问题是我需要对许多比本示例中的 on 大得多的结构执行此操作,并且我正在寻找一种自动执行此操作的方法. 不幸的是,这可能是唯一也是最好的方法。这意味着您需要在编码之前对数据进行建模。 我同意,这可能是一项繁琐的工作,因为这意味着您需要为要映射/包含可观察对象的每种类型的对象预定义一个模型。但是想想......一定有办法告诉 Knockout 如何映射属性。你知道这不是魔法。 :) 我认为长期建模有利于可维护性的原因。还假设您使用 TypeScript。在 TypeScript 中,对象必须是强类型的,因此是预先定义好的。因此,在客户端也必须进行建模。【参考方案2】:

我认为 knockout 没有内置的方法来观察子元素的变化。如果我理解您的问题,当有人更改名称时,您希望更改详细信息作为一个实体来引起注意。你能举一个具体的例子来说明你将如何使用它吗?你会使用订阅可观察到的细节来执行某些操作吗?

您的代码没有将详细信息设为可观察的原因是因为 javascript 是按值传递的,因此更改函数中 'object' 参数的值不会更改您传递的实际值,只会更改函数内部的参数。

编辑

如果更改自动传播给父母,我认为这应该使所有孩子都可以观察到,但是你第一次通过的根应该已经是一个可观察的。

// object should already be observable
var makeChildrenObservables = function (object) 
    if(!ko.isObservable(object)) return;

    // Loop through its children
    for (var child in object()) 
        if (!ko.isObservable(object()[child])) 
            object()[child] = ko.observable(object()[child]);
        
        makeChildrenObservables(object()[child]);
    
;

【讨论】:

我需要这个,所以我可以更改包含姓名和姓氏的框架上的颜色。如果这两个更改中的任何一个,我将其设置为黄色。这只是一个例子,但这就是我的想法。如果任何一个孩子发生了变化,Knockout.js 会触发父母的变化,所以是的,它会起作用。有没有办法通过引用而不是值传递对象?如果我使用某种扩展函数(可能是 jQuery.extend?)不会改变原始指针但它引用的值怎么办? 通过使用computed observables、it works。只要您更改firstNamelastNamefullName 就会被触发。通过这种方式,您可以跟踪结构中任何元素的变化。 @jason-goemaat 发布的功能有效(谢谢!),但需要知道它是否已被应用。我已经设置了一个函数来设置 JS 并使所有内容本身都是可观察的。 :D 它的工作原理是敲除运行计算并跟踪访问的内容,为每个添加一个依赖项。如果无论如何您在计算中使用单个可观察对象,则使父对象可观察是没有意义的。我认为你需要做的就是在你的吼声(或每个对象)上创建一个函数并订阅所有孩子...... 这只是一个例子,我在计算中要做的是某种变化跟踪,计算将评估父对象的 ko.mapping.toJSON 看是否有内部结构的任何更改,以便我可以撤消任何嵌套对象。 正如我所承诺的,this is the result 我在这里提出的问题。它是 knockout.js 的扩展,为实体提供 REST 方法和一个不错的 undo【参考方案3】:

根据我的经验,ko.mapping.fromJS 不会从对象中生成可观察对象。

假设你有这个 ViewModel 构造函数:

var VM = function(payload) 
  ko.mapping.fromJS(payload, , this);

还有这个数据对象:

var data1 = 
  name: 'Bob',
  class: 
    name: 'CompSci 101',
    room: 112
  

然后你使用 data1 创建 VM1:

var VM1 = new VM(data1);

那么 VM1.class 将不是一个 ko.observable,它是一个普通的 javascript 对象。

如果您随后使用具有空类成员的数据对象创建另一个视图模型,即:

var data2 = 
  name: 'Bob',
  class: null

var VM2 = new VM(data2);

那么 VM2.class 是一个 ko.observable。

如果你接着执行:

ko.mapping(data1, , VM2)

那么 VM2.class 仍然是一个 ko.observable。

因此,如果您从对象成员为 null 的种子数据对象创建 ViewModel,然后使用填充的数据对象填充它们,您将拥有可观察的类成员。

这会导致问题,因为有时对象成员是可观察的,有时则不是。表单绑定将适用于 VM1,但不适用于 VM2。如果 ko.mapping.fromJS 总是让所有东西都成为 ko.observable 从而保持一致就好了?

【讨论】:

【参考方案4】:

通过使用Knockout-Plugin,我们可以使子元素可观察。我们有很多选项来管理我们希望数据如何可观察。

这是一个示例代码:

var data = 
    people: [
        
            id: 1,
            age: 25,
            child : [
                id : 1,childname : "Alice",
                id : 2,childname : "Wonderland"
            ]
        , 
        id: 2, age: 35
    ],
    Address:[
        
            AddressID : 1,
            City : "NewYork",
            cities : [
                
                    cityId : 1,
                    cityName : "NewYork"
                ,
                
                    cityId :2,
                    cityName : "California"
                
            ]
        ,
        
            AddressID : 2,
            City : "California",
            cities : [
                
                    cityId :1,
                    cityName : "NewYork"
                ,
                
                    cityId :2,
                    cityName : "California"
                
            ]
        
    ],
    isSelected : true,
    dataID : 6
;
var mappingOptions = 
    people: 
        create: function(options) 
            console.log(options);
            return ko.mapping.fromJS(options.data, childmappingOptions);
        
    ,
    Address: 
        create: function(options) 
            console.log(options);
            return ko.mapping.fromJS(options.data, childmappingOptions);
        
    
;
var childmappingOptions = 
    child: 
        create: function(options) 
            return ko.mapping.fromJS(options.data,  observe: ["id","childname"]);
        
    ,
    cities :
        create: function(options) 
            return ko.mapping.fromJS(options.data,  observe: ["cityId","cityName"]);
        
    
;
var r = ko.mapping.fromJS(data, mappingOptions);

我附上了一个工作小提琴:http://jsfiddle.net/wmqTx/5/

【讨论】:

【参考方案5】:

我将用我的示例解决方案扩展 Paolo del Mundo 的答案(我认为它可能很容易成为目前最好和唯一的解决方案)。

考虑 frapontillo 的原始对象:


    id : 1,
    details: 
        name: "Johnny",
        surname: "Boy"
    

details 属性本身是一个对象,因此不能是可观察的。下面示例中的User 属性也是如此,它也是一个对象。这两个对象不能是 observables但它们的 LEAF 属性可以是

数据树/模型的每个叶属性都可以是可观察的。实现这一点的最简单方法是在将映射模型传递给映射插件之前正确定义它 strong> 作为参数。

请参阅下面的示例。

示例:

假设我们需要显示一个 html 页面/视图,其中我们有一个网格上的用户列表。在“用户”网格旁边显示了一个用于从网格中编辑选定用户的表单。

第 1 步:定义模型

function UsersEdit() 
    this.User = new User();                    // model for the selected user      
    this.ShowUsersGrid = ko.observable(false); // defines the grid's visibility (false by default)
    this.ShowEditForm = ko.observable(false);  // defines the selected user form's visibility (false by default)      
    this.AllGroups = [];                       // NOT AN OBSERVABLE - when editing a user in the user's form beside the grid a multiselect of all available GROUPS is shown (to place the user in one or multiple groups)
    this.AllRoles = [];                        // NOT AN OBSERVABLE - when editing a user in the user's form beside the grid a multiselect of all available ROLES is shown (to assign the user one or multiple roles)


function User() 
    this.Id = ko.observable();
    this.Name = ko.observable();
    this.Surname = ko.observable();
    this.Username = ko.observable();
    this.GroupIds = ko.observableArray(); // the ids of the GROUPS that this user belongs to
    this.RoleIds = ko.observableArray();  // the ids of the ROLES that this user has

第 2 步:映射(获取嵌套的 OBSERVABLE)

假设这是您的原始 JSON 模型,其中包含您想要映射的数据并获得具有嵌套 observable 的 KO 模型。

var model = 
    User: 
        Id: 1,
        Name: "Johnny",            
        Surname = "Boy",
        Username = "JohhnyBoy",
        GroupIds = [1, 3, 4],
        RoleIds = [1, 2, 5]
    
;

现在所有这些都已定义,您可以映射:

var observableUserEditModel = ko.mapping.fromJS(model, new UsersEdit());

你已经完成了! :)

observableUserEditModel 将保存所有的 observable,甚至是嵌套的。现在,为了测试这一点,您唯一需要注意的是将 observableUserEditModel 对象与您的 HTML 绑定。提示:使用 with 绑定并测试可观察的 observableUserEditModel 数据结构,将其插入 HTML 视图:

<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>

【讨论】:

"details 属性本身是一个对象,因此不能是可观察的。"你的意思是?当然 observables 可以包含对象。【参考方案6】:

也许在未来的版本中可能会有一个配置选项导致 ko.mapping.fromJS 始终创建 observables。它可以在新项目或更新现有项目的绑定后启用。

我为防止此问题所做的工作是确保模型种子始终在每个级别都填充 Objects 成员属性。通过这种方式,所有对象属性都映射为 POJO(Plain Old Javascript Objects),因此 ViewModel 不会将它们初始化为 ko.observables。它避免了“有时可观察,有时不可观察”的问题。

最好的问候, 迈克

【讨论】:

以上是关于Knockout.js 使每个嵌套对象都成为 Observable的主要内容,如果未能解决你的问题,请参考以下文章

Knockout js绑定可空对象的属性

在 knockout.js 中进行 ajax 检索后使字段可观察

knockout.js remove 不适用于主视图模型中的嵌套视图模型和视图模型

knockout.js 数据中的嵌套 foreach 显示但未正确显示

使用 Knockout.JS 通过 JSON 将 HTML 复选标记传递到表中

每个视图都有单独的knockout js文件