Пользователь
0,0
рейтинг
22 июля 2011 в 17:12

Разработка → Knockout, практический опыт использования

Некоторое время назад я обещал рассказать о нашем опыте работы с Knockout. Мы используем данную библиотеку в одном из проектов в течение последних 4 месяцев. Это немного, но за это время команда набрала некоторый опыт, который, я думаю, может быть интересен читателям.

Но для начала совсем чуть-чуть теории.

MVC, MVP и MVVM.



Если с первыми двумя шаблонами большинство более-менее знакомо, о о последнем слышали очень немногие: шаблон MVVM появился в Microsoft и получил распространение в среде .net-разработчиков, но и среди них он не так широко известен.

В чем весь сыр-бор? MVC прижился в вэбе во многом потому, что он отлично ложится на сценарий запрос-ответ. Пришел запрос, он передался на соответствующий контроллер, тот инициировал какие-то процессы в модели, а затем создается View. Обычно это темплейт, который заполняется данными из модели. Затем полученный маркап передается клиенту в браузер.



Основная черта такого сценария — краткое время жизни View — он создается на каждый запрос, просто заполняется данными в один проход — и все.

Когда появился Ajax, а вслед за ним single-page applications, оказалось, что Представление живет долго, и следовательно простым темплейтом уже не обойтись. Увеличенное время жизни означает усложнение того, что принято называть lifecycle, и следовательно — появления логики на View.

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

Идея 1: Enter MVP. Логику представления выносят в особый слой и объединяют с контроллером в Презентер. Задача презентера — отделить модель от представления, отвечать за передачу данных между ними и содержать в себе логику изменения представления.



Презентер — посредник. Он по идее мог бы быть stateful или stateless. Однако на деле в нем состояние есть всегда — это состояние вида. Альтернативой было бы содержать его в модели, но этого, естественно, тоже никто не хочет. Поэтому примем, что в Презентере состояние есть.

Если посмотрим на MVP применительно к web, то наш Презентер мог бы быть объектом в JavaScript с полями состояния. Как оно меняется через HTML и взаимодействие с пользователем? — Через события DOM. Т.е. наш Презентер — это объект с полями и event listener’ами для связи с View.

Идея 2: Для связи презентера с моделью также можно использовать эвенты. Т.е. всякий раз, когда в полях объекта-презентера происходит изменение, запускается соответствующий эвент. Обработчики событий вызывают бизнес-методы в модели, и результат также отражается в виде изменений значений других полей презентера. Опять вызываются события, но на этот раз это могут быть обработчики представления, например. Тогда изменения в модели отражаются в интерфейсе пользователя. Вот такой вот event-driven workflow.

Идея 3: Теперь если инфраструктуру обработки сообщений вынести из презентера, спрятать в отдельный фреймворк, то сам объект презентер превращается в простой объект с полями. Меняя значения полей, мы инициируем какие-то процессы в модели или в представлении. Такой простой объект получил название ViewModel — он с точки зрения нашей бизнес-логики моделирует интерфейс пользователя, не вдаваясь при этом в подробности.



В .net паттерн прижился во многом благодаря тому, что для лейаута UI компонентов они стали использовать XAML — маркап, в котором привязку элементов к полям ViewModel можно описать декларативно. С другой стороны, events поддерживаются на уровне фреймворка, поэтому реализовать привязку ViewModel и Model тоже можно очень удобно.

В JavaScript у нас такой поддержки событий нет, поэтому для реализации MVVM-шаблона требуется две вещи:
  1. Механизм привязки свойств DOM-элементов к полям ViewModel.
  2. PubSub-инфраструктура для поддержки евентов, связанных с изменением значений полей ViewModel.


Knockout JS



Knockout JS — как раз такой фреймворк, который реализует эти два механизма. Для связи View-ViewModel используются data-атрибуты HTML5.

Пример:

<div id=”customerDetailsdata-bind=”visible: currentCustomer() !== null>

* This source code was highlighted with Source Code Highlighter.


currentCustomer — поле в объекте ViewModel. Вообще-то это не совсем поле:

var vewModel = {
  currentCustomer: ko.observable(defaultCustomer),

}

* This source code was highlighted with Source Code Highlighter.


