- PVSM.RU - https://www.pvsm.ru -
На Хабре уже много писали про Knockout.js (раз [1], два [2], три [3], четыре [4], пять [5], видео [6]). Для тех кто не в курсе, Knockout.js [7] — это популярная JavaScript библиотека, позволяющая реализовать Model-View-View Model (MVVM) [8] паттерн на клиенте. Освоить Knockout.js можно очень быстро — ведь есть система интерактивного обучения [9], куча живых примеров [10] (можно потыкать и посмотреть исходный код) и прекрасная документация [11].
Очень часто Knockout.js используют в связке с ASP.NET MVC [12] — ведь библиотека существенно упрощает написание клиентской логики. Однако, возникает много типичных проблем для клиент-серверной разработки: основную модель и часть логики её обработки приходится описывать как на клиенте (JavaScript), так и на сервере (C#/VB). Кроме того, есть рутинная часть, связанная с обращением клиента к серверным методам и передачи им модели для обработки. Но не стоит печалиться! Теперь у нас есть Knockout MVC [13] — это .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 [14]. Если нет, то желательно хотя бы ознакомиться с функциональностью данного движка — он поднимает вёрстку сайта на новый уровень. Далее пойдут примеры представлений именно под 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))
Полное описание всех доступных синтаксических конструкций можно посмотреть в документации [15].
Ну, а теперь попробуем создать кнопку, которая вызывает некоторый серверный метод, выполняющий операции над моделью. Нам нужно задуматься о следующих моментах в нашем JavaScript-коде:
Стоп! Ничего этого не нужно делать! Нужно просто написать вот такую строчку:
@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);
}
}
Это всё! Ни одной лишней строчки писать не нужно! Можно вообще забыть о том, что у нас клиент-серверное приложение с сопутствующей технической рутиной — при разработке нужно концентрироваться только на логике модели! Посмотреть подробнее, как всё это работает, можно тут [16]
Что в Knockout.js сделано прям совсем не очень удобно — это работа с контекстами [17]. Вложенные контексты возникают тогда, когда вы используете вложенные 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>
Можно посмотреть пример [18].
Кроме всего прочего, в библиотеке имеются некоторые дополнительные функции, которые иногда могут существенно облегчить жизнь:
Lazy — и о чудо — данные будут подгружаться дополнительным ajax-запросом после загрузки страницы. Понятно, что не так сложно и вручную написать соответствующий JavaScript-код, но всё равно приятно, когда такие решения достаются из коробки. Можно посмотреть как всё это выглядит на живом примере [19].Итак, подведём итог. Основные особенности Knockout MVC:
Автор: DreamWalker
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/9932
Ссылки в тексте:
[1] раз: http://habrahabr.ru/post/124731/
[2] два: http://habrahabr.ru/post/123692/
[3] три: http://habrahabr.ru/post/136782/
[4] четыре: http://habrahabr.ru/post/124931/
[5] пять: http://habrahabr.ru/post/125148/
[6] видео: http://habrahabr.ru/post/125524/
[7] Knockout.js: http://knockoutjs.com/
[8] Model-View-View Model (MVVM): http://ru.wikipedia.org/wiki/Model-View-ViewModel
[9] система интерактивного обучения: http://learn.knockoutjs.com/
[10] живых примеров: http://knockoutjs.com/examples/
[11] документация: http://knockoutjs.com/documentation/introduction.html
[12] ASP.NET MVC: http://www.asp.net/mvc
[13] Knockout MVC: http://knockoutmvc.com
[14] Razor View Engine: http://weblogs.asp.net/scottgu/archive/2010/07/02/introducing-razor.aspx
[15] документации: http://knockoutmvc.com/Home/Documentation
[16] тут: http://knockoutmvc.com/ParametersToServer
[17] работа с контекстами: http://knockoutjs.com/documentation/binding-context.html
[18] пример: http://knockoutmvc.com/CombineContext
[19] живом примере: http://knockoutmvc.com/BigData
[20] живой пример: http://knockoutmvc.com/UserScript
[21] можете посмотреть: http://knockoutmvc.com/GiftList
[22] jQuery.Validation: http://docs.jquery.com/Plugins/Validation
[23] Knockout MVC — официальный сайт: http://knockoutmvc.com/
[24] Введение в Knockout MVC: http://knockoutmvc.com/Home/Introduction
[25] Скачать Knockout MVC: http://knockoutmvc.com/Home/Downloads
Нажмите здесь для печати.