На Хабре уже много писали про Knockout.js (раз, два, три, четыре, пять, видео). Для тех кто не в курсе, Knockout.js — это популярная JavaScript библиотека, позволяющая реализовать Model-View-View Model (MVVM) паттерн на клиенте. Освоить Knockout.js можно очень быстро — ведь есть система интерактивного обучения, куча живых примеров (можно потыкать и посмотреть исходный код) и прекрасная документация.
Очень часто Knockout.js используют в связке с ASP.NET MVC — ведь библиотека существенно упрощает написание клиентской логики. Однако, возникает много типичных проблем для клиент-серверной разработки: основную модель и часть логики её обработки приходится описывать как на клиенте (JavaScript), так и на сервере (C#/VB). Кроме того, есть рутинная часть, связанная с обращением клиента к серверным методам и передачи им модели для обработки. Но не стоит печалиться! Теперь у нас есть Knockout MVC — это .NET оболочка для Knockout.js, которая генерирует весь нужный JavaScript-код за нас. Нам остаётся только описать нашу модель на C# и в MVVM-стиле указать для каждого нужного html-элемента к какому свойству модели нужно привязаться (а можно указать и целые выражения — они будут транслированы в js). Таким образом, можно получить полноценное кроссбраузерное клиентское веб-приложение без единой строчки JavaScript!
А теперь немного подробнее
Модель
Модель пишется один раз на сервере (скажем, на C#). На стороне клиента весь JavaScript-код сгенерируется автоматически. Более того, мы можем описывать не только обычные свойства, но и не очень сложные выражения, которые будут транслированы в JavaScript. Причём это всё делается не только для основной модели, но и для её подмоделей (на чистом Knockout.js это делать совсем не так просто). Ну, например, описываем мы такую модель:
public class Item
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Expression<Func<string>> FullName()
{
return () => FirstName + " " + LastName;
}
}
public class Model
{
public List<Items> Items { get; set; }
}
// ...
var model = new Model
{
Items = new List<Item>
{
new Item {FirstName = "Annabelle", LastName = "Arnie"},
new Item {FirstName = "Bertie", LastName = "Brianna"},
new Item {FirstName = "Charles", LastName = "Cayenne"},
}
};
а по ней автоматом сгенерируется вот такой JavaScript:
var viewModelJs = {"Items":[{"FirstName":"Annabelle","LastName":"Arnie"},{"FirstName":"Bertie","LastName":"Brianna"},{"FirstName":"Charles","LastName":"Cayenne"}]};
var viewModelMappingData = {
'Items': { create: function(options) {
var data = ko.mapping.fromJS(options.data);
data.FullName = ko.computed(function() { try { return this.FirstName()+' '+this.LastName()} catch(e) { return null; } ;}, data);
return data;
}}};
var viewModel = ko.mapping.fromJS(viewModelJs, viewModelMappingData);
ko.applyBindings(viewModel);
В данном примере полученный JavaScript относительно простой, но при чуть более сложной модели писать его ручками не так уж и приятно. А если вам захочется его порефакторить, то вам нужно будет синхронно и очень аккуратно менять JavaScript-модель и C#-модель (в нормальном приложении модель на C# всё равно понадобится для описания логики действий над этой самой моделью).
Привязки данных
Если вы работаете с ASP.NET MVC, то для описания представлений вы наверняка используете Razor View Engine. Если нет, то желательно хотя бы ознакомиться с функциональностью данного движка — он поднимает вёрстку сайта на новый уровень. Далее пойдут примеры представлений именно под Razor.
Ну, например я хочу создать TextBox, который будет содержать значение некоторого свойства A нашей модели. Мне достаточно написать:
@ko.Html.TextBox(m => m.A)
В скобочках указывается лямбда-выражение, которое нашей модели m сопоставляет свойство, к которому я бы хотел сделать привязку данных. Причём в данном случае мы пишем выражение на C#, а это означает что нам помогает IntelliSense:
А если я случайно напишу название неправильно, то узнаю об этом ещё на этапе компиляции — красная полосочка в студии покажет, что где-то я ошибся.
Ну, а теперь посмотрим пример посложнее. Например, я хочу, чтобы часть странички выводилась тогда и только тогда, когда в моей модели выполняется одновременно два каких-либо условия. Всё, что мне нужно написать:
@using (ko.If(model => model.Condition1 && model.Condition2))
Далее в фигурных скобочках указывается соответствующая часть представления. Просто, не правда ли? Ведь написав одну такую строчку, можно про неё сразу забыть — нам не нужно отслеживать состояние Condition1 и Condition2, писать js-код, который будет управлять видимостью элементов и т.п. А если мы заходим переименовать одно из условий или превратить его в computed-свойство, то магия рефакторинга сама поправит наше выражение (и, соответственно, исправится генерируемый JavaScript).
Knockout MVC содержит много полезных конструкций. Например, можно очень легко перебрать элементы какой-либо коллекции и для каждого элемента вывести чего-нибудь важное:
@using (var items = ko.Forearch(m => m.Items))
Полное описание всех доступных синтаксических конструкций можно посмотреть в документации.
Обращение к серверу
Ну, а теперь попробуем создать кнопку, которая вызывает некоторый серверный метод, выполняющий операции над моделью. Нам нужно задуматься о следующих моментах в нашем JavaScript-коде:
- Нужно собрать данные о текущей модели и сериализовать их в строчку
- Нужно привязать к нашей кнопке ajax-отсылку этой строчки на сервер
- Нужно как-то описать логику получения данных с сервера и обновления визуальных элементов
Стоп! Ничего этого не нужно делать! Нужно просто написать вот такую строчку:
@ko.Html.Button("Some text", "FooAction", "Foo")
Вдумчивый читатель спросит: «А если я хочу передать в серверный метод некоторые параметры?» Нет ничего проще:
@ko.Html.Button("Some text", "FooAction", "Foo", new { index = 0, caption = "Knockout MVC"})
А вот так мы опишем соответствующий метод на сервере:
public class FooController : KnockoutController {
public ActionResult FooAction(FooModel model, int index, string caption)
{
model.FooAction(index, caption); // Тут будет основная логика
return Json(model);
}
}
Это всё! Ни одной лишней строчки писать не нужно! Можно вообще забыть о том, что у нас клиент-серверное приложение с сопутствующей технической рутиной — при разработке нужно концентрироваться только на логике модели! Посмотреть подробнее, как всё это работает, можно тут
Работа с контекстами
Что в Knockout.js сделано прям совсем не очень удобно — это работа с контекстами. Вложенные контексты возникают тогда, когда вы используете вложенные foreach
и with
конструкции. Если у вас один уровень вложенности — то всё хорошо. Но если вы пишете сложное приложение, в котором возникает 4-5 уровней вложенности, то иногда можно встретить конструкции вида $parents[2].name
или $parentContext.$parentContext.$parentContext.$index
. Воспринимать такой код достаточно сложно, а когда дело доходит до рефакторинга — то совсем становится грустно. В Knockout MVC писать намного проще — ведь теперь у каждого контекста есть своё имя! И теперь код выглядит так:
@using (var subModel = ko.With(m => m.SubModel))
{
using (var subSubModel = subModel.With(m => m.SubSubModel))
{
@subSubModel.Html.Span(m => ko.Model.A + " " + subModel.GetIndex() + " " + m.B)
}
}
Вложенное выражение само превратится в:
<span data-bind="text : $parents[1].A()+' '+$parentContext.$index()+' '+B()"></span>
Можно посмотреть пример.
Другие вкусные плюшки
Кроме всего прочего, в библиотеке имеются некоторые дополнительные функции, которые иногда могут существенно облегчить жизнь:
- Ленивая загрузка данных. Когда страничка грузится долго — это всегда раздражает. Иной раз недовольный пользователь может уйти с сайта, не дождавшись окончания загрузки. Особенно обидно, когда на самом деле страничка маленькая, а большую часть времени грузится JavaScript. Одна из возможностей Knockout MVC заключается в том, что можно в одном месте прописать небольшое слово
Lazy
— и о чудо — данные будут подгружаться дополнительным ajax-запросом после загрузки страницы. Понятно, что не так сложно и вручную написать соответствующий JavaScript-код, но всё равно приятно, когда такие решения достаются из коробки. Можно посмотреть как всё это выглядит на живом примере. - Дописывание пользовательского JavaScript. А вот ещё одна печальная ситуация: вы начали использовать очередной framework, вам всё нравится, но вот одной маленькой функциональности вам не хватает. И без использования framework-а вы бы эту функциональность легко дописали бы, но в контексте его использования это либо сделать невозможно, либо ну очень сложно. С Knockout MVC вам не нужно бояться таких ситуаций, ведь любую привязку данных, любую дополнительную функцию можно дописывать вручную к уже созданной автоматически сгенерированной модели — ваша свобода ничем не ограничена! И опять-таки есть живой пример
- Совместимость с другими библиотеками. Если вы используете Knockout.js, то у вас уже по умолчанию есть совместимость с любой другой JavaScript-библиотекой (jQuery, Prototype и т.д.) или её плагином. Ну, например, вы можете посмотреть работу с jQuery.Validation
Резюме
Итак, подведём итог. Основные особенности Knockout MVC:
- Написан на базе популярной js-библиоткеке Knockout.js
- Полная интеграция с ASP.NET MVC3
- Создание приложений с использованием паттерна Model-View-View Model (MVVM)
- Все привязки данных (к конкретным свойствам и к выражениям) пишутся на C#/VB
- Модель нужно описать только один раз на сервере
- Для написания всех привязок данных работает IntelliSense
- Можно вообще не писать JavaScript-код
- А размер подключаемого js-кода очень мал (сжатый Knockout занимает порядка 40Kb)
- Активное использование Ajax-все серверные методы отрабатывают без перезагрузки страниц
- Краткий лаконичный синтаксис, который легко пишется и легко читается
- Разработка очень проста и напоминает создание обычного приложения — можно вообще забыть о клиент-серверном взаимодействии
- Зависимостей почти нету: только Knockout.js (само-собой) и jQuery (а он скорее всего и так у вас подключен)
- Кроссбраузерность: IE 6+, Firefox 2+, Opera 10+, Chrome, Safari
- Полная совместимость с другими библиотеками (jQuery, Prototype и всё, что вам захочется)
- Детальная документация, подробное описание API, много живых примеров
Ссылки
- Knockout MVC — официальный сайт
- Введение в Knockout MVC
- Документация для Knockout MVC
- Скачать Knockout MVC
Автор: DreamWalker