Knockout.js Сделать каждый вложенный объект Наблюдаемым

Я использую Knockout.js в качестве библиотеки MVVM для привязки моих данных к некоторым страницам. В настоящее время я создаю библиотеку для вызова REST на веб-службу. Моя веб-служба RESTful возвращает простую структуру:

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

У меня есть наблюдаемый основной родитель, myObject. Когда я делаю

myObject(ko.mapping.fromJS(data))

наблюдаемые в myObject:

  • id
  • name
  • surname

Как я могу сделать 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 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]);
    }
};

Я уверен, что это что-то о неправильных ссылках, но как я могу это решить? Спасибо.

Ответ 1

Я не думаю, что нокаут имеет встроенный способ наблюдения за изменениями дочерних элементов. Если я понимаю ваш вопрос, когда кто-то изменяет имя, которое вы хотите изменить для деталей как подлежащее замещению. Можете ли вы привести конкретный пример того, как вы это используете? Используете ли вы подписку на детали, наблюдаемые для выполнения каких-либо действий?

Причина, по которой ваш код не делает детали видимыми, заключается в том, что javascript передается по значению, поэтому изменение значения аргумента "объект" в вашей функции не изменяет фактическое значение, которое вы передали, а только значение аргумент внутри вашей функции.

Edit

Если изменения будут автоматически распространяться на родителей, это должно сделать все дети наблюдаемыми, я думаю, но ваш корень, который вы проходите в первый раз, уже должен быть наблюдаемым.

// 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

Я бы использовал плагин отображения для нокаута.

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

Это создаст вашу иерархию объектов со всеми дочерними элементами в качестве наблюдаемых.

Ответ 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.

Итак, если вы создаете 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 в приведенном ниже примере, который также является объектом. Эти два объекта не могут быть наблюдаемыми , но их свойства LEAF могут быть.

Каждое свойство листа вашего дерева данных/модели МОЖЕТ БЫТЬ НАБЛЮДЕНЫ. Самый простой способ добиться этого - это то, что вы правильно определяете модель сопоставления, прежде чем передавать ее в плагин сопоставления как параметр.

См. мой пример ниже.

Пример:

Скажем, нам нужно показать html-страницу/представление, где у нас есть список пользователей в сетке. Рядом с сетью Users отображается форма для редактирования выбранного пользователя из сетки.

ШАГ 1: ОПРЕДЕЛЕНИЕ МОДЕЛЕЙ

function UsersEdit() {
    this.User = new User();                    // model for the selected user      
    this.ShowUsersGrid = ko.observable(false); // defines the grid visibility (false by default)
    this.ShowEditForm = ko.observable(false);  // defines the selected user form visibility (false by default)      
    this.AllGroups = [];                       // NOT AN OBSERVABLE - when editing a user in the user 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 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());

И ВЫ СДЕЛАНЫ!:)

НаблюдаемаяUserEditModel будет содержать все ваши наблюдаемые, даже вложенные. Теперь единственное, что вам нужно позаботиться, чтобы проверить это, - связать объект observableUserEditModel с вашим HTML. Подсказка: используйте привязку with и протестируйте наблюдаемую структуру данных observableUserEditModel , вставив ее в свой HTML-вид:

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

Ответ 6

Возможно, в будущей версии может быть параметр конфигурации, который вызывает ko.mapping.fromJS, чтобы всегда создавать наблюдаемые. Он может быть включен для новых проектов или после обновления привязок существующего проекта.

Что я делаю, чтобы предотвратить эту проблему, убедитесь, что семена модели всегда имеют свойства объектов объектов, заполненные на каждом уровне. Таким образом, все свойства объекта отображаются как POJO (обычные объекты Javascript), поэтому ViewModel не инициализирует их как ko.observables. Это позволяет избежать "иногда наблюдаемых, иногда не" проблем ".

С уважением, Mike