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

时间:2012-05-11 16:21:17

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

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

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

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

myObject(ko.mapping.fromJS(data))

myObject中的观察点是:

  • id
  • name
  • surname

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

我已经设置了一个基本的递归函数,应该可以解决这个问题。当然,myObject.details并没有成为可观察的。

// 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]);
    }
};

我很确定这是关于错误引用的内容,但我该如何解决这个问题呢?谢谢。

6 个答案:

答案 0 :(得分:22)

我会使用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);
}

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

答案 1 :(得分:14)

我认为淘汰赛没有内置的方法来观察子元素的变化。如果我理解了您的问题,当有人更改名称时,您需要将细节更改为要注意的实体。你能给出一个如何使用它的具体例子吗?您是否会使用对可观察细节的订阅来执行某些操作?

你的代码没有使细节成为可观察的原因是因为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]);
    }
};

答案 2 :(得分: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对象。

如果您随后使用具有null类成员的数据对象创建另一个viewmodel,即:

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那么它会很一致会很好吗?

答案 3 :(得分:3)

通过使用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/

答案 4 :(得分:2)

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

考虑 frapontillo 的原始对象:

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

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

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

请参阅下面的示例。

示例:

让我们说我们需要显示一个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步:制图(获得嵌套的观察)

让我们说这是您的原始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将保存所有可观察对象,甚至是嵌套对象。现在,为了测试这一点,您唯一需要注意的是将observableUserEditModel对象与HTML绑定。提示:使用with绑定并测试在HTML视图中插入此内容的可观察observableUserEditModel 数据结构:

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

答案 5 :(得分:1)

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

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

祝你好运, 麦克