Компания
547,54
рейтинг
1 июля 2014 в 13:45

Разработка → Ribs.js — вложенные атрибуты, вычисляемые поля и биндинги для Backbone.js



Привет! Меня зовут Валерий Зайцев, я клиентсайд-разработчик проекта Таргет Mail.ru. В нашем проекте мы используем небезызвестную библиотеку Backbone.js, и, конечно же, нам стало чего-то не хватать. Поразмыслив над возможными решениями наших проблем, я решил написать свое дополнение к Backbone.js, как говорится с блэкджеком и… О нем я и хочу рассказать в этой статье.

Ribs.js — библиотека, расширяющая возможности Backbone. И прелесть в том, что именно расширяет, а не изменяет. Вы можете использовать ваш любимый Backbone, как и прежде, но по необходимости задействовать новые возможности:
  • вложенные атрибуты: работа с атрибутами модели любой вложенности;
  • вычисляемые атрибуты: добавление в модель атрибутов, которые автоматически пересчитываются при изменении зависимостей (других атрибутов модели);
  • биндинги: динамическая связь между атрибутами модели и DOM-элементами.

Рассмотрим эти возможности подробнее.

Вложенные атрибуты

Начнем с самого простого и очевидного. Если вы много пишете на Backbone, то наверняка сталкивались с проблемой, когда нужно внести изменения в модель, атрибуты которой далеко не плоские.
var Simpsons = Backbone.Ribs.Model.extend({
    defaults: {
        homer: {
            age: 40,
            weight: 90,
            job: 'Safety Inspector'
        },
        bart: {
            age: 10,
            weight: 30,
            job: '4th grade student'
        }
    }
});

var family = new Simpsons();

Предположим, что Гомер плотно пообедал и набрал пару килограммов:

Backbone:
var homer = _.clone(family.get('homer'));

homer.weight = 92;
family.set('homer', homer);

Для того, чтобы не нарушать get/set подход, нам необходимо:
  1. забрать объект из модели;
  2. создать копию этого объекта;
  3. внести необходимые изменения;
  4. положить обратно.

Согласитесь, это крайне неудобно. А если учесть тот факт, что объекты могут быть огромными, то это еще и очень затратно. Куда проще изменить именно тот атрибут, который нужно:

Backbone + Ribs:
family.set('homer.weight', 92);

В результате этого set-a будет сгенерировано событие 'change:homer.weight'. Не исключена ситуация, когда вам нужно, чтобы события были сгенерированы по всей цепочке вложенности. Для этого методу set необходимо передать {propagation: true}.
family.set('homer.weight', 92, {propagation: true});

В этом случае будут сгенерированы события 'change:homer.weight' и 'change:homer'.

Вычисляемые атрибуты

Сразу оговорюсь, что я привык называть их вычисляемыми полями, поэтому прошу меня извинить за двойную терминологию. Итак, приступим. Очень часто возникает ситуация, когда атрибуты модели нужно преобразовать в определенную форму (назовем ее «результат»), а потом этот результат использовать, да еще и не в одном месте. И хорошо бы, чтобы при изменении атрибутов, результат обновлялся, и всё, что на него завязано, тоже бы обновлялось. В результате получается достаточно громоздкая вереница из дополнительных методов и подписок, которую в будущем будет достаточно проблематично поддерживать.

К примеру, Профессор Фринк задумал некое безумное исследование, в котором ему очень важно контролировать общий вес Гомера и Барта. Давайте сравним реализации на чистом Backbone и на Backbone + Ribs.

Backbone:
var Simpsons = Backbone.Model.extend({
    defaults: {
        homer: {
            age: 40,
            weight: 90,
            job: 'Safety Inspector'
        },
        bart: {
            age: 10,
            weight: 30,
            job: '4th grade student'
        }
    }
});

var family = new Simpsons(),
    doSmth = function (model, value) {
        console.log(value);
    };

family.on('change:bart', function (model, bart) {
    var prev = family.previous('bart').weight;

    if (bart.weight !== prev) {
        doSmth(family, bart.weight + family.get('homer').weight);
    }
});

family.on('change:homer', function (model, homer) {
    var prev = family.previous('homer').weight;

    if (homer.weight !== prev) {
        doSmth(family, homer.weight + family.get('bart').weight);
    }
});

