Как создать вложенные модели в Ember.js?

Просматривая документацию Ember.js, я не могу понять, как создавать вложенные модели. Предположим, что у меня есть следующий JSON:

App.jsonObject = {
    id: 1812,
    name: 'Brokerage Account',
    positions: [
        {
            symbol: 'AAPL',
            quantity: '300'
        },
        {
            symbol: 'GOOG',
            quantity: '500'
        }
    ]
}

Если я создаю объект модели, подобный этому

App.account = Ember.Object.create(App.jsonObject);

только свойства верхнего уровня (id и name) привязаны к шаблонам, массив вложенных позиций не привязывается правильно. В результате добавление, удаление или обновление позиций не влияет на отображение. Есть ли ручной или автоматический способ преобразования массива позиций так, чтобы он был осведомлен о связности (аналогично ObservableCollection в WPF)?

Я создал jsfiddle, чтобы поэкспериментировать с этим: http://jsfiddle.net/nareshbhatia/357sg/. Как вы можете видеть, изменения в свойствах верхнего уровня отражаются на выходе, но никаких изменений в позициях нет. Я был бы очень признателен за любые подсказки о том, как это сделать.

Спасибо.

Нареш

Edit: Идеальная ситуация была бы, если бы Ember.js мог каким-то образом разобрать мой JSON-канал на вложенные объекты Ember.js. Например, что, если я явно определяю свои классы следующим образом, может ли помочь Ember.js создать нужные объекты?

App.Account = Ember.Object.extend({
    id: null,
    name: null,
    positions: Ember.ArrayProxy.create()
});

App.Position = Ember.Object.extend({
    symbol: null,
    quantity: null,
    lastTrade: null
});

App.account = App.Account.create(App.jsonObject);

Моя конечная цель - отобразить эту структуру в иерархической сетке, которая может расширяться/сворачиваться на уровне учетной записи. Исходя из мира WPF/Silverlight, довольно легко сделать что-то вроде этого. Все, что вам нужно сделать, это указать вложенный шаблон для сетки в XAML, и он знает, как интерпретировать вашу модель. Вы можете найти пример здесь - это не слишком сложно. Мне интересно, возможно ли что-то подобное в Ember.js.

Ответ 1

При добавлении объектов массива Ember с привязками вам нужно использовать pushObject not push. И вы, вероятно, должны использовать get() при доступе к свойству positions. См. Мой jsFiddle для исправлений.

http://jsfiddle.net/ud3323/Y5nG5/

Ответ 2

Я создал рекурсивный объект factory, который вам может понравиться:

Он будет перебирать объект и создавать массивы Ember или вложенные объекты, возвращая полное дерево Ember Object!

Вот источник для вашей справки и скрипт JS: http://jsfiddle.net/SEGwy/2/

Обратите внимание: скрипта записывает вывод на консоль, поэтому вы не увидите никакого вывода на экране.

  RecursiveObject = {

      // http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
      TypeOf: function (input) {
          try {
              return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
          } catch (e) {
              return typeof input;
          }
      },

      // Factory method..
      create: function (args) {

          args = args || {};
          ctxt = this;

          switch (this.TypeOf(args)) {

              // Return an Ember Array
              case "array":

                  var result = Ember.A();

                  for (var i = 0; i < args.length; i++) {
                      x = this.create(args[i]);
                      result.push(x);
                  };

                  break;

              // Or a recursive object.
              case "object":

                  var result = Ember.Object.create();

                  $.each(args, function (key, value) {

                      result.set(key, ctxt.create(value));

                  });

                  break;

              default:

                  // Or just return the args.
                  result = args;
                  break;

          }

          return result;

      }

  }

  // Example

  jsonObject = {
    id: 1812,
    name: 'Brokerage Account',
    positions: [
        {
            symbol: 'AAPL',
            quantity: '300'
        },
        {
            symbol: 'GOOG',
            quantity: '500'
        }
    ]
  }

  x = RecursiveObject.create(jsonObject);
  console.log(x);

Ответ 3

Я написал свою собственную простую модель factory на основе решения Alexandros K, но поддерживаю модули ES6. Думаю, я поделился бы этим:)

import Ember from 'ember';

var ModelFactory = Ember.Object.extend({});

ModelFactory.reopenClass({
    getType(obj) {
        if(!obj) {
            return null;
        }

        if(Array === obj.constructor) {
            return "array";
        }
        else if(typeof obj === "object") {
            return "object";
        }

        return null;
    },

    // Factory method..
    create: function (arg) {

        const _this = this;

        switch (this.getType(arg)) {

            // Return an Ember Array
            case "array":

                var newArray = [];

                arg.forEach(function(item) {
                    newArray.pushObject(_this.create(item));
                });

                return newArray;

            // Or a recursive object.
            case "object":

                var newObject = Ember.Object.create();

                for (var key in arg) {
                    if (arg.hasOwnProperty(key)) {
                        newObject.set(key, this.create(arg[key]));
                    }
                }

                return newObject;

            default:

                // Or just return the args.
                return arg;
        }
    }
});


export default ModelFactory;

Использование:

ModelFactory.create(json)

Ответ 4

Ответа на этот вопрос Ember 2.5.0/May 2016

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

TL; DR: В более поздних версиях Ember вы определяете свою модель, расширяя объект Model в данных ember с необходимыми атрибутами. Атрибуты определяются путем вызова DS.attr(type) (где параметр type может быть пропущен для прохождения через любые типы, которые вы хотите, например, вложенные объекты из ваш JSON).

Итак, для этого вопроса вы можете создать app/models/account (возможно, используя ember-cli → ember g resource account):

import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';

export default Model.extend({
    name: attr('string'),
    positions: hasMany('position')
});

и аналогично для app/models/position:

import Model from 'ember-data/model';
import attr from 'ember-data/attr';

export default Model.extend({
    symbol: attr('string'),
    quantity: attr('number')
});

Мой случай:

Я хотел иметь device, который связан с connection, который может быть последовательным или TCP-соединением, каждый с разными параметрами/параметрами. В простом JS я бы сделал что-то вроде этого:

[{
    name: "My serial device",
    connection: {
        type: "serial",
        options: {
            port: "COM1",
            baud: 115200,
            data_bits: 8,
            parity: "none",
            stop_bits: 1,
            flow_control: "none"
        }
    }
}, {
    name: "My TCP/IP device",
    connection: {
        type: "tcp",
        options: {
            host: "127.0.0.1",
            port: 23
        }
    }
}]

в Ember это преобразуется в модель device с свойством соединения, тип которого является объектом (оставлено неуказанным):

import Model from 'ember-data/model';
import attr from 'ember-data/attr';

export default Model.extend({
    name: attr('string'),
    connection: attr()
});

Если вы задаетесь вопросом, что является хорошим критерием для определения того, должна ли какая-то его собственная модель (с отношением belongsTo, hasMany и т.д.) или нет, простой вопрос для начала - ресурс, о котором идет речь, в противном случае имел бы идентификационные номера, связанные с ним. В этом случае нет смысла отслеживать соединения и устройства отдельно, поэтому я решил не создавать отдельную модель для соединений.