Как видите, значение поля оборачивается в ko.observable. Через эту обертку Knockout следит за тем, как меняется значение поля. Т.е. там вызываются соответствующие обработчики событий. Недостатком, конечно, является то, что приходится везде работать через вызов метода, писать скобочки.

viewModel.currentCustomer = joeCoder;

* This source code was highlighted with Source Code Highlighter.


превращается в

viewModel.currentCustomer(joeCoder);

* This source code was highlighted with Source Code Highlighter.


Привыкнуть можно, но иногда скобочки в геттере забываются.

Чтобы подписаться, требуется писать следующее:

viewModel.currentCustomer.subscribe(function (newValue) {
// this === context<br>
}, context);


* This source code was highlighted with Source Code Highlighter.


Так можно привязывать ViewModel к нашим бизнес-методам. Внутри фреймворка для всех data-bind-атрибутов на самом деле создаются такие же обработчики. Вся привязка инициируется вызовом

ko.applyBindings(viewModel)

* This source code was highlighted with Source Code Highlighter.


Так что на поверхности все вполне доступно — никакой магии.

Конечно, если б дело ограничивалось только этим, то уже было бы довольно мило. Но дядя Сандерсон пошел дальше.

Во-первых, он сделал обертку для массивов: ko.observableArray. Он, правда, умеет немного: pop, push, reverse, shift, sort, splice, unshift, slice, remove, removeAll, indexOf. Вполне достаточно для работы, но некоторых изысков underscore не хватает. Понятное дело, можно прочитать значение — сам массив, — провести с ним операции, а потом записать его обратно целиком. Но получается некрасиво (почему, объясню дальше). Поэтому лучше держаться того, что уже есть.

Второе немаловажное дополнение — ko.dependentObservable(). Т.е. можно определить свойство в ViewModel, которое будет зависеть от других свойств. При этом нокаут ведет автоматический треккинг зависимостей! Это одна из самых важных фич библиотеки — настоятельно рекомендую пользоваться. Опять же, внутри никакой магии. При расчете ko следит за тем, к каким observabl’ам идет обращение и добавляет их в треккер. В случае, если значение одного из этих свойств изменилось, вызывается пересчет для зависимого свойства. Понятное дело, что в качестве свойств-основ тоже могут выступать зависимые свойства.

Что можно делать с помощью dependentObservable? Основное его назначение — упрощать логику. Часто бывает, что в data-bind оказывается сложное условие. Выглядит это очень неуютно — JavaScript в моем HTML!!1. Дебаг этого условия так же оказывается неудобным — дебаггер не будет заходить в тело атрибута. Поэтому проще вынести условие или его компонент в виде dependentObservable и в дальнейшем работать с ним.

Кстати, к вопросу об отладке. Как я уже сказал, в тело атрибута дебаггер не заходит. Все потому, что в applyBindings фреймворк пробегается по всем тегам и для всех байндингов создает свои dependentObservables. Делает он это, естественно, через eval. Не очень хорошо, но это выбор между читаемостью и чистотой. Кстати, для тех, кто считает, что видеть куски JavaScript кода в атрибутах HTML — это возврат к inline обработчикам зари интернета навроде onclick, спешу сообщить, что никто не заставляет этого делать. Можно навесить их через DOM-api, как, например, показано здесь: Unobtrusive Knockout support library for jQuery

Что еще? Есть поддержка темплейтов jQuery-Tmpl. В свое время ожидалось, что этот плагин станет частью jQuery, однако планы разработчиков поменялись. С другой стороны Микрософт его тоже активно уже не разрабатывает — единственный девелопер перешел к другим проектам. Так что проект немного мертворожденный — мой прогноз: он будет находиться в состоянии beta1 еще примерно год. Потом либо выкинут, либо доведут до ума.

Но даже для beta1 мне он показался достаточным, особенно в связке с Knockout. На самом деле в нашем проекте мы не используем темплейтной логики — всех этих выражений в фигурных скобках. Итерируемся мы через Knockout foreach, подставляем значения в тело и атрибуты тегов тоже через ko. Во-первых, начав применять data-bind-атрибуты, мы бы хотели применять их везде, а во-вторых при рендеринге через foreach в сочетании с операциями из ko.observableArray перерисовывается не вся коллекция, а только измененные элементы. Вот почему я считаю, что по возможности стоит ограничиваться только теми операциями.

Еще не стоит забывать, что в параметрах темплейта можно указать колбэк afterRender. С его помощью можно достроить в темплейте то, что силами байндингов строить неудобно, ну или выполнить какие-то дополнительные действия. Мы в afterRender инициируем виджеты, если таковые должны быть в темплейте. В колбэк передаются два параметра. Первый — это renderedNodesArray — фрагмент DOM, который отрендерил темплейт. Второй — data — данные, переданные темплейту.

Почему мне понравился этот фреймворк?


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

UI код отлаживать очень нелегко. С одной стороны, он не кажется особенно сложным, по сравнению с той же бизнес-логикой. С другой, к нему трудно подступиться, трудно выдрать его из контекста, трудно поместить его в юнит-тест, например. Knockout дает мне возможность описывать UI не с помощью кода, а с помощью правил. Это огромный плюс.

Тот факт, что правила описываются в маркапе, это тоже плюс — на мой взгляд. Вот секция, отвечающая за панель инструментов, и вот правила ее поведения. Очень удобно! Особенный жирный плюс за то, что маркап остается понятным для дизайнерских инструментов — никаких кастомных тегов, никаких XML неймспейсов. Любой редактор откроет ваш HTML без проблем.

Knockout vs. Backbone


Альтернативой Knockout сегодня для многих является Backbone. В нем есть много всего хорошего. Коллекции работают с underscore — это большой плюс при написании логики преобразования, фильтрации и т.п. Есть поддержка синхронизации данных с сервером, есть history management. Но при работе с UI я должен прописывать все связи модели с DOM руками. Это не сложно, но очень утомительно. Кода получается немного, особенно если сравнить вариант unobstrusive Knockout binding. Но тот факт, что этот монотонный код приходится писать вручную, очень огорчает.

С другой стороны, в Knockout нет ничего со стороны модели за пределами эвентов во ViewModel. Там-то как раз Backbone и силен. На деле ваш выбор в пользу той или иной библиотеки должен основываться на том, насколько большой в вашем коде составляющая UI или Model. Представьте, что вы пишите проект с нуля, не используя ни одну из этих библиотек. Если вы предвидите, что большая часть проблем возникнет на стороне DOM, то наверное, лучше взять Knockout, чтобы он облегчил вам жизнь. Если же основная сложность возникнет на стороне интеграции, то вам, возможно подойдет Backbone.

Основной плюс Backbone перед Knockout я лично вижу в Sync — способности синхронизировать данные с сервером. Однако Sync работает хорошо только в случае, когда серверное API подчиняется правилам REST. Многие думают, мол, здорово, сейчас я заставлю это работать. Однако они упускают з виду тот факт, что REST и JSON-over-HTTP — не одно и то же! Если с первым Backbone работает из коробки — просто указываешь URL ресурса, — то со вторым его нужно подпиливать. И тогда задаешься вопросом: а так ли уж Sync лучше, чем простой вызов $.ajax?

В нашем проекте пришлось иметь дело именно с JSON-over-HTTP. Кроме того, число видов сообщений, которые мы отправляем и принимаем с сервера, невелико, пока только четыре. Только один из них — синхронизация данных. Все остальное — разовые действия пользователей. Даже несмотря на то, что приходится писать эти вызовы самим, для моей команды это не слишком сложно.

Кстати о команде. Из четырех человек только я один имею опыт работы с JavaScript вообще. Чтобы начать работу, я провел с ними курс языка и библиотек. Оказалось, что для всех именно DOM api представляет наибольшую сложность, даже в облике jQuery. Отчасти это связанно с CSS и с тем, что они просто не знают, какой атрибут за что отвечает. Поэтому им очень понравилось, что можно всю работу с ним локализовать вокруг одного объекта — ViewModel. Работая над разными узлами, они практически не наступают друг другу на ноги.

Если вам интересно сравнение двух библиотек, есть серия постов. Там описывается применение для простого сценария. Но сценарии бывают разными :)

Практика


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

1. Отличие ViewModel от Model.

Не все чувствуют разницу. Простое правило:
  • Model отвечает на вопрос “Что есть в моем приложении и что оно умеет делать?”
  • ViewModel — на вопрос “Что я показываю на странице?”

К чему я это? А к тому, что многие начинают смотреть на ViewModel, как на модель. Тут у них возникает ряд вопросов. Например: в Презентере могут быть указаны список товаров, выделенный товар и индекс выделенного товара. Сразу же найдется кто-то, кто скажет, что не надо хранить и выделенный товар, и индекс — мол, данные дублируются. Однако в отличие от модели нам не важно, присутствует ли во ViewModel какая-то избыточность. Если она позволяет нам существенно упростить код приложения, то почему бы нет?

Многие также спрашивают, можно ли использовать несколько ViewModel. Можно, но нужно ли? Мы предпринимали такую попытку, но нашей ошибкой стало то, что делили мы обязанности между ViewModels в соответствии с их содержимиым — то есть работали с ними как с сущностями модели. Тогда как ViewModels представляет участки страницы и каждый из них работает только со своим участком. Потом, когда вам потребуется данные из одного ViewModel показать на другом участке страницы, напрямую через data-bind ничего не выйдет.

Так что мой вам совет: используйте один ViewModel для всей страницы — все будет просто работать. Есть риск, что объект станет большим, но никто не мешает вам в коде писать несколько раз

_.extend(ViewModel, {
// еще свойства и методы<br>
})


* This source code was highlighted with Source Code Highlighter.


Такими блоками можно разбить функциональность на модули.

2. Несмотря на то, что в примерах к Knockout Model отсутствует напрочь, а сами сторонники заявляют вам с улыбкой, что она-де на сервере, не верьте им.

На клиенте модель тоже есть, и ее придется писать ручками — в отличие от Backbone Нокаут вам в этом помогать не будет. С другой стороны, не так оно и страшно, а ваш здравый смысл и общие правила гигиены вам помогут. Помните две вещи: события и модульность. Если ajax код становится для вас проблемой, посмотрите в сторону AmplifyJS.

3. К сожалению, даже в тех редких случаях, когда Knockout хочет нам помочь в моделестроении, у него не очень получается.

Метод ko.toJSON не умеет работать с датами, и пишет в них null. Есть способы это обойти, но все они немного корявые. Самый простой — добавить еще одно dependentObservable свойство со значением JSON.stringify(date()) — там окажется просто строка, которую можно отправить на сервер. Но некрасиво! — два поля на каждую дату, а маршаллинг работает только в одну сторону. Поэтому мы пошли другим путем: просто написали собственный обходчик для ViewModel, который осторожно обходится с датами. Благо ko предлагает метод unwrapObservable который возвращает простой объект. Работает он, правда, только на верхнем уровне, так что потом приходится обходить все свойства полученного объекта на случай, если где-то еще observables остались

4. Несмотря на темплейтинг, иногда придется создавать DOM другими методами.

Особенно это справедливо для виджетов и компонентов третьих сторон. В таких случаях ko тоже может мешать: например, когда компоненту требуется простой массив, а у нас это observableArray, т.е. в коде компонента:

items.pop()

* This source code was highlighted with Source Code Highlighter.


а нам бы хотелось, чтобы там было

items().pop()

* This source code was highlighted with Source Code Highlighter.


Тут все зависит от ситуации. Иногда мы патчим компоненты для внутреннего использования — особенно, если они небольшие или если какие-то изменения в них уже были внесены. Но в большинстве случаев мы передаем туда unwrapped array, при этом приходится работать с подписками на изменения, чаще всего — писать два обработчика — на изменение данных в компоненте и на изменение данных во ViewModel.

5. Knockout работает очень хорошо в рамках традиционных бизнес-сценариев.

Формы, вывод данных, таблицы — если это именно то, что делает ваше приложение, то он вам подойдет на 100%.

Если у вас что-то экзотическое, например, работа с SVG и Canvas, много динамически создаваемых и изменяемых без вашего контроля DOM-элементов, как в нашем проекте, то у вас должны быть сомнения. Мы выбрали ko, так как в нашем проекте и от него, и от Backbone толку в целом не очень много — две трети кода вообще не связано с задачами, которые выполняют эти библиотеки. Но даже в оставшейся трети Knockout нам показался полезнее.

