воскресенье, 11 декабря 2011 г.

Что такое Knockout JS или MVVM на javascript. Пример

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

Что это такое и зачем надо ?

Рассмотрим пример, сразу говорю выдумал на ходу, но он отображает суть.
Вы можете купить продукт из списка, завернуть в подарочную упаковку,  указать надпись на коробке выбрать доставку и сразу видеть цену, которая меняется от выбранных опций и Summary, которое отображает выбранные опции как результат.
При нажатии на 'Sumbit' отображает JSON, которы может поститься на сервер или тп.


Сколько бы вы делали этот пример просто на JS + jQuery ? Конечно, вы можете сказать "та нехер делать", но код будет выглядеть месивом из подписывания на события, if'ы для треканья зависимостей между элементами, что показать, какой скрыть, потом будете еще дэбажить, а потом начнете переосмысливать жизненные ценности и позавете Darth Vadera, чтоб он все разрулил :).

На входе

- Набор элементов формы (View)
- Данные, которые отражаются и отображаются в этих элементах (Model) и  бизнес логика, которая скрывает, отображает элементы и делает расчеты (Controller или ViewModel).

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

Итого, как мы видим, получается MVC, но более точно это MVVM, шаблон группы MVx, который был придуман под технологию WPF и очень хорошо укладывается с возможностями этой технологии.
В WPF это реализуется с помощью binding ("биндинга") и класса (ViewModel) с dependency property и routed events.

Наш ViewModel и Dependancy Property

 Как мы видим у нас есть свойства, которые создаются с помощью observable. Так экоститема knockout знает, что если значение этого свойство набиндить на элемент, например, Address то это значение отобразится в текстовом поле. Если пользователь изменит значение текстового поля, новое значение запишется обратно в свойство (Address), с которым связан элемент через observable. Получается двухсторонняя связь. Можно сделать и в одну сторону, это потом.

Наш Binding 



У нас есть один атрибут datа-bind, в которм мы указываем с каким свойством (значением) связывать этот элемент и дополнительные опции. Для разных элементов свойства привязываются по разному, но в общем значение datа-bind начинается так - "value: Property"

Рассмотрим по порядку:

select data-bind="options: Products, value: Product" - это значит, что мы биндим список продуктов на выпадающий список и если выбранный продукт изменится, это значение запишется в свойство Product. Если бы мы указали viewModel.Product = ko.observable("Jeans") то сразу выбраными были бы "Jeans".
input type="radio" data-bind="checked: GiftWrap" - это значит, что мы биндим value радиокнопки на свойство GiftWrap. Какая радиокнопка выбрана - такое значение и будет "Yes" или "No". Ничего не выбранно - значит ничего не будет, null.
div data-bind="visible: GiftWrap() == 'Yes'" - тут начинается интересное. С помощью visible мы решаем будет ли элемент отображатся по условию после двоеточия. Если мы поставим радиокнопку в Yes, будет div style="display: none", в другом случае div style="display: block". Как это работает ? Как только одно из observable свойств меняется, экосистема knockout проверяет весь биндинг и пересчитыват условия в атрибутах.
С простыми свойствами я думаю несложно. Давайте рассмотрим этот кусок
и код

Если бы у нас было observable, мы бы не смогли посчитать Total, т. к. оно зависит от других значений свойств. Для этого мы используем новый тип свойства dependentObservable, который обозначает, что это свойство считается динамически. Сколько свойств во ViewModel изменится, столько раз knockout пересчитает свойство Total. Нам остается только отобразить это свойство в span data-bind="text: Total". Это все работает автоматически !!! Достаточно только поставить нужное значение и опции в атрибут data-bind. Точно также работает Summary.
Я не буду дальше описывать в деталях синтаксис и тп, на сайте knockout очень хорошо все расписано с отличными примерами. Моя цель была заинтересовать и показать, как можно избавиться от кучи ненужной привязки к событиям через jQuery, месива, рассредоточеной бизнес логики и других "прелестей" при работе с сложным UI и JS.
Я считают, что этот фреймворк должен быть на счету у каждого серъезного разработчика.

Что может этот фреймворк:
- простой биндинг, поле-свойство
- сложный биндинг, динамическое значение поля
- можете писать свои настройки типо как value, checked и тп.
- можете использовать jTemplate
- можете использовать ajax
- можете биндить события, типо click, chenched и тп
- можете биндить не только на значение элемента, но и например явно на атрибуты или стили, например если вы хотите чтоб в style свойство color менялось если сумма больше $20 и тп.
- можете делать все что угодно, если мой пример отображает суть вашей проблемы.

Мне очень нравится этот фреймворк. Читаешь доку, смотришь код и все как-то интуитивно понятно, запоминай только опции в атрибутах, а так работать одно удовольствие. Уже не раз сокращал старый код в раза полтора-два точно. Но это не главное, а главное что бизнес логика в одном месте, а вьюха в другом.
Есть еще подобный фреймворк backbones, но мне он показался ужасным и более низкоуровневым, там надо много писать руками, очен много, но у него есть свои преимущества.

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


5 комментариев:

  1. Поменяй два раза "на выходе" написано. Имелось ввиду на входе 100%

    ОтветитьУдалить
  2. Прочитал - интересно все написано.

    ОтветитьУдалить
  3. Спасибо. Познавательно. Буду применять. Если я правильно понял возможности библиотеки - то это бомба.

    ОтветитьУдалить