В коллекциях Backbone.js не обязательно должны храниться объекты одного типа. Если в неё явно добавить экземпляр класса, унаследованного от Backbone.Model
, то он без изменений будет помещён в неё. У коллекции есть атрибут model
, в котором хранится конструктор модели. Когда в коллекцию добавляется простой объект, то он в качестве параметра передаётся в этот конструктор.
В атрибуте
model
можно, фактически, указать любую функцию. Возвращаемые значения (экземпляры классов, унаследованных от Backbone.Model
) будут помещаться в коллекцию. Такие функции ещё называют фабриками или фабричными методами. В документации к атрибуту model
как раз представлен вариант такой фабрики.
Чтобы каждый раз не писать однотипных проверок в подобных ситуациях, я предлагаю пользоваться паттерном «Фабрика фабрик»:
App.Factory = function (getter, hash, def) {
return function () {
var value = getter.apply(this, arguments);
var ctor = hash[value] ? hash[value] : def;
return new ctor(arguments[0], arguments[1]);
};
};
- getter — функция, которая возвращает значение, на основании которого будет выбираться требуемый конструктор;
- hash — объект ключ-значение с перечислением конструкторов, которые могут использоваться фабрикой;
- def — конструктор по-умолчанию.
Предположим, что в коллекции могут находиться объекты следующих классов:
App.Item = Backbone.Model.extend({});
App.ItemNews = App.Item.extend({});
App.ItemAction = App.Item.extend({});
App.ItemMessage = App.Item.extend({});
Таким образом, фабрика объектов будет выглядеть так:
App.ItemFactory = App.Factory(
function (attr) { return attr.type; },
{
"news": App.ItemNews,
"action": App.ItemAction,
"message": App.ItemMessage
},
App.Item
);
App.Factory
возвращает функцию, которая, в свою очередь, будет возвращать требуемый экземпляр в зависимости от значения атрибута type
. Эту функцию и передаём в описание коллекции.
App.Items = Backbone.Collection.extend({
model: App.ItemFactory
});
Теперь коллекция будет заполняться моделями нужного типа автоматически.
Практическое применение паттерна можно посмотреть в исходном коде примера.
Коментарии к заметке
Это офигенно! Спасибо вам за труд и за то, что делитесь знаниями!
Fungi, cпасибо.
Назрел вопрос: а можно ли мне таким образом запихать коллекцию в коллекцию? Насколько я знаю, в backbone такого не предусмотрено((( А то вот мне вернулись с сервера несколько наборов моделей (как в вашем примере), а как мне оформить их в коллекции? Потому как мало толку от просто моделей, надо работать с коллекциями.
Есть плагины, которые берут на себя обслуживание вложенных моделей и коллекций. Например, Backbone-relational.js или Backbone Associations. В принципе, можно написать какой-то свой класс, решающий твою конкретную задачу.
Такие плагины, как правило, работают по такому принципу: в классе задаётся схема, описывающая все поля (типы, дефолтные значения и т.п.), которые могут встретиться в DTO, и Backbone сам начинает правильно конструировать вложенные объекты. Плюс, к атрибутам таких вложенных объектов можно будет получить доступ через getter/setter родительского объекта.
Большое спасибо за развернутый ответ и ссылки! Будем изучать…
этот метод не будет работать если вызвать model.clone(), тк в App.Factory в arguments будут лететь не инициализирующие данные, то есть App.ItemFactory будет всегда возвращать def
Александр, сделал пример с клонирование модели . Для наглядности там выводится тип сущности. Убеждаемся, что данные действительно клонируются и экземпляры имеют разные
cid
.В методе
clone()
вызывается тот же самый конструктор, который использовался первый раз, с текущими значениями атрибутов. Фабрика уже не участвует в создании экземпляра.Может быть я не так понял тебя?
приношу извинения! был неправ — метод работает и с клонированием! у меня модель M содержала коллекцию С, которая при инициализации наполнялась данными, передаваемыми в json в модель, примерно так:
соответственно при клонировании в C лежал уже не json данные для коллекции, а сам объект коллекции. сделал проверку на существование метода toJSON
теперь верно работает.
Спасибо за фидбэк!
В дополнение небольшой коммент, не относящийся к теме сабжа:
странная реализация в моделях backbone метода toJSON — он не учитывает, что аттрибутами могут быть любые объекты js, например коллекции — при toJSON он вернет внутри саму коллекцию, а не ее json вид, связано это с такой реализацией в прототипе
для себя в нужных моделях сделал следующее:
Александр, ага, теперь я понял что ты хотел сделать. Ещё раз повторюсь, что Backbone «из коробки» не работает с вложенными объектами.
Посмотрите в сторону Backbone-relational.js. Я упоминал его в одном из предыдущих комментариев. Для вашего случая он очень хорошо подходит — пример. Там даже есть поддержка создания объектов разных типов внутри одной коллекции.
К сожалению в документации и статьях о backbone мало сложных приложений и понимание построения правильного апликейшена приходит лишь после долгих обдумываний… С большим удовольствием прочитал бы пример или статью на более сложном примере приложения, например есть 2 сущности — пользователи и группы, один пользователь может состоять в нескольких группах сразу и одна группа может иметь сразу много пользователей. надо сделать интерфейс слева пользоватли, справа группы и с помощью checkbox, расположенных рядом с группой, можно было включить или выключить того или иного пользователя в группу. Выбор пользователя простым кликом по нему.
Задачу примерно понял. Сделаю пример в свободное время.
Ога, к сожалению, а может к счастью приходится думать)))
Кстати если делать clone() модели Backbone-relational, связи не копируются. Копируются только вложенные атрибуты, но не модели, увы. Т.е. копию от объекта я могу сделать, т.к. есть вложенные атрибуты, а вот копию от копии уже нет, говорит «this.model is null»
Fungi, это действительно так. Используй
.toJSON()
:Сначала всё дерево объектов сериализуется, а затем конструируются новые экземпляры всех объектов (вложенных в том числе).
С клонированием всё очень сложно с точки зрения архитектуры приложения. В JS все объекты мутабельны и с этим можно как-то жить. Из экземпляра не проблема сделать новую независимую копию, скопировав (может быть даже рекурсивно) все поля в другой объект.
Но в Бекбоне объекты связаны со своим представлением на сервере по
id
. Когда мы работаем с одним экземпляром объекта в пределах приложения, то его состояние консистентно из в любой момент может быть синхронизировано с сервером. Делая клон объекта, мы получаем новый экземпляр с тем же самымid
. Какой объект после этого можно будет синхронизировать с сервером, а какой нет? А что делать с клонами вложенных объектов?Предполагаю, что клон нужен, чтобы перенести состояние модели из одного компонента приложения в другое и в дальнейшем использовать только часть данных из этого состояния. Так и делайте это с помощь какого-то абстрактного Data Transfer Object, а не копии модели. Конвертацию можно делать с помощью методов .pick() и .map().