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

А если надо элемент целиком удалять из DOM-дерева (и добавлять обратно потом)?
Есть html-биндинг — можно вставлять целые куски html-кода в определенный DOM-элемент. Если речь не об этом, то опишите задачу подробнее.
Вот кусочек из реального проекта, задача — убирать кнопку из DOM-дерева, если девайс нельзя отключить. Сделано на rivets.js.
<button rv-if="device:CanBeRemoved" class="btn-del js-deleteDevice">✖</button>
Могу предложить вот такой вариант. У вас в модели есть некое поле 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'
    }
}
Я понял. Пихать кусок верстки в модель как-то не очень хочется, если честно…
Да, не спорю, это просто пример для наглядности. Но вы можете хранить верстку, где вам удобно, а в js ее получать с помощью require.js, например.
Ну все равно получается, что мы в модели храним ссылку на верстку:)
Вы всегда можете вызвать метод, который сгенерирует/вернет вам вёрстку необходимого элемента, в этом нет ничего зазорного. Тем самым и не «ссылка на вёрстку» и вполне себе изящное решение. Но вообще, в подобных случаях, обычно либо просто скрывают/показывают элемент, либо пользуются методикой, аналогичной предложенной мной.
Простите, я очень старомоден в этих вопросах и продолжаю считать, что модель не должна знать ровно ничего о том, как она выводится пользователю.
А если вам нужно просто прятать элемент в зависимости от состояния флага, то просто добавляете toggle-биндинг:
bindings: {
    '.js-deleteDevice': {
        toggle: 'model.flag'
    }
}
Иногда нужно именно убирать (если верстка зависит от :nth-child селекторов, например).

Вот я вижу у вас есть toggle-биндинг, есть text и есть html — а насколько легко добавить свой? В упомянутом rivets.js тоже есть встроенные (там они называются байндеры), и легко можно добавить свой — примерно для таких вот случаев.
Добавил возможность определения своих типов биндингов. Документация будет позже, а пока просто приведу пример:
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
        }
    }
})

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

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

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

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

А вот этого быть не должно! Вычисляемое поле должно обновиться не зависимо от флага {silent: true}. А иначе вы получаете неконсистентную модель, и из этого вытекают нюансы, когда дергать render и др.
Вычисляемое поле должно обновиться не зависимо от флага {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 есть?

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

Если я передаю {silent:true}, я хочу, чтобы не было триггера событий и ничего больше. Ни о какой базовой и дополнительной функциональности речи не идет. Этот флаг нужен исключительно для этого. Если приведенный пример для Вас является нормой, и это ровно то поведение, которое вы ожидаете, то мне нечего добавить. Я считаю такое поведение неправильным и неприемлемым.
И я не разделяю библиотеки на сорта и уж тем более не считаю никого идиотом. У нас с Вами разные взгляды на вещи, и я предлагаю остаться каждому при своем мнении, и закончить эту исчерпавшую себя дискуссию.
Если я передаю {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 — потому что плагин в первую очередь должен вести себя предсказуемо и единообразно — так, как большинство остальных плагинов.
Здравствуйте, 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 ограничивает меня как разработчика.
Вообще я с трудом представляю практический смысл в неконсистентых вычисляемых полях.
Никто этого смысла не видит — потому что его нет. Как и в том, чтобы передавать {silent:true}. Изначально я попросил ZaValera привести причину, по которой вместо трёх простых маленьких плагинов сделан один большой цельный — он привёл единственный пример с {silent:true} — на мой взгляд не особо адекватный пример, т.к. не понятно кому и зачем нужно будет использовать {silent:true}, но тем не менее пример имеет право на жизнь (тем более, что других примеров нет). Поэтому мы и обсуждаем неконсистентность вычисляемых полей.
Равно как не вижу в чем Ribs ограничивает меня как разработчика.
Он ограничивает в том, что пытается решать за разработчика что тот хотел получить в результате {silent:true} — возможно в большинстве случаев он угадает и разработчик будет счастлив, но что делать в оставшихся случаях? Backbone хорош именно тем, что не накладывает никаких ограничений и даёт возможность стоить поверх себя абсолютно любую архитектуру. Как правило нет никакого смысла передавать {silent:true}, о чём говорит и официальная дока, но если всё-таки передали — ожидается, что это заблокирует выполнение всех обработчиков события 'change:' (в нашем примере), а не всех кроме вычисляемых полей ribs.js.
События добавления в коллекцию не произошли, но … Так что подход ZaValera никак не идет в разрез с концепцией бекбона.
По этой логике {silent:true} не должен иметь вообще никакого эффекта — всё должно работать не зависимо от того, произошли события или нет. Или не всё, а только некоторые «критичные» плагины — я об этом уже писал выше, про плагины первого и второго сорта.
Никто этого смысла не видит — потому что его нет. Как и в том, чтобы передавать {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, с иным поведением. Т.е. у Вас есть выбор. Так что скорее тут не ограничение, а расширение возможностей разработчика.
Просто Вы воспринимаете вычисляемые поля как механизм, связанный с подписками на события. В то время они таковым не являются.
Отлично, мы потихоньку подбираемся к сути вопроса. А почему, собственно, не являются? Кто решает, какие механизмы (реализованные через подписки на события!) считать связанными с подписками, а какие нет?
А с чего Вы решили, что механизм вычисляемых полей в Ribs реализован через подписки на события?
Кто решает, какие механизмы (реализованные через подписки на события!) считать связанными с подписками, а какие нет?

Я, как автор библиотеки, решаю какие механизмы я буду реализовывать и КАК я их буду реализовывать.
Очевидно, что разработчик решает и предлагает Вам свой подход. Вы можете с ним согласиться и использовать инструмент, а можете не согласиться и не использовать. Все очень просто. :)
неоднократно сталкивался в рабочих задачах с необходимостью провести несколько «тихих» пакетных изменений в куче моделей (в случае с множеством подписок на 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.

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

Я не встречал ни одной библиотеки для Backbone, которая поступает иначе. Перекрывать возможности этого фреймворка, имхо, кощунство.
Only those users with full accounts are able to leave comments. Log in, please.