ns.Model
Модель представляет собой данные. Она однозначно идентифицируется своим ключом, который строится во время инициализации. Разный ключ всегда означает разный экземпляр модели.
Декларация
Определение новой модели происходит через статическую функцию ns.Model.define
ns.Model.define('modelName', modelDeclObject[, baseModel])
Объект-декларация состоит из следующих свойств.
ctor
ctor
- это функция-конструтор. Обратите внимание, что он вызывается самым первым, до инициализации самой модели, т.о. в конструкторе еще не доступны некоторые свойства.
Полностью готовый экземпляр бросает событие ns-model-init
.
/**
* @classdesc prj.mMyModel
* @augments ns.Model
*/
ns.Model.define('my-model', {
/**
* @constructs prj.mMyModel
*/
ctor: function() {
this._state = 'initial';
this.CONST = 100;
}
});
events
events
- объект с декларацией подписок на события noscript.
Любая подписка имеет вид:
{
"на что подписаться": "обработчик"
}
Обработчиком может быть название метода из прототипа или функция.
Пример:
{
"my-custom-event": "onCustomEvent"
}
methods
methods
- объект с методами. По сути является прототипом объекта.
/**
* @classdesc prj.mMyModel
* @augments ns.Model
*/
ns.Model.define('my-model', {
/** @lends prj.mMyModel.prototype */
methods: {
BAR: 100
foo: function(){}
}
});
params
Параметры нужны для как для построения ключа, так и для запроса моделей с сервера.
ns.Model.define('my-model', {
params: {
// Любое значение, кроме null расценивается как дефолтное значение этого параметра.
'author-login': null,
'album-id': null,
// Этим двум параметрам заданы дефолтные значения.
'page': 0,
'pageSize': 20
}
});
В запросе на сервер отправляются все параметры, которые не null
.
Важно понимать, что HTTP - текстовый протокол, поэтому все значения отправятся как строки.
Т.о. 0
станет "0"
, false
- "false"
. А это значит, что параметры, которые в вашем приложении не обрабатываются как строки, надо приводить к правильному типу на сервере. Иначе, можно получить такую ошибку
// отправили параметры как
// ?flag=false
//
if (params.flag) {
// эта ветка выполнится, потому что params.flag === "false"
}
paramsRewrite
paramsRewrite
- функция в декларации, изменяющая параметры после их создания стандартными способами.
Она вызывается всегда и ее стоит использовать для динамического изменения параметров перед созданием модели.
ns.Model.define('myModel', {
"params": {
"p1": null,
"p2": null,
"p3": null,
"p4": null
},
"paramsRewrite": function(params) {
// бизнес-логика приложения предполагает, что p1 и p3 вместе быть не могут
if (params.p1 && params.p3) {
delete params.p3;
}
return params;
}
});
Получение экземпляра модели
ns.Model.get('modelName', params)
- строит ключ изparams
и возвращает соответствующую модель. Если такого экземпляра нет, то он будет создан.ns.Model.getValid('modelName', params)
- тоже самое что иns.Model.get
. Только экземпляр еще проверяется на валидность. Если валидный экземпляр не найден, то возвращаетсяnull
.
Запрос данных модели с сервера
Данные модели:
- могут прийти с севера (модель можно явно запросить с помощью метода
ns.request()
или неявно, создав и выполнивns.Update
на странице) - могут быть установлены вручную (см. Работа с данными)
В случае запроса модели с сервера модель перезапрашивается если:
- она не валидна (
isValid()
возвращаетfalse
) - её можно запросить ещё раз (
canRequest()
возвращаетtrue
)
Локальные модели
Локальная модель - модель, которая никогда не запрашивается на сервере.
Данные такой модели устанавливают вручную.
Чтобы модель стала локальной нужно переопределить метод canRequest()
, к примеру, так:
ns.Model.define('local-model', {
methods: {
canRequest: function() {
return false;
}
}
});
Работа с данными
Методы для получения данных:
#getData()
- возвращает весь объект данных модели. Этот метод можно переопределять для доп. обработки данных. Например, для коллекции этот метод собирает актуальные данных из всех элементов.#get(jpath)
- выбирает данные по jpath и приводит результат к упрощенному виду. Результат приведения зависит как от самих данных, так и от jpath. Поэтому при изменениях формат результата может меняться.{ "foo": "1", "bar": [ { "id": 1 } ] } this.get('.foo') -> "1" this.get('.bar.id') -> ["1"]
#select(jpath)
- выбирает данные по jpath. В отличии от#get
, не занимается приведением и всегда возвращает массив результатов выборки, т.о. формат результат остается стабильным при изменениях.
{
"foo": "1",
"bar": [
{ "id": 1 }
]
}
this.get('.foo') -> ["1"]
this.get('.bar.id') -> ["1"]
Методы для изменения данных:
#set(jpath, value)
- изменяет данные по jpath. Поддерживаются только несложные jpath.
this.set('.foo', 2);
#setData(data)
- устаналивает полностью новые данные. В частности, этот метод вызывается при получении данных с сервера.
Пре- и постобработка данных
extractData
Метод извлекает данные из ответа сервера. По умолчанию берется поле data
из ответа. Если метод не возвращает данные, то считается, что модель загружена с ошибкой.
ns.Model.define('my-model', {
methods: {
extractData: function(serverResponse) {
if (serverResponse) {
return serverResponse.result;
}
}
}
});
extractError
Метода извлекает данные об ошибке сервера. По умолчанию берется поле error
из ответа.
Метод вызывается, когда #extractData()
не вернул данные.
ns.Model.define('my-model', {
methods: {
extractError: function(serverResponse) {
if (serverResponse) {
return serverResponse.error;
}
}
}
});
hasDataChanged
Этот метод может контроллировать изменились ли данные на самом деле, чтобы не вызывать лишних события и перерисовок.
Аргументом метода являются новые данные, а старые можно получить способами описанными выше, например #getData
. Должен вернуть boolean
.
ns.Model.define('my-model', {
methods: {
hasDataChanged: function(newData) {
var oldData = this.getData();
// изменяем данные, только если изменилось поле id
return oldData.id !== newData.id
}
}
});
preprocessData
Этот метод позволяет обработать полученные данные. Аргументом метода являются новые данные, должен вернуть обработанные данные.
ns.Model.define('my-model', {
methods: {
preprocessData: function(newData) {
// например, проверяем формат поля в данных
newData.field = Array.isArray(newData.field) ? newData.field : [];
return newData;
}
}
});
События
ns-model-changed
- модель изменилась. В аргументах приходит jpath, по которому было сделано изменение. Если он пустой, то изменилась вся модель (обычно методом#setData()
)ns-model-changed<.jpath>
- изменились данные по указанному jpath. В аргументах приходит jpath, по которому было сделано изменение. События кидаются иерархично, т.о. для.for.bar
будет три события:ns-model-changed.foo.bar
,ns-model-changed.foo
,ns-model-changed
ns-model-before-destroyed
- модель будет инвалидирована и уничтожена.ns-model-destroyed
- модель была инвалидированна и уничтожена.ns-model-init
- модель создана и проинициализованнаns-model-touched
- у модели изменилась версия. Такое событие будет как результатом изменения данных через#set
или#setData
, так и прямым вызовом метода#touch()
Запрос
По умолчанию все модели запрашиваются по урлу ns.request.URL
.
Если запрашиваются несколько моделей, то они группируются в один запрос.
Это поведение можно изменить с помощью метода request()
у модели, который должен вернуть Vow.Promise
.
В этом случае, вся логика запроса находится в этом методе, в том числе модель сама должна вызвать методы setData()
или setError()
.
Такие запросы не группируются, но подчиняются общим правилам ns.request
, т.е. будут работать перезапросы и пока не завершится первый запрос, нельзя сделать дублирующий.
ns.Model.define('model', {
methods: {
request: function() {
return ns.http('https://api.twitter.com', {}, {type: 'GET'})
.then(function(data) {
this.setData(data);
}, function(error) {
this.setError(error);
}, this);
}
}
});