var bart = _.clone(family.get('bart'));

bart.weight = 32;
family.set('bart', bart);//В консоль будет выведено: 122

var homer = _.clone(family.get('homer'));

homer.weight = 91;
family.set('homer', homer);//В консоль будет выведено: 123

Можно было написать немного по-другому, но это не сильно спасет ситуацию. Разберем, что мы здесь понаписали. Определили функцию, которая будет что-то делать с искомым суммарным весом. Подписались на обработку 'change:homer' и 'change:bart'. В обработчиках проверяем, изменилось ли значение веса, и в этом случае вызываем нашу рабочую функцию. Согласитесь, достаточно много писанины для достаточно простой и распространенной ситуации. Теперь то же самое, но короче, нагляднее и проще.

Backbone + Ribs:
var Simpsons = Backbone.Ribs.Model.extend({
    defaults: {
        homer: {
            age: 40,
            weight: 90,
            job: 'Safety Inspector'
        },
        bart: {
            age: 10,
            weight: 30,
            job: '4th grade student'
        }
    },

    computeds: {
        totalWeight: {
            deps: ['homer.weight', 'bart.weight'],
            get: function (h, b) {
                return h + b;
            }
        }
    }
});

var family = new Simpsons(),
    doSmth = function (model, value) {
        console.log(value);
    };

family.on('change:totalWeight', doSmth);

family.set('bart.weight', 32); //В консоль будет выведено: 122
family.set('homer.weight', 91); //В консоль будет выведено: 123

Что же здесь происходит?! Мы добавили вычисляемое поле, которое зависит от двух атрибутов. При изменении какого-либо из атрибутов, вычисляемое поле пересчитается автоматически. Вычисляемый атрибут можно воспринимать, как обычный атрибут.

Вы можете прочитать его значение:
family.get('totalWeight'); // 120

Можете подписаться на его изменение:
family.on('change:totalWeight', function () {});

В случае необходимости, можно описать метод set для вычисляемого поля, и сетить его без зазрения совести. Стоит отметить, что вычисляемые поля можно использовать в зависимостях других вычисляемых полей. Также, вычисляемые поля очень удобны в биндингах!

Биндинги

Биндинг — это связь между моделью и DOM-элементом. Проще тут и не скажешь. Веб-разработчику изо дня в день приходится выводить всякие данные в интерфейс. Следить за их изменениями. Обновлять. Снова выводить… А тут уже и рабочий день закончился. Вернемся к нашим желтым друзьям. Допустим, захотелось нам выводить суммарный вес в какой-нибудь span.

Backbone:
var Simpsons = Backbone.Model.extend({
    defaults: {
        homer: {
            age: 40,
            weight: 90,
            job: 'Safety Inspector'
        },
        bart: {
            age: 10,
            weight: 30,
            job: '4th grade student'
        }
    }
});

var Table = Backbone.View.extend({
    initialize: function (family) {
        this.family = family;

        family.on('change:bart', function (model, bart) {
            var prev = this.family.previous('bart').weight;

            if (bart.weight !== prev) {
                this.onchangeTotalWeight(bart.weight + family.get('homer').weight);
            }
        }, this);

        family.on('change:homer', function (model, homer) {
            var prev = family.previous('homer').weight;

            if (homer.weight !== prev) {
                this.onchangeTotalWeight(homer.weight + family.get('bart').weight);
            }
        }, this);
    },

    onchangeTotalWeight: function (totalWeight) {
        this.$('span').text(totalWeight);
    }
});

var family = new Simpsons(),
    table = new Table(family);


Backbone + Ribs:
var Simpsons = Backbone.Ribs.Model.extend({
    defaults: {
        homer: {
            age: 40,
            weight: 90,
            job: 'Safety Inspector'
        },
        bart: {
            age: 10,
            weight: 30,
            job: '4th grade student'
        }
    },

    computeds: {
        totalWeight: {
            deps: ['homer.weight', 'bart.weight'],
            get: function (h, b) {
                return h + b;
            }
        }
    }
});

var Table = Backbone.Ribs.View.extend({
    bindings: {
        'span': {text: 'family.totalWeight'}
    },

    initialize: function (family) {
        this.family = family;
    }
});

var family = new Simpsons(),
    table = new Table(family);

