web-dev-qa-db-ja.com

Knockout.jsすべてのネストされたオブジェクトを観察可能にする

Knockout.jsをMVVMライブラリとして使用して、データをいくつかのページにバインドしています。現在、REST Webサービスを呼び出すためのライブラリを作成しています。RESTfulWebサービスは、単純な構造を返します。

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

観察可能なメインの親myObjectがあります。私がする時

myObject(ko.mapping.fromJS(data))

myObjectのオブザーバブルは次のとおりです。

  • id
  • name
  • surname

details(および理論的には構造内の任意のオブジェクトを観察可能にする)この動作が必要なのは、計算されたオブザーバブルを詳細に設定し、内部データのいずれかが変更されるとすぐに気付くことができるためです。

トリックを実行する基本的な再帰関数を設定しました。もちろん、_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]);
    }
};

私はそれが誤った参照に関するものだと確信していますが、どうすればこれを解決できますか?ありがとうございました。

34
frapontillo

ノックアウトには、子要素への変更を監視する組み込みの方法があるとは思わない。私があなたの質問を理解している場合、誰かが名前を変更するとき、あなたは注目される実体として詳細への変更を望みます。これをどのように使用するか、具体的な例を挙げていただけますか?観察可能な詳細へのサブスクリプションを使用して、何らかのアクションを実行しますか?

コードが詳細を観察可能にしない理由は、javascriptが値で渡されるためです。関数の 'object'引数の値を変更しても、渡された実際の値は変更されず、内部の引数の値のみが変更されます関数。

編集

変更willが自動的に親に伝播する場合、これによりすべての子が観察可能になりますが、最初に渡すルートは既に観察可能になっているはずです。

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

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

これにより、すべての子がオブザーバブルとしてオブジェクト階層が作成されます。

23
Paolo del Mundo

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/

3
AR M

私が経験したことから、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クラスメンバーを持つデータオブジェクトを使用して別のビューモデルを作成する場合、つまり:

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
Beans

Paolo del Mundoの答え(現時点では簡単に最良かつ唯一の解決策になると思います)を私のソリューション例で拡張します。

frapontilloの元のオブジェクトを考慮してください:

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

detailsプロパティ自体はオブジェクトであるため、監視可能にすることはできません。同じことは、オブジェクトでもある以下の例のUserプロパティにも当てはまります。これらの2つのオブジェクトは、オブザーバブルにすることはできませんただし、LEAFプロパティは可能です

データツリー/モデルのすべてのリーフプロパティは観測可能です。それを達成する最も簡単な方法は、マッピングプラグインに渡す前にマッピングモデルを適切に定義する asパラメータ。

以下の例を参照してください。

例:

グリッド上にユーザーのリストがある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モデルであり、ネストされたオブザーバブルを持つ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バインディングを使用して、観察可能なobservableUserEditModelデータ構造をテストし、これをHTMLビューに挿入します。

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

将来のバージョンでは、ko.mapping.fromJSが常にオブザーバブルを作成するようにする構成オプションがあるかもしれません。新しいプロジェクトに対して、または既存のプロジェクトのバインディングを更新した後に有効にすることができます。

この問題を防ぐために、モデルシードのすべてのレベルで常にObjectメンバープロパティが設定されるようにします。このように、すべてのオブジェクトプロパティはPOJO(Plain Old Javascript Objects)としてマップされるため、ViewModelはそれらをko.observablesとして初期化しません。 「時には観測可能、時にはそうではない」という問題を回避します。

よろしく、マイク

1
Beans