
For those who do not know,
Knockout.js is a popular JavaScript library that allows you to implement the
Model-View-View Model (MVVM) pattern on the client. On Habré already wrote a lot about him (
one ,
two ,
three ,
four ,
five ,
video ). You can master Knockout.js very quickly - after all, there is
an interactive learning system , a lot of
living examples (you can poke and look at the source code) and excellent
documentation .
Very often, Knockout.js is used in conjunction with
ASP.NET MVC - because the library greatly simplifies writing client logic. However, there are many typical problems for client-server development: the basic model and part of its processing logic have to be described both on the client (JavaScript) and on the server (C # / VB). In addition, there is a routine part related to client accessing server methods and transferring models to them for processing. But do not be sad! Now we have
Knockout MVC - this is the .NET wrapper for Knockout.js, which generates all the necessary JavaScript code for us. It remains only to describe our model in C # and specify in MVVM-style for each desired html element to which property of the model you need to bind (or you can specify whole expressions - they will be translated into js). Thus, you can get a full-fledged cross-browser client web application without a single line of JavaScript!
And now a little more
Model
The model is written once on the server (say, in C #). On the client side, all JavaScript code is generated automatically. Moreover, we can describe not only ordinary properties, but also not very complex expressions that will be translated into JavaScript. And all this is done not only for the main model, but also for its submodels (on pure Knockout.js, this is not so easy to do). Well, for example, we describe this model:
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; } }
and the following JavaScript is automatically generated by it:
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);
In this example, the resulting JavaScript is relatively simple, but with a slightly more complex model, writing it with pens is not so pleasant. And if you want to refactor it, then you will need to synchronously and very carefully change the JavaScript model and the C # model (in a normal application, the C # model will still be needed to describe the logic of actions on this model itself).
Data bindings
If you are working with ASP.NET MVC, then you probably use the
Razor View Engine to describe the views. If not, then it is desirable to at least familiarize yourself with the functionality of this engine - it takes the layout of the site to a new level. Further examples of representations will go just under Razor.
Well, for example, I want to create a TextBox that will contain the value of some property
A of our model. It is enough for me to write:
@ko.Html.TextBox(m => mA)
Brackets indicate a lambda expression that our model
m matches a property to which I would like to bind data. And in this case we are writing an expression in C #, which means that IntelliSense helps us:

And if I accidentally write the name incorrectly, then I find out about it at the compilation stage - a red strip in the studio will show that somewhere I was wrong.
Well, now let's see an example more complicated. For example, I want part of a page to be displayed if and only if in my model two conditions are met simultaneously. All I need to write is:
@using (ko.If(model => model.Condition1 && model.Condition2))
Next, the curly brackets indicate the corresponding part of the view. Simple, isn't it? After all, having written one such line, you can immediately forget about it - we do not need to track the state of Condition1 and Condition2, write js-code that will control the visibility of elements, etc. And if we go to rename one of the conditions or turn it into a computed property, then the refactoring magic will correct our expression itself (and, accordingly, the generated JavaScript will correct).
Knockout MVC contains many useful designs. For example, it is very easy to sort through the elements of a collection and, for each element, output something important:
@using (var items = ko.Forearch(m => m.Items))
A complete description of all available syntax can be found in the
documentation .
')
Accessing the server
Well, now let's try to create a button that calls some server method that performs operations on the model. We need to think about the following points in our JavaScript code:
- It is necessary to collect data on the current model and serialize it into a line.
- It is necessary to bind to our button ajax-sending this line to the server
- It is necessary to somehow describe the logic of receiving data from the server and updating the visual elements.
Stop! Nothing needs to be done! You just need to write this line:
@ko.Html.Button("Some text", "FooAction", "Foo")
A thoughtful reader will ask: “And if I want to pass some parameters to the server method?” There is nothing simpler:
@ko.Html.Button("Some text", "FooAction", "Foo", new { index = 0, caption = "Knockout MVC"})
And this is how we describe the corresponding method on the server:
public class FooController : KnockoutController { public ActionResult FooAction(FooModel model, int index, string caption) { model.FooAction(index, caption);
It's all! It is not necessary to write a single line! You can even forget about the fact that we have a client-server application with an accompanying technical routine - when developing we need to concentrate only on the logic of the model! See more about how it all works
here.Work with contexts
What is done in Knockout.js straight is not at all very convenient - it is
work with contexts . Nested contexts occur when you use nested
foreach
and
with
constructs. If you have one level of nesting, then everything is fine. But if you write a complex application, in which there are 4-5 levels of nesting, then you can sometimes come up with constructions like
$parents[2].name
or
$parentContext.$parentContext.$parentContext.$index
. It is quite difficult to perceive such code, and when it comes to refactoring, it becomes completely sad. It's much easier to write in Knockout MVC - because now every context has its own name! And now the code looks like this:
@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() + " " + mB) } }
The nested expression itself will turn into:
<span data-bind="text : $parents[1].A()+' '+$parentContext.$index()+' '+B()"></span>
You can see
an example .
Other tasty buns
Among other things, the library has some additional features that can sometimes make life easier:
- Lazy loading data. When a page is loaded for a long time - it is always annoying. Sometimes a dissatisfied user can leave the site without waiting for the download to complete. It is especially annoying when the page is actually small, and most of the time JavaScript is loaded. One of the features of Knockout MVC is that you can write a small word
Lazy
in one place - and a miracle - the data will be loaded with an additional ajax-request after the page loads. It is clear that it is not so difficult to manually write the corresponding JavaScript code, but it’s still nice when such solutions are taken out of the box. You can see how it all looks on a live example . - Add custom javascript . And here is another sad situation: you started using the regular framework, you like everything, but you lack one little functionality. And without using the framework, you would easily add this functionality, but in the context of its use, this is either impossible to do, or very difficult. With Knockout MVC you do not need to be afraid of such situations, because any data binding, any additional function can be added manually to an already created automatically generated model - your freedom is unlimited! And again there is a living example.
- Compatible with other libraries . If you use Knockout.js, then you already have compatibility with any other JavaScript library (jQuery, Prototype, etc.) or its plugin by default. Well, for example, you can see the work with jQuery. Validation
Summary
So let's summarize. Key features of Knockout MVC:
- Written on the basis of the popular js library Knockout.js
- Full integration with ASP.NET MVC3
- Creating applications using the Model-View-View Model (MVVM) pattern
- All data bindings (to specific properties and to expressions) are written in C # / VB
- The model needs to be described only once on the server.
- IntelliSense works for writing all data bindings.
- You can never write JavaScript code
- And the size of the connected js-code is very small (compressed Knockout takes about 40Kb)
- Active use of Ajax-all server methods work without reloading pages
- Concise, concise syntax that is easy to write and easy to read.
- Development is very simple and resembles the creation of a regular application - you can forget about client-server interaction altogether.
- There are almost no dependencies: only Knockout.js (by itself) and jQuery (and it is most likely already connected)
- Cross-browser compatibility: IE 6+, Firefox 2+, Opera 10+, Chrome, Safari
- Full compatibility with other libraries (jQuery, Prototype and whatever you want)
- Detailed documentation, detailed API description, many live examples.
Links