Теперь, при любых изменениях веса Гомера или Барта, span будет обновлен. Помимо текста, вы можете создавать и другие связи между параметрами DOM-элементов и атрибутами моделей:
  • двусторонняя связь с input-ами различных типов (text, checkbox, radio);
  • css-атрибута;
  • css-классы;
  • модификаторы;
  • и другое.

Помимо обычных биндингов в Ribs.js можно создать биндинг коллекции. Описание этого механизма заслуживает отдельной статьи, поэтому в рамках данной статьи расскажу в двух словах. Биндинг коллекции связывает коллекцию моделей, Backbone.View и некий DOM-элемент. Для каждой модели из коллекции создается свой экземпляр View и кладется в DOM-элемент. Причем при любых изменениях коллекции (добавление/удаление моделей, сортировка) интерфейс обновляется без вашего вмешательства. Тем самым вы получаете динамическое представление для всей коллекции. Область применения очевидна — разнообразные списки и структуры с однотипными данными.

Почему именно Ribs.js, а не что-то другое?

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

Три составляющие Ribs.js (вложенные атрибуты, вычисляемые поля и биндинги) могут работать независимо друг от друга. Но вся мощь раскрывается, когда вы используете их вместе (последний пример это наглядно иллюстрирует).

Ближайший известный мне конкурент — Epoxy.js. Это библиотека со схожими возможностями, но:
  • она не умеет работать с вложенными атрибутами, а это, как мы уже убедились, очень полезная вещь;
  • одну коллекцию можно использовать только в одном биндинге (в Ribs.js вы можете на базе одной коллекции создавать сколько угодно разнообразных представлений);
  • в тесте с биндингом коллекции из 10000 моделей Epoxy.js уступает Ribs.js почти в 2 раза. Исходники теста лежат здесь;
  • есть еще ряд придирок к реализации и удобству использования. В сложных задачах из-за этого приходится выдумывать обходные пути и вставлять костыли.

Используя Ribs.js, вы можете сосредоточиться на бизнес-логике, не отвлекаясь на реализацию простейших механизмов. Код становится нагляднее и компактнее, а это самым положительным образом сказывается как на самой разработке, так и на последующей поддержке. К тому же, работа над Ribs.js будет продолжена. Многие идеи, реализованные в Ribs.js родились в ходе работы над реальными боевыми задачами. Эти идеи будут появляться дальше, и лучшие из них будут попадать в следующие версии библиотеки.
Автор: @ZaValera

Комментарии (42)

  • 0
    двусторонняя связь с input-ами различных типов (text, checkbox, radio);
    css-атрибута;
    css-классы;
    модификаторы;
    и другое.

    А если надо элемент целиком удалять из DOM-дерева (и добавлять обратно потом)?
    • +2
      Есть html-биндинг — можно вставлять целые куски html-кода в определенный DOM-элемент. Если речь не об этом, то опишите задачу подробнее.
      • +1
        Вот кусочек из реального проекта, задача — убирать кнопку из DOM-дерева, если девайс нельзя отключить. Сделано на rivets.js.
        <button rv-if="device:CanBeRemoved" class="btn-del js-deleteDevice">✖</button>
        
        • 0
          Могу предложить вот такой вариант. У вас в модели есть некое поле flag, которое отвечает за наличие DOM-элемента в дереве.
          В модель добавляем вычисляемое поле:
          computeds: {
              template: {
                  deps: ['flag'],
                  get: function (flag) {
                      return flag ? '<button rv-if="device:CanBeRemoved" class="btn-del js-deleteDevice">✖</button>' : '';
                  }
              }
          }
          


          А во view добавляем биндинг:
          bindings: {
              '.some-wrapper': {
                  html: 'model.template'
              }
          }
          
          • +1
            Я понял. Пихать кусок верстки в модель как-то не очень хочется, если честно…
            • +1
              Да, не спорю, это просто пример для наглядности. Но вы можете хранить верстку, где вам удобно, а в js ее получать с помощью require.js, например.
              • 0
                Ну все равно получается, что мы в модели храним ссылку на верстку:)
                • 0
                  Вы всегда можете вызвать метод, который сгенерирует/вернет вам вёрстку необходимого элемента, в этом нет ничего зазорного. Тем самым и не «ссылка на вёрстку» и вполне себе изящное решение. Но вообще, в подобных случаях, обычно либо просто скрывают/показывают элемент, либо пользуются методикой, аналогичной предложенной мной.
                  • 0
                    Простите, я очень старомоден в этих вопросах и продолжаю считать, что модель не должна знать ровно ничего о том, как она выводится пользователю.
        • +4
          А если вам нужно просто прятать элемент в зависимости от состояния флага, то просто добавляете toggle-биндинг:
          bindings: {
              '.js-deleteDevice': {
                  toggle: 'model.flag'
              }
          }
          
          • 0
            Иногда нужно именно убирать (если верстка зависит от :nth-child селекторов, например).

            Вот я вижу у вас есть toggle-биндинг, есть text и есть html — а насколько легко добавить свой? В упомянутом rivets.js тоже есть встроенные (там они называются байндеры), и легко можно добавить свой — примерно для таких вот случаев.
            • +1
              Добавление своих типов биндингов в самых ближайших планах.
              • 0
                Круто, спасибо.
            • +1
              Добавил возможность определения своих типов биндингов. Документация будет позже, а пока просто приведу пример:
              Backbone.Ribs.View.extend({
                  bindings: {
                      '.bind-text': {
                          textCustom: 'model.text'
                      },
                      '.bind-css': {
                          cssCustom: {
                              'color': 'model.color',
                              'font-weight': 'model.weight'
                          }
                      },
                  },
              
                  handlers = {
                      textCustom: function ($el, value) {
                          $el.text(value);
                      },
              
                      cssCustom: {
                          set: function ($el, value, key) {
                              $el.css(key, value);
                          },
                          multiple: true
                      }
                  }
              })
              

              Если до появления документации возникнут вопросы, обращайтесь.
              • 0
                Спасибо!
  • +4
    А вообще штука очень клевая, мне нравится.
  • 0
    backbone это конструктор с разделением концепций. ближайший аналог можно приготовить из backbone-nested и backbone-computedfields для моделей, а так же backbone.stickit для вьюх.
    • +1
      А backbone-computedfields нормально работает с backbone-nested? Из моего опыта — не все и не всегда можно безболезненно подружить без манки-патчинга.
      • 0
        смотрите сами jsfiddle.net/n7Sfp/. backbone-nested обеспечивает полную обратную совместимость со стандартными моделями backbone, а backbone-computedfields просто аккуратная надстройка над моделями.
        • 0
          Ясно. Спасибо, что сделали фиддл:)
    • 0
      Выбор за вами. Использовать три библиотеки, которые придется еще стыковать друг с другом, или одну, в которой все это связано из коробки.
      • +1
        хорошие библиотеки Backbone стыкуются между собой практически без швов. мне кажется, что в этой магии вся соль экологии Backbone — что качественно выделяет его среди других. имхо, конечно.
      • +1
        Скажем так. Если нет серьёзных оснований что-то делать цельным большим комком вместо кучки раздельных модулей — всегда лучше делать несколько мелких модулей (ибо проще писать, поддерживать, и увеличивается гибкость). Понятно, что «выбор за нами», но от Вас, как от автора цельного решения, хочется услышать несколько более аргументированный ответ: в чём именно сложности стыковки конкретно этих трёх библиотек или какие преимущества даёт реализация этой функциональности цельным куском (которые невозможно или слишком сложно или очень медленно реализуются при использовании отдельных библиотек).
        • +2
          Вот простейший пример того, что создатели разных библиотек могут относиться к одним и тем же вещам по-разному. К примеру, {silent: true}:
          jsfiddle.net/n7Sfp/3/
          backbone-nested — проигнорировал {silent: true}
          backbone-computedfields — отреагировал на {silent: true} правильно

          Такая, казалось бы, мелочь в большом сложном проекта обойдется очень дорого. А если копнуть глубже, то я найду еще примеры «стыковки без швов».
          • 0
            По-моему в данном примере всё происходит абсолютно правильно. А чего конкретно Вы ожидали от nested — чтобы он не сработал и вместо установки значения для model.attributes.profile.email установилось значение для model.attributes['profile.email']? {silent:true} блокирует отправку события, а не «всё ломает». Событие не было отправлено. computedfields работает на событиях, поэтому он не «отреагировал правильно» а просто не сработал. На мой взгляд — всё ведёт себя корректно и предсказуемо.

            А как в аналогичных условиях отреагирует ribs.js?
            • 0
              А Вас не смущает, что одни биндинг обновился, а другой нет? Такое поведение ну никак не подходит под «корректно и предсказуемо».
              В ribs.js при set-е с {silent: true} ни один биндинг не обновится.
              При обычном set-е, соответственно, обновятся оба биндинга.
              • 0
                А, я понял о чём Вы. Но ведь дело вовсе не в {silent:true}! Поменяйте местами model.set и view.render — и оба биндинга не обновятся, как Вы и хотели.

                А так получается, что Вы сначала явным {silent:true} рассинхронизируете обычные и вычисляемые поля, потом рендерите модель в этом состоянии, а потом почему-то обвиняете биндинги — которые вообще в этом процессе пока никак не участвовали.
                • 0
                  рассинхронизируете обычные и вычисляемые поля

                  А вот этого быть не должно! Вычисляемое поле должно обновиться не зависимо от флага {silent: true}. А иначе вы получаете неконсистентную модель, и из этого вытекают нюансы, когда дергать render и др.
                  • 0
                    Вычисляемое поле должно обновиться не зависимо от флага {silent: true}.
                    С какой стати? Есть базовая функциональность Backbone — она работает всегда. Есть много разной дополнительной функциональности — предоставляемой плагинами Backbone или реализованной в самом приложении — которая обычно работает на событиях Backbone. В официальной документации по поводу {silent:true} сказано предельно чётко:
                    Note that this is rarely, perhaps even never, a good idea.
                    Что логично, т.к. обычно это сломает вышеупомянутую дополнительную функциональность.

                    И получается, что (если я Вас правильно понял) Вы утверждаете, что бывает разные виды этой дополнительной функциональности: которые должны ломаться при {silent:true} (плагины второго сорта), и которые не должны ломаться при {silent:true} (плагины первого сорта). Вот это как раз и вносит совершенно лишние нюансы, непредсказуемость и неконсистентность поведения.

                    Если я передаю {silent:true} я хочу, чтобы отработала только базовая функциональность Backbone — а иначе зачем бы мне это вообще делать? И я не думаю, что плагины вроде Ribs.js должны считать меня идиотом и игнорировать {silent:true} только потому, что им кажется что я бы этого хотел. Вообще, пытаться защищать пользователя от стрельбы себе в ногу имеет смысл на некоторых языках, но Javascript, Perl и им подобные к ним никак не относятся.

                    Ещё какие-то примеры проблем вызванных стыковкой nested, computedfields и stickit есть?

                    • 0
                      Давайте захватим цитату чуть раньше:
                      if you'd like to prevent the event from being triggered, you may pass {silent: true} as an option.

                      Если я передаю {silent:true}, я хочу, чтобы не было триггера событий и ничего больше. Ни о какой базовой и дополнительной функциональности речи не идет. Этот флаг нужен исключительно для этого. Если приведенный пример для Вас является нормой, и это ровно то поведение, которое вы ожидаете, то мне нечего добавить. Я считаю такое поведение неправильным и неприемлемым.
                      И я не разделяю библиотеки на сорта и уж тем более не считаю никого идиотом. У нас с Вами разные взгляды на вещи, и я предлагаю остаться каждому при своем мнении, и закончить эту исчерпавшую себя дискуссию.
                      • 0
                        Если я передаю {silent:true}, я хочу, чтобы не было триггера событий и ничего больше.
                        Вам не кажется, что это звучит бессмысленно? События сами по себе ничего не меняют, и никто не отключает события ради самого отключения событий. Их отключают для того, чтобы не запустились обработчики событий — т.е. чтобы не сработала та самая дополнительная функциональность, о которой я говорил.
                        Я считаю такое поведение неправильным и неприемлемым.
                        Понятно. Я согласен, что у нас разные взгляды и что продолжать дискуссию смысла нет — Вы абсолютно в своём праве делать так, как считаете правильным. Но я бы хотел обратить Ваше внимание на то, что по сути Ваш подход нарушает базовый принцип Backbone:
                        Philosophically, Backbone is an attempt to discover the minimal set of data-structuring (models and collections) and user interface (views and URLs) primitives that are generally useful when building web applications with JavaScript. In an ecosystem where overarching, decides-everything-for-you frameworks are commonplace, and many libraries require your site to be reorganized to suit their look, feel, and default behavior — Backbone should continue to be a tool that gives you the freedom to design the full experience of your web application.
                        Приняв решение, что в Ribs.js целостность данных важнее свободы пользователя заблокировать функциональность Ribs.js явно передав {silent:true} Вы двинулись по вышеупомянутому пути «decides-everything-for-you». Это абсолютно нормально если Вы делаете полноценный высокоуровневый фреймворк поверх Backbone, но, на мой взгляд, является ошибкой в случае разработки плагина к Backbone — потому что плагин в первую очередь должен вести себя предсказуемо и единообразно — так, как большинство остальных плагинов.
                        • +1
                          Здравствуйте, powerman.

                          Вот Вы пишите:
                          Приняв решение, что в Ribs.js целостность данных важнее свободы пользователя заблокировать функциональность Ribs.js явно передав {silent:true} Вы двинулись по вышеупомянутому пути «decides-everything-for-you».

                          А как тогда быть с тем фактом, что бекбон самостоятельно поддерживает целостность данных?
                          Простой пример: пусть у нас есть коллекция col с 5ю моделями.

                          console.log(col.length); // 5
                          
                          col.add({foo:'bar'}, {silent: true});
                          
                          console.log(col.length); // 6
                          


                          События добавления в коллекцию не произошли, но бекбон не смотря на переданную инструкцию {silent:true} сохраняет целостность коллекции.
                          Так что подход ZaValera никак не идет в разрез с концепцией бекбона.

                          Get вычисляемых полей должен по сути всегда забирать их зависимости и вычислять правильные значения — это логичное и понятное поведение. Не только лишь в Ribs, но и в упомянутом в статье Epoxy.

                          {silent: true} на get никак не влияет — что в модели лежит, то мы и получим.

                          В Ribs, как я понял, для того, чтобы по много раз при get'е вычисляемых полей не обсчитывать одно и тоже, они вычисляются один раз при set'е их зависимостей. Что логично с точки зрения оптимизации.

                          P.S. Вообще я с трудом представляю практический смысл в неконсистентых вычисляемых полях. Равно как не вижу в чем Ribs ограничивает меня как разработчика.
                          • 0
                            Вообще я с трудом представляю практический смысл в неконсистентых вычисляемых полях.
                            Никто этого смысла не видит — потому что его нет. Как и в том, чтобы передавать {silent:true}. Изначально я попросил ZaValera привести причину, по которой вместо трёх простых маленьких плагинов сделан один большой цельный — он привёл единственный пример с {silent:true} — на мой взгляд не особо адекватный пример, т.к. не понятно кому и зачем нужно будет использовать {silent:true}, но тем не менее пример имеет право на жизнь (тем более, что других примеров нет). Поэтому мы и обсуждаем неконсистентность вычисляемых полей.
                            Равно как не вижу в чем Ribs ограничивает меня как разработчика.
                            Он ограничивает в том, что пытается решать за разработчика что тот хотел получить в результате {silent:true} — возможно в большинстве случаев он угадает и разработчик будет счастлив, но что делать в оставшихся случаях? Backbone хорош именно тем, что не накладывает никаких ограничений и даёт возможность стоить поверх себя абсолютно любую архитектуру. Как правило нет никакого смысла передавать {silent:true}, о чём говорит и официальная дока, но если всё-таки передали — ожидается, что это заблокирует выполнение всех обработчиков события 'change:' (в нашем примере), а не всех кроме вычисляемых полей ribs.js.
                            События добавления в коллекцию не произошли, но … Так что подход ZaValera никак не идет в разрез с концепцией бекбона.
                            По этой логике {silent:true} не должен иметь вообще никакого эффекта — всё должно работать не зависимо от того, произошли события или нет. Или не всё, а только некоторые «критичные» плагины — я об этом уже писал выше, про плагины первого и второго сорта.
                            • +1
                              Никто этого смысла не видит — потому что его нет. Как и в том, чтобы передавать {silent:true}

                              Если нет смысла в неконсистентых вычисляемых полях, то зачем ожидать их неконсистентности при передаче {silent:true}?

                              он привёл единственный пример с {silent:true} — на мой взгляд не особо адекватный пример, т.к. не понятно кому и зачем нужно будет использовать {silent:true}

                              Пример ZaValera более чем адекватный — неоднократно сталкивался в рабочих задачах с необходимостью провести несколько «тихих» пакетных изменений в куче моделей (в случае с множеством подписок на change, которые, например, меняют сильно DOM). После, конечно же, руками триггерились нужные события/вызывались нужные методы. Т.е. я хочу привести в пример задачу по оптимизации.

                              По этой логике {silent:true} не должен иметь вообще никакого эффекта — всё должно работать не зависимо от того, произошли события или нет. Или не всё, а только некоторые «критичные» плагины — я об этом уже писал выше, про плагины первого и второго сорта.

                              Так про то и речь, что silent:true — не инструмент для блокирования какой-то «дополнительной» функциональности, а просто режим, в котором не триггерятся события.

                              Он ограничивает в том, что пытается решать за разработчика что тот хотел получить в результате {silent:true} — возможно в большинстве случаев он угадает и разработчик будет счастлив, но что делать в оставшихся случаях? Backbone хорош именно тем, что не накладывает никаких ограничений и даёт возможность стоить поверх себя абсолютно любую архитектуру. Как правило нет никакого смысла передавать {silent:true}, о чём говорит и официальная дока, но если всё-таки передали — ожидается, что это заблокирует выполнение всех обработчиков события 'change:' (в нашем примере), а не всех кроме вычисляемых полей ribs.js.

                              Просто Вы воспринимаете вычисляемые поля как механизм, связанный с подписками на события. В то время они таковым не являются. И следовательно никак не должны зависеть от {silent:true}.
                              Если же мыслить не в контексте «основная и дополнительная» функциональность, а в контексте Ribs.js, то все очень стройно.

                              И да, Вы всегда можете описать вычисление каких-то атрибутов модели самостоятельно через подписки, так что {silent:true} не будет их менять. Или используете механизм Ribs, с иным поведением. Т.е. у Вас есть выбор. Так что скорее тут не ограничение, а расширение возможностей разработчика.
                              • 0
                                Просто Вы воспринимаете вычисляемые поля как механизм, связанный с подписками на события. В то время они таковым не являются.
                                Отлично, мы потихоньку подбираемся к сути вопроса. А почему, собственно, не являются? Кто решает, какие механизмы (реализованные через подписки на события!) считать связанными с подписками, а какие нет?
                                • 0
                                  А с чего Вы решили, что механизм вычисляемых полей в Ribs реализован через подписки на события?
                                  Кто решает, какие механизмы (реализованные через подписки на события!) считать связанными с подписками, а какие нет?

                                  Я, как автор библиотеки, решаю какие механизмы я буду реализовывать и КАК я их буду реализовывать.
                                • 0
                                  Очевидно, что разработчик решает и предлагает Вам свой подход. Вы можете с ним согласиться и использовать инструмент, а можете не согласиться и не использовать. Все очень просто. :)
                              • 0
                                неоднократно сталкивался в рабочих задачах с необходимостью провести несколько «тихих» пакетных изменений в куче моделей (в случае с множеством подписок на change, которые, например, меняют сильно DOM). После, конечно же, руками триггерились нужные события/вызывались нужные методы.
                                Кстати, а почему Вы решили не делать так, как рекомендует Backbone?
                                Passing through a specific flag in the options for your event callback to look at, and choose to ignore, will usually work out better.

                                • 0
                                  {silent: true} отличный инструмент, которой прекрасно справляется со своими задачами. Да, в неумелых руках он может привести к нежелательным последствиям, поэтому разработчики Backbone об этом предупреждают. Зачем мне придумывать лишние флаги, когда это уже заложено в Backbone. А на поздних этапах разработки, когда подписчиков уже достаточное количество, вводить новый флаг и его обработку крайне затратное мероприятие.
  • +1
    И в итоге получился canjs
  • +1
    И прелесть в том, что именно расширяет, а не изменяет. Вы можете использовать ваш любимый Backbone, как и прежде, но по необходимости задействовать новые возможности:

    Я не встречал ни одной библиотеки для Backbone, которая поступает иначе. Перекрывать возможности этого фреймворка, имхо, кощунство.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка