Все, что вы хотели знать о моделях и коллекциях в Titanium
Но по какой-то причине боялись спросить.
Модели — достаточно важная часть приложения, потому что без данных никуда.
В этой статье я постараюсь максимально подробно осветить все аспекты использования моделей в MVC фреймворке для разработки мобильных приложений Appcelerator Titanium.
Если вы еще не пробовали связываться с моделями, то, надеюсь, эта статья сэкономит вам пару километров нервов.
Итак, первое, что стоит знать при работе с моделями в Titanium, это то, что они основаны на моделях и коллекциях из Backbone. Это значит, во-первых, вы можете использовать все свойства и методы, описанные в документации Backbone; во-вторых, будет очень не лишним предварительно с ней знакомиться, если вы еще этого не сделали.
На всякий случай сделаю акцент на терминологии: модель — это единичная сущность, коллекция — это набор однотипных моделей.
Важно: Titanium использует Backbone версии 0.9.2, так что некоторые методы из документации Backbone могут быть не реализованы.
Коллекции так же дополнительно наследуют методы Underscore.js для облегчения жизни разработчика. Например, метод .map
Создать модель можно двумя равносильными способами: с помощью Appcelerator Studio или вручную.
Вручную вы можете создать файл специального формата в папке /app/models/ (что, собственно, и сделает за вас студия):
exports.definition = {
config : {
// схема данных
},
extendModel: function(Model) {
_.extend(Model.prototype, {
// расширение Backbone.Model
});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
// расширение Backbone.Collection
});
return Collection;
}
}
Если вы пользуетесь помощью студии — просто вызовите контекстное меню у проекта и выберите пункт «New → Alloy Model». При создании диалоговое окно предложит вам выбрать тип адаптера: localStorage, properties или sql. Типы адаптеров описаны дальше.
Все, что вам нужно описать в этом файле для базового функционала, это блок config. В нем содержится схема данных и тип адаптера синхронизации. Этот блок должен содержать следующие поля:
- columns: словарь, описывающий поля модели. Ключ — название поля, значение — тип. Типы как в SQLite: string, varchar, int, tinyint, smallint, bigint, double, float, decimal, number, date, datetime и boolean
- defaults: тот же словарь, что и columns, но теперь значения — это значения полей по умолчанию, если какое-то из них не определено при синхронизации
- adapter: этот объект содержит два поля:
- type: тип адаптера синхронизации
- collection_name: название коллекции. Может отличаться от названия модели. Значение этого поля вы будете использовать для создания коллекции
- idAttribute: первичный ключ
Например, мы создаем модель books. Для этого создаем в папке /app/models/ файл books.js:
exports.definition = {
config: {
"columns": {
"title": "String",
"author": "String" // если тип адаптера sql, то здесь можно еще SQLite keywords добавлять
},
"defaults": {
"title": "-",
"author": "-"
},
"adapter": {
"type": "sql",
"collection_name": "books",
"idAttribute" : "title",
"db_file" : "db_books", // название базы данных, с которой нужно работать. По умолчанию _alloy_
"db_name" : "books.sqlite", // название файла базы относительно каталога /app/assets. Если не указано, то создастся файл [имя_базы].sqlite
}
}
}
Здесь у нас модель с двумя текстовыми полями: название книги и автор. Оба поля по умолчанию имеют значение »-». Пока все просто.
Тип адаптера sql говорит о том, что при попытке получить данные, модель с помощью встроенного адаптера попытается соединиться с SQLite базой на устройстве и получить данные из таблицы books.
Собственные свойства и методы в модели можно добавить так:
exports.definition = {
config : {
// схема данных
},
extendModel: function(Model) {
_.extend(Model.prototype, {
// расширение Backbone.Model
customProperty: 'book',
customFunction: function() {
Ti.API.info('Привет, я книга');
},
});
return Model;
}
}
То же самое касается коллекций. Если вы добавили собственные методы в extendModel, они будут доступны у модели, а у коллекции нет, и наоборот.
Обратиться к модели/коллекции можно двумя способами:
- Создав глобальный синглтон
- Создав локальную ссылку
- Указав в XML разметке, если вы используете Alloy
Глобальный синглтон
В этом случае модель будет доступна сразу везде, во всех контроллерах. Если она уже объявлена в одном, то в другом вызов метода Alloy.Models.instance вернет ссылку на эту объявленную модель. Пример — модель User, она может быть везде одна.
Аналогично для коллекций.
Локальная ссылка
Локальную ссылку можно создать с помощью метода Alloy.createModel. Тогда эта модель будет доступна только внутри того контроллера, где создана. Метод — фабрика, передаете в него название модели (имя файла минус .js) и параметры. И все.
Аналогично для коллекций.
XML
Элемент разметки должен быть прямым потомком. И у него должен присутствовать атрибут src со значением «имя-файла-модели-минус-.js». Тогда в контроллере будет доступен синглтон этой модели в пространстве имен Alloy.Models:
var drama = Alloy.Models.book;
drama.set('title', 'Дальше живите сами');
drama.set('author', 'Джонатан Троппер');
Также у этого элемента есть атрибут instance. Если он имеет значение true, то будет создана ссылка на модель (локальная, доступна только внутри одного контроллера). Кстати, в этом случае модель не будет доступа в пространстве имен Alloy.Models, к ней нужно будет обращаться по id:
var drama = $.myBook;
drama.set('title', 'Дальше живите сами');
drama.set('author', 'Джонатан Троппер');
Аналогично для коллекций.
Наконец мы добрались до того раздела, о котором было обещано в разделе «Создание». Для работы с моделями мало просто описать схему данных. Нужно еще их к чему-то подключить. Вот это подключение и есть адаптер синхронизации.
Адаптер — это реализация Backbone.sync. Так как наиболее распространенная задача — это CRUD операции на удаленном сервере (ну, лично из моего опыта), то по умолчанию метод Backbone.sync делает RESTful JSON запросы по тем адресам, которые вы укажете в свойствах Model.urlRoot и Collection.url. Так гласит официальная документация. На практике же чтобы добиться толку от моделей/коллекций, очень удобно пользоваться адаптером restapi, о котором я расскажу чуть позже. А пока вернемся к официальной же документации.
Адаптеры могут быть четырех типов:
- sql
- properties
- localStorage (с версии alloy 1.5.0 не используется, так что не будем его рассматривать)
- свой тип
SQL
Если в конфиге в разделе adapter не определен ключ idAttribute, то Alloy сам сгенерирует в вашей таблице поле alloy_id и будет использовать его как первичный ключ.
Обратите внимание на поля конфига db_file и db_name (в разделе «Создание»). Здесь они важны.
В отличие от других типов, в этом методе Backbone.Collection.fetch может принимать целую строку SQL-запроса:
var library = Alloy.createCollection('book');
// Название таблицы - это collection_name из конфига
var table = library.config.adapter.collection_name;
// простой запрос
library.fetch({query:'SELECT * from ' + table + ' where author="' + searchAuthor + '"'});
// prepared statement
library.fetch({query: { statement: 'SELECT * from ' + table + ' where author = ?', params: [searchAuthor] }});
То есть хотите простой запрос — передавайте в параметрах объект с ключом query, а хотите сложный — передавайте и запрос, и параметры.
Кстати, можете еще передавать ключ id. В этом случае адаптер сам поймет, что вы хотите использовать ограничение WHERE id=?…
myModel.fetch({id: 123});
// то же самое, что
myModel.fetch({query: 'select * from ... where id = ' + 123 });
properties
Если ваша база — не SQLite, то вы можете создать свой адаптер и делать с данными все что угодно. Для этого создайте в каталоге app/assets/alloy/sync или app/lib/alloy/sync файл адаптера, например, myAdapter.js, а потом укажите в конфиге модели в adapter.type название этого файла, то есть «myAdapter».
Собственно файл этот должен экспортировать три функции:
- module.exports.beforeModelCreate (config) (не обязательно) — метод, как ясно из названия, выполняется перед созданием модели. В параметрах передается config этой модели. Здесь, например, можно добавить baseUrl к адресу запроса. Возвращает модифицированный объект config
- module.exports.afterModelCreate (не обязательно) — принимает два параметра: новенький только что созданный класс Backbone.Model и название файла модели
- module.exports.sync — реализация метода Backbone.sync. Он должен возвращать данные
Если вам нужно получать данные с удаленного сервера, а создавать собственный адаптер не хочется, есть замечательный адаптер restapi, который не входит в число встроенных, но выполняет свою работу (RESTful JSON запросы) на ура.
Необходимых настроек минимум — поле URL в конфиге:
exports.definition = {
config: {
"URL": "http://example.com/api/modelname",
//"debug": 1,
"adapter": {
"type": "restapi",
"collection_name": "MyCollection",
"idAttribute": "id"
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {});
return Collection;
}
}
Разумеется, это не все доступные настройки, за полным списком смотрите документацию. А что еще важного в этом адаптере — это то, что метод fetch в нем принимает словарь с тремя возможными полями:
- data параметры запроса
- success callback для успешного запроса
- error callback для ошибки
Итак, это все, что нужно знать для того, чтобы на базовом уровне уметь работать с моделями в Titanium. Тема эта несколько шире, например, я сознательно опустил здесь описание процесса миграции между различными версиями базы, data binding, использование моделей без Alloy, а также работа с SQLite. Все эти пробелы я надеюсь восполнить в следующей статье, следите за обновлениями.
Эта статья основана на а) официальной документации; б) собственном опыте использования моделей в Alloy. Если среди хабрасообщества есть люди, которые разбираются в этом вопросе лучше меня и видят ошибки или неточности, прошу, комментируйте, обсудим.