Knockout и Backbone


У многих из вас в процессе чтения статьи могла возникнуть идея объединить Backbone и Knockout. К сожалению об этом проще сказать, чем сделать. Функциональность фреймворков во многом пересекается, трудно определить, где нужно провести грань между ними. Мы попробовали это сделать между Knockout ViewModel и Backbone Model, но код в результате стал слишком громоздким: пришлось писать синхронизатор данных, который по размеру в сочетании с моделью Backbone стал больше, чем то, что мы писали для связи ViewModel с сервером. Отладка тоже усложнилась, а Sync оказался не таким удобным в применении для наших сервисов, как мы надеялись.

Так что пока приходится выбирать между одним или другим. В будущем наверняка появятся байндинги в Backbone и аналог Sync в Нокауте. Если вы чувствуете себя первопроходцем, являетесь ярым сторонником Backbone и хотите, чтобы в нем тоже появились байндинги, я дам вам еще один совет. Присмотритесь к jQuery-Link. Это еще один плагин от Микрософт, который они также вяло дорабатывают, как и jQuery-Tmpl. Но тем не менее, он может стать основой для реализации байндингов.

Сами Microsoft по всей видимости заняли выжидательную позицию. Steve Sunderson — автор Knockout — работает у них, а видео с его презентации на MIX11 стало самым популярным, обгоняя все кейноуты и все презентации по IE10, performance optimization и будущем HTML5, EcmaScript и любым другим технологиям компании. Сейчас Стив совместно со звездой .net Скоттом Гатри колесит по миру и показывает всем свое творение. А на этой неделе он опубликовал новые обучающие материалы. Если фреймворк “взлетит”, то компания будет поддерживать его так же, как и jQuery.

