Pull to refresh

Все, что вы хотели знать о моделях и коллекциях в Titanium

JavaScriptDevelopment of mobile applications
Но по какой-то причине боялись спросить.

Модели — достаточно важная часть приложения, потому что без данных никуда.

В этой статье я постараюсь максимально подробно осветить все аспекты использования моделей в MVC фреймворке для разработки мобильных приложений Appcelerator Titanium.

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

Backbone.js


Итак, первое, что стоит знать при работе с моделями в Titanium, это то, что они основаны на моделях и коллекциях из Backbone. Это значит, во-первых, вы можете использовать все свойства и методы, описанные в документации Backbone; во-вторых, будет очень не лишним предварительно с ней знакомиться, если вы еще этого не сделали.

На всякий случай сделаю акцент на терминологии: модель — это единичная сущность, коллекция — это набор однотипных моделей.

Важно: Titanium использует Backbone версии 0.9.2, так что некоторые методы из документации Backbone могут быть не реализованы.

Underscore.js


Коллекции так же дополнительно наследуют методы Underscore.js для облегчения жизни разработчика. Например, метод .map

Создание


Создать модель можно двумя равносильными способами: с помощью Appcelerator Studio или вручную.
Вручную вы можете создать файл специального формата в папке <project>/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. Для этого создаем в папке <project>/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


Элемент разметки <Model> должен быть прямым потомком <Alloy>. И у него должен присутствовать атрибут src со значением «имя-файла-модели-минус-.js». Тогда в контроллере будет доступен синглтон этой модели в пространстве имен Alloy.Models:
XML

<Alloy> 
    <Model src="book" /> 
    <Window> 
        <TableView id="table" /> 
    </Window> 
</Alloy> 


Controller

var drama = Alloy.Models.book; 
drama.set('title', 'Дальше живите сами'); 
drama.set('author', 'Джонатан Троппер'); 


Также у этого элемента есть атрибут instance. Если он имеет значение true, то будет создана ссылка на модель (локальная, доступна только внутри одного контроллера). Кстати, в этом случае модель не будет доступа в пространстве имен Alloy.Models, к ней нужно будет обращаться по id:
XML

<Alloy> 
    <Model id="myBook" src="book" instance="true"/> 
    <Window> 
        <TableView id="table" /> 
    </Window> 
</Alloy> 


Controller

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-запроса:
Строка запроса в .fetch()
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=?..
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


Если вам нужно получать данные с удаленного сервера, а создавать собственный адаптер не хочется, есть замечательный адаптер restapi, который не входит в число встроенных, но выполняет свою работу (RESTful JSON запросы) на ура.
Необходимых настроек минимум — поле URL в конфиге:
Пример модели с адаптером restapi
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. Если среди хабрасообщества есть люди, которые разбираются в этом вопросе лучше меня и видят ошибки или неточности, прошу, комментируйте, обсудим.
Tags:javascriptappceleratortitaniumalloy
Hubs: JavaScript Development of mobile applications
Total votes 11: ↑10 and ↓1+9
Views3.4K

Popular right now