Статьи

Представляем Backbone.ComputedFields

Недавно я работал над небольшим проектом, которым я хочу поделиться здесь. Он называется Backbone.ComputedFields и представляет собой небольшой плагин, который немного расширяет функциональность Backbone.Model.

Мне нужно было иметь модель с «виртуальными» полями. А именно, поля, которые не принадлежат модели напрямую, но вычисляются на основе значений некоторых других полей.

Самым простым решением было бы просто ввести некоторые методы модели, скажем, model.getComputedField() / model.setComputedField()и сохранить значение внутри объекта модели. Но это оказывается плохой идеей по нескольким причинам. Во-первых, мы ломаем обычный интерфейс Backbone для получения и установки значений — model.get('computed') / model.set('computed', 100). Также, если модель привязана к представлению, мы отвечаем за ручное создание событий в случае изменения вычисляемого или зависимого поля.

Итак, после нескольких итераций родился Backbone.ComputedFields . Цели проекта: быть простым, быть декларативным, быть дружелюбным к привязке модели (читать, уважать события).

Случаи применения

Типичные сценарии использования Backbone.ComputedFields : расчет цены; объединение нескольких полей; инкапсуляция логики получения объекта по ссылке.

Очень важно, что вычисляемое поле может измениться. На основании его значения зависимые поля должны быть обновлены.

Примеры

Давайте рассмотрим несколько примеров. Модели здесь очень упрощены. Но это показывает основное применение Backbone.ComputedFields .

Расчет цены

Модель представляет продукт, который содержит цену нетто и ставку НДС.

var Produc = Backbone.Model.extend({
    initialize: function () {
        this.computedFields = new Backbone.ComputedFields(this);
    },
 
    computed: {
        grossPrice: {
            depends: ['netPrice', 'vatRate'],
            get: function (fields) {
                return fields.netPrice * (1 + fields.vatRate / 100);
            },
            set: function (value, fields) {
                fields.netPrice = value / (1 + fields.vatRate / 100);
            }
        }
    }
});

Итак, мы имеем grossPriceкак вычисляемое поле. Это поле зависит от «netPrice» и «vatRate» и рассчитывается по простым формулам.

var product = new Product({ netPrice: 100, vatRate: 20 });
var grossPrice = product.get('grossPrice');

В этом случае цена брутто будет 120.

product.set({grossPrice: 300});
var netPrice = product.get('netPrice');

После обновления цены брутто netPrice будет пересчитан, а netPrice будет равен 250.

Объединяющие поля

Давайте иметь модель для представления человека с именем и фамилией.

var Person = Backbone.Model.extend({
    initialize: function () {
        this.computedFields = new Backbone.ComputedFields(this);
    },
 
    computed: {
        fullName: {
            depends: ['firstName', 'lastName'],
            get: function (fields) {
                return fields.firstName + ' ' + fields.lastName;
            }
        }
    }
});

Я пропускаю сеттер, потому что нам не нужно указывать полное имя здесь.

var person = new Person({firstName: 'Alexander', lastName: 'Beletsky'});
var fullName = person.get('fullName');

Ссылочные объекты

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

var Invoice = Backbone.Model.extend({
    initialize: function (attrs, options) {
        this.customers = options.customers;
        this.computedFields = new Backbone.ComputedFields(this);
    },
 
    computed: {
        customer: {
            depends: ['customerId'],
            get: function (fields) {
                return fields.customerId && this.customers.get(fields.customerId);
            },
            set: function(customer, fields) {
                fields.customerId = customer.get('id');
            }
        }
    }
});

Здесь у нас есть поле клиента, которое вычисляется. 

Таким образом, я могу получить модель клиента, даже если счет-фактура просто содержит идентификатор счета-фактуры.

invoice.set({customer: anotherCustomer});
var customerId = invoice.get('customerId');

Если я меняю клиента счета-фактуры, идентификатор клиента будет инициализирован с идентификатором anotherCustomer.

Выводы

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