Объектно-ориентированные модели и backbone.js

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

/* first pattern */
{ "id": 1,
  "label": "a foo"
}

/* second pattern */
{ "id": 2,
  "label": [ {"value": "a foo", "lang": "en"},
             {"value": "un foo", "lang": "fr"}]
}

В моем коде на стороне клиента я не хочу, чтобы код представления беспокоился о том, доступна ли метка на нескольких языках, какой из них выбрать и т. д. Или я мог бы захотеть скрыть подробную структуру JSON по другим причинам. Итак, я мог бы обернуть значение JSON в объект с подходящим API:

/** Value object for foo instances sent from server */
var Foo = function( json ) {
  this.json = json;
};

/** Return a suitable label for this foo object */
Foo.prototype.label = function() {
  var i18n = ... ;
  if (i18n.prefLang && _.isArray(this.json.label)) // ... etc etc
};

Так что все это довольно обычный шаблон объекта-значения, и он полезен, потому что более отделен от конкретного Структура JSON, более проверяемая и т. д. ОК, хорошо.

Чего я в настоящее время не вижу, так это того, как использовать один из этих объектов-значений с Backbone и Marionette. В частности, я хотел бы использовать объект Foo в качестве основы для Backbone Model и привязать его к Marionette ItemView. Однако, насколько я вижу, значения в Model берутся непосредственно из структуры JSON — я не вижу способа распознать, что объекты являются функциями:

var modelFoo = new Backbone.Model( foo );
> undefined
modelFoo.get( "label" ).constructor
> function Function() { [native code] }

Итак, мой вопрос: каков хороший способ отделить атрибуты Backbone Model от специфики данной структуры JSON, такой как сложное значение API? Можно ли заставить объекты, модели и представления играть красиво?

Изменить

Позвольте мне добавить еще один пример, поскольку я думаю, что приведенный выше пример, посвященный проблемам i18n, передает только часть моей озабоченности. Несколько упрощая, в моей области есть водоемы, состоящие из рек, озер и приливных зон. С водоемом связана одна или несколько точек отбора проб, и в каждой точке отбора проб имеется самая последняя проба. Это может вернуться из API данных на сервере примерно так:

{"id": "GB12345678",
 "centre": {"lat": 1.2345, "long": "-2.3456"},
 "type": "river",
 "samplingPoints": [{"id": "sp98765",
                     "latestSample": {"date": "20130807", 
                                      "classification": "normal"}
                    }]
}

Таким образом, в моем коде представления я мог написать такие выражения, как:

<%= waterbody.samplingPoints[0].latestSample.classification %>

or

<% if (waterbody.type === "river") { %>

но это было бы ужасно, и его легко сломать, если изменится формат API. Немного лучше, я мог бы абстрагировать такие манипуляции во вспомогательные функции шаблона, но для них все еще сложно писать тесты. Что я хотел бы сделать, так это иметь класс объекта значения Waterbody, чтобы мой код представления мог иметь что-то вроде:

<%= waterbody.latestClassification() %>

Одна из основных проблем, с которыми я сталкиваюсь в Marionette, — это настойчивое требование вызова toJSON() для моделей, передаваемых в представления, но, возможно, в некоторых предложениях вычисляемых свойств есть способ обойти это.


person Ian Dickinson    schedule 06.08.2013    source источник
comment
Вы можете взглянуть на использование serializeData в модели. Вы можете манипулировать моделью так, как считаете нужным для представления.   -  person kalley    schedule 08.08.2013
comment
обновил мой ответ, чтобы ответить на ваше редактирование   -  person Creynders    schedule 08.08.2013


Ответы (2)


Самое чистое решение IMO - поместить в модель средство доступа к меткам вместо VO:

var FooModel = Backbone.Model.extend({
    getLabel : function(){
        return this.getLocalized("label");
    },
    getLocalized : function(key){
        //return correct value from "label" array
    }
});

и пусть представления используют FooModel#getLabel вместо FooModel#get("label")

--РЕДАКТИРОВАТЬ 1

Эта библиотека кажется интересной и для вашего варианта использования: Backbone.Schema

Он позволяет вам формально объявить тип атрибутов вашей модели, но также предоставляет некоторый синтаксический сахар для локализованных строк и позволяет создавать динамические атрибуты (называемые «вычисляемыми свойствами»), составленные из значений других атрибутов.

--EDIT 2 (в ответ на отредактированный вопрос)

IMO VO, возвращаемый с сервера, должен быть заключен в модель, и эта модель передается в представление. Модель реализует latestClassification, а не VO, это позволяет представлению напрямую вызывать этот метод в модели.

person Creynders    schedule 07.08.2013
comment
Backbone.Schema выглядит интересно, спасибо за подсказку. Я обновил вопрос, чтобы прояснить некоторые другие мои опасения, лежащие в основе этого вопроса. - person Ian Dickinson; 08.08.2013

Простым подходом к этому (возможно, слишком простым для вашей реализации) было бы переопределить метод модели parse для возврата подходящих атрибутов:

var modelFoo = Backbone.Model.extend({
    parse: function ( json ) {
        var i18n = ... ;
        if (i18n.prefLang && _.isArray(json.label)) {
            // json.label = "complex structure"
        }
        return json;
    }
});

Таким образом, только ваша модель заботится о том, как форматируются данные с сервера, без добавления еще одного уровня абстракции.

person Nick Tomlin    schedule 06.08.2013
comment
Так что относитесь к этому как к некоему фасадному узору. Хорошо, я это вижу. В случае меток i18n отбрасывание меток непредпочтительного языка будет означать, что если пользователь переключает языки в пользовательском интерфейсе, мне придется попросить сервер повторно отправить данные. Но да, если отображение данных сервера в форму представления является статическим, это может быть возможной тактикой. - person Ian Dickinson; 06.08.2013
comment
@IanDickinson, вы всегда можете переместить исходную метку в другое свойство. например json.labels = json.label; json.label = "complex value";, и если json.label не является массивом, вы можете просто добавить его в json, используя json.labels = [json.label], и создать для этого согласованный API. - person kalley; 07.08.2013
comment
Спасибо за предложение. Я отредактировал вопрос, чтобы расширить мою основную проблему. - person Ian Dickinson; 08.08.2013