company_banner

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 родились в ходе работы над реальными боевыми задачами. Эти идеи будут появляться дальше, и лучшие из них будут попадать в следующие версии библиотеки.
    Mail.Ru Group 796,19
    Строим Интернет
    Поделиться публикацией
    Похожие публикации
    Комментарии 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
                            }
                        }
                    })
                    

                    Если до появления документации возникнут вопросы, обращайтесь.
        • +4
          А вообще штука очень клевая, мне нравится.
          • 0
            backbone это конструктор с разделением концепций. ближайший аналог можно приготовить из backbone-nested и backbone-computedfields для моделей, а так же backbone.stickit для вьюх.
            • +1
              А backbone-computedfields нормально работает с backbone-nested? Из моего опыта — не все и не всегда можно безболезненно подружить без манки-патчинга.
              • 0
                смотрите сами jsfiddle.net/n7Sfp/. backbone-nested обеспечивает полную обратную совместимость со стандартными моделями backbone, а backbone-computedfields просто аккуратная надстройка над моделями.
            • 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, которая поступает иначе. Перекрывать возможности этого фреймворка, имхо, кощунство.

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

                      Самое читаемое