Tutorial: ns.model

ns.model

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);
        }
    }
});
comments powered by Disqus