Knockout.js: привязка к сложному объекту

Я новичок в knockout.js и пытаюсь привязать к следующему объекту, представляющему пользователя:

{
    "$id": "1",
    "$values": [
        {
            "$id": "2",
            "Locations": {
                "$id": "3",
                "$values": []
            },
            "Photos": {
                "$id": "4",
                "$values": []
            },
            "UserId": 1,
            "Name": "Test User"
        }
    ]
}

Пользователь может иметь нулевые или более местоположения, а также ноль или более фотографий.

Модель просмотра:

function UsersViewModel() {
    var self = this;
    self.users = ko.observableArray();

    var baseUri = 'http://localhost:46241/api/users';

    $.getJSON(baseUri, function (data) {
        self.users = data;
    });
}

$(document).ready(function () {
    ko.applyBindings(new UsersViewModel());
})

HTML содержит следующую привязку:

<ul id="update-users" data-bind="foreach: users"> 
     <li> 
            <div><div class="item">User ID</div>
                <input type="text" data-bind="value: $data.UserId" />
            </div>                 
            <div><div class="item">Name</div>
                <input type="text" data-bind="value: $data.Name" />
            </div>                  
     </li> 
</ul>

Неужели я делаю это неправильно? Или ссылки на объекты пользователей на объекты и фотографии могут испортить привязку?

Ответ 1

Неужели я делаю это неправильно?

Да. Вы переписываете свой observableArray в обратном вызове JSON, тем самым уничтожая его:

$.getJSON(baseUri, function (data) {
    self.users = data;
});

В общем случае наблюдаемые нокауты присваиваются следующим образом:

$.getJSON(baseUri, function (data) {
    self.users(data.$values);
});

Обратите внимание, что в вашем случае data.$values, похоже, содержит фактический массив, а не data.

Это сработает для вашего дела, но не полностью использует нокаут.

Для этого случая был создан плагин сопоставления. Он рекурсивно пересекает сложный объект и превращает все его свойства в наблюдаемые и все содержащиеся массивы в наблюдаемые массивы. Это дает вам мелкомасштабный контроль над состоянием объекта, позволяя отслеживать каждое изменение.

$.getJSON(baseUri, function (data) {
    ko.mapping.fromJS(data.$values, {}, self.users);
});

Он также позволяет частичные обновления: предположим, что вы получаете список пользователей с сервера, и ваша модель представления уже имеет половину из них. Плагин сопоставления может добавлять только те, которые отсутствуют в вашей модели представления, и вносить соответствующие изменения в существующие. Прочтите раздел "Расширенное использование" в документах, если вы хотите знать, как это сделать.


Предполагая, что вы получаете массив пользовательских объектов с сервера и используете плагин сопоставления, ваша привязка будет выглядеть так:

<ul id="update-users" data-bind="foreach: users"> 
     <li> 
            <div><div class="item">User ID</div>
                <input type="text" data-bind="value: UserId" />
            </div>                 
            <div><div class="item">Name</div>
                <input type="text" data-bind="value: Name" />
            </div>
            <ul class="photos" data-bind="foreach: Photos.$values">
              <!-- ... -->
            <ul>
     </li> 
</ul>

В общем случае комментарий @Greg Smith корректен. Попытайтесь потерять $ в объектных ключах, они могут столкнуться со специальными переменными в нокауте в какой-то момент. Например, вы можете безопасно заменить их символами подчеркивания.

В более общем примечании я попытался бы исключить все понятие $id/$values в вашем JSON, если бы я был вами, это не похоже на какую-либо цель:

[
    {
        "Locations": [],
        "Photos": [],
        "UserId": 1,
        "Name": "Test User"
    }
]