Backbone тоже никуда не денется, к нему большой интерес среди рубистов, программистов NodeJS и всего авангарда JavaScript-сообщества. Так что выбирая любой из них можете быть уверены — не прогадаете!
@alist
карма
78,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

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

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

  • +2
    Да, к сожалению, никто еще не реализовал фреймворк, который вбирал бы в себя преимущества observable нокаута, моделей бэкбоуна и роутинга pathjs (ну, или же роутинг контроллера backbone тоже вполне неплох). За ссылочку на обход дата-биндинга в HTML спасибо, хорошая идея. В целом, мне, после копания во всем вышеперечисленном всё же кажется, что нокаут гораздо проще для понимания, хотя и решает всё же немного другие задачи. И да, приятно видеть, что он развивается.
    • 0
      всему свое время. Автору спасибо за статью.
    • +2
      А как вам подход Sproutcore в плане объединения преимуществ нокаута, бэкбона и pathjs?
      • 0
        Ага, тоже советую посмотреть на SC. Он, на мой взгляд, имеет самую лучшую реализацию подходов, описанных в посте.
      • 0
        к сожалению, беглого взгляда на него хватило, чтобы задуматься об избыточности кода. Это, разумеется, субъективно. Если кто-то может показать хороший пример на SC (помимо getting started на офсайте), я буду чрезвычайно благодарен. Пока что меня больше заинтересовало javascript mvc, хоть это и немного разные инструменты.
  • 0
    > data-bind=”visible: currentCustomer() !== null”
    настораживают подобные вещи во фреймворке. это очень похоже на onclick="...", на то, от чего отказались годы назад, введя DOM3
    • 0
      Да, я потому и писал о том, что сложные выражения лучше выводить из выражений байндингов в отдельные dependentObservable — а то оглянуться не успеешь, как окажется в коде каша а ля «Привет, 99!»

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

      При таком подходе можно мысленно считать, что значения data-bind-атрибутов — не код, а правила.

      Ну и конечно, не забываем о том, что атрибуты можно расставить потом из JavaScript-кода, тогда ваш HTML останется чистым. Ссылка на реализацию подхода в статье.
  • +2
    взахлёб читал, спасибо порадовали, весьма полезно. Делитесь опытом не стесняйтесь.
  • +1
    еще один сильный минус knockoutjs по сравнению с backbone — это то, что knockoutjs по сути ведет один человек, при чем комиты совершает довольно редко. У него реально нету времени и например версия 1.3 была аннонсирована очень давно, а воз и ныне там. Я писал свой widget под knockoutjs 1.2 с кучей костылей, надеясь, что с выходом 1.3 я их повыкидываю и что 1.3 появится достаточно скоро — а сейчас сижу как у разбитого корыта — вот начинаю переписывать под backbone
  • 0
    Когда вышел на него, был безмерно счастлив (ну люблю я биндинг). Для серьезных проектов, конечно, рановато (как вобщем-то и backbone), но для фана и несложных проектов — очень даже.
    • 0
      Мы пробовали на серьезном проекте каркас строить на backbone, — к сожалению, еще действительно рано. Изобрев с десяток костылей и достаточно повысив избыточность, после очередного ревью кода было решено переписать всё заново и без backbone
      • 0
        Как я уже сказал, у нас тоже много кода, непосредственно с Нокаутом не связанного, но когда дело касается UI, Нокаут пока обычно работает на нас. Всегда есть два варианта:

        Либо DOM под нашим контролем — тогда мы используем байндинги. Это тот случай, на который Knockout и был рассчитан.

        Либо DOM создается каким-то третьим компонентом. Тогда задача заключается в привязке его к ViewModel через subscribe. При этом основные сложности возникают всегда с массивами. Если данные одиночные, то дело ограничивается тремя строчками кода, что в общем-то тоже не напряжно.
        • 0
          С Нокаутом мы над проектом не работали, когда я его пробовал, это были исключительно собственные демо приложения для тренировки. Но мысль попробовать была, он, пожалуй, один из немногих, сочетает в себе простоту понимания и использования и не делает код похожим на переписку спецслужб.
  • –1
    На первой картинке «ResponCe», а нужно «Response». Поправьте плз.
    За статью спасибо)
  • +1
    В одном из моих проектов — корпоративной интранет системе (написанной на symfony1.4) пришлось многие модули пришлось реализовывать одностранично. Причём при выборе того или иного элемента, изменении текста требовалось сразу произвести действия на сервере.
    Когда собственные велосипеды утомили то попробовал и Knockout и Backbone. Первый не пошёл вовсе, а в
    последний влюбился по уши.
    Но всё же всё не так однозначно. Knockout очень хорош когда действия пользователя не выходят за рамки страницы, но если нужно обращаться к серверу то, имхо, тут только Backbone.
    Хотя… Наверное если бы сразу подумал про Knockout+Amplify, то их совместных возможностей хватило бы для замены Backbone.
  • +2
    Вот еще интересная штука появилась: agilityjs.com/

    Выглядит похоже на пересечение нокаута и бэкбона пока что, но я пока еще толком ничего не пытался написать.
    • –1
      Ага, тоже сегодня на HackerNews увидел, буду наблюдать, но для текущего проекта вряд ли буду переезжать.
      • 0
        Ага. :) Но я, если честно, полистал демки, и что-то меня совсем испугало. В общем, не знаю, пока что для текущего проекта бекбон нравится. Код иногда приходится писать, но всë же просто… нравится. ;)
        • 0
          испугало в смысле то, что хтмл с джаваскриптом вместе. Некрасиво это всë как-то выглядит. :\
          • 0
            Мне кажется, что как и с Нокаутом, нужно искать тонкую грань между «определим правила поведения для элементов» и «OMG!!! JavaScript in my HTML!!1»

            С другой стороны, Аджилити позиционируется как библиотека для небольших приложений. Так что многие подходы, которые совершенно недопустимы для средних и больших проектов, тут вполне прокатят.
            • 0
              Кстати, на тему нокаутных биндов к джаваскрипту, есть вот такая штука для бекбона — github.com/bruth/synapse
              • 0
                Ухты, похоже на то, что делает jQuery-Link. Недавно на ScriptJunkie была статья о том, как с его помощью реализовать MVVM.
                • 0
                  Да, я уже подумываю, что можно расширить Backbone.View, и сделать this.observe('inner-el', field/expression) (inner-el — внутри this.el), и будет тотальная красота. :)

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