📜 ⬆️ ⬇️

Knockout, hands-on experience

Some time ago, I promised to talk about our experience with Knockout. We use this library in one of the projects during the last 4 months. This is not much, but during this time the team gained some experience, which, I think, may be of interest to readers.

But for a start quite a bit of theory.

MVC, MVP and MVVM.



If most of the first two templates are more or less familiar, very few have heard about the latter: the MVVM template appeared in Microsoft and became popular among .net developers, but it is not so widely known among them.
')
What is all the fuss? MVC got accustomed to the web in many respects because it fits perfectly on the request-response script. A request arrived, it was transmitted to the appropriate controller, it initiated some processes in the model, and then a View is created. This is usually a template that is filled with data from the model. Then the resulting markup is passed to the client in the browser.



The main feature of this scenario is the short lifetime of the View - it is created for each request, it is simply filled with data in one pass - that's all.

When Ajax appeared, and after it single-page applications, it turned out that the Presentation lives for a long time, and therefore a simple template is not enough. Increased lifetime means complication of what is called the lifecycle, and therefore the appearance of logic on the View.

Naturally, no one wants it, everyone wants it - so that the application logic is provided in the code, and not in the markup. It is easier to maintain, easier to test and change. What do they do in such cases?

Idea 1: Enter MVP. The presentation logic is carried out in a special layer and merged with the controller into the Presenter. The task of the presenter is to separate the model from the presentation, be responsible for transferring data between them, and contain the logic to change the presentation.



Presenter - mediator. He could be stateful or stateless. However, in reality there is always a state in it - it is a state of the species. An alternative would be to keep it in the model, but, of course, no one wants that either. Therefore, we assume that there is a state in Presenter.

If we look at MVP in relation to the web, then our Presenter could be an object in JavaScript with state fields. How does it change through HTML and user interaction? - Through DOM events. Those. our Presenter is an object with fields and event listeners for communicating with View.

Idea 2: You can also use the event to connect the presenter with the model. Those. whenever a change occurs in the fields of the presenter object, the corresponding event is launched. Event handlers invoke business methods in the model, and the result is also reflected as changes in the values ​​of the other fields of the presenter. Events are called again, but this time it could be view handlers, for example. Then changes in the model are reflected in the user interface. Here is such an event-driven workflow.

Idea 3: Now if the message processing infrastructure is removed from the presenter, hidden in a separate framework, the object itself is transformed into a simple object with fields. Changing the values ​​of the fields, we initiate some processes in the model or in the presentation. Such a simple object is called ViewModel - it simulates the user interface from the point of view of our business logic, without going into details.



The .net pattern got accustomed largely due to the fact that for the UI component layout they began to use XAML, a markup in which the binding of elements to the ViewModel fields can be described declaratively. On the other hand, events are supported at the framework level, so implementing a ViewModel and Model can also be very convenient.

In JavaScript, we don’t have this kind of event support, so two things are needed to implement an MVVM template:
  1. The mechanism for binding the properties of DOM elements to the fields of the ViewModel.
  2. PubSub-infrastructure to support events related to changes in the values ​​of ViewModel fields.


Knockout js



Knockout JS is just such a framework that implements these two mechanisms. For communication View-ViewModel used HTML5 data-attributes.

Example:

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

* This source code was highlighted with Source Code Highlighter .


currentCustomer - field in the ViewModel object. Actually, this is not quite a field:

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

* This source code was highlighted with Source Code Highlighter .


As you can see, the value of the field is wrapped in ko.observable. Through this wrapper, Knockout monitors how the field value changes. Those. corresponding event handlers are called there. The disadvantage, of course, is that you have to work everywhere by calling the method, writing brackets.

viewModel.currentCustomer = joeCoder;

* This source code was highlighted with Source Code Highlighter .


turns into

viewModel.currentCustomer(joeCoder);

* This source code was highlighted with Source Code Highlighter .


You can get used to it, but sometimes the brackets in the getter are forgotten.

To subscribe, you need to write the following:

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


* This source code was highlighted with Source Code Highlighter .


So you can bind the ViewModel to our business methods. Inside the framework, for all data-bind attributes, the same handlers are actually created. The entire binding is initiated by the call.

ko.applyBindings(viewModel)

* This source code was highlighted with Source Code Highlighter .


So on the surface everything is quite accessible - no magic.

Of course, if the matter was limited only to this, then it would be quite nice. But Uncle Sanderson went on.

First, he made a wrapper for arrays: ko.observableArray. True, he knows a little: pop, push, reverse, shift, sort, splice, unshift, slice, remove, removeAll, indexOf. Quite enough for work, but some underscore frills are not enough. Understandably, you can read the value — the array itself — to perform operations with it, and then write it back in its entirety. But it turns ugly (why, I will explain further). Therefore, it is better to hold on to what is already there.

The second important addition is ko.dependentObservable (). Those. You can define a property in the ViewModel that will depend on other properties. At the same time, knockout leads automatic tracking of dependencies ! This is one of the most important features of the library - I strongly recommend using it. Again, there is no magic inside. When calculating, ko keeps track of which observabl are addressed and adds them to the tracker. If the value of one of these properties has changed, recalculation for the dependent property is called. It is clear that dependent properties can also act as the properties of the fundamentals.

What can I do with dependentObservable? Its main purpose is to simplify the logic. It often happens that a complex condition appears in the data-bind. It looks very uncomfortable - JavaScript in my HTML !! 1. Debug of this condition also turns out to be inconvenient - the debugger will not enter the attribute body. Therefore, it is easier to make a condition or its component in the form of dependentObservable and then work with it.

By the way, to the issue of debugging. As I said, the debugger does not enter the attribute body. That's because in applyBindings, the framework runs through all tags and creates its own dependentObservables for all buyindings. He does it, naturally, through eval. Not very good, but it is a choice between readability and cleanliness. By the way, for those who believe that seeing pieces of JavaScript code in HTML attributes is a return to inline Internet dawn handlers like onclick, I hurry to inform you that no one forces you to do this. You can hang them through a DOM-api, as, for example, shown here: Unobtrusive Knockout support library for jQuery

What else? There is support for jQuery-Tmpl templates. At one time it was expected that this plugin will become part of jQuery, but the developers' plans have changed. On the other hand, Microsoft is also not actively developing it anymore - the only developer has moved on to other projects. So the project is a little stillborn - my prediction: it will be in beta1 state for about a year. Then either thrown out, or brought to mind.

But even for beta1, it seemed to me sufficient, especially in conjunction with Knockout. In fact, in our project we do not use template logic - all these expressions are in curly braces. We iterate through Knockout foreach, substitute values ​​into the body and tag attributes, too, through ko. First, starting to apply data-bind-attributes, we would like to apply them everywhere, and secondly, when rendering via foreach in combination with operations from ko.observableArray, not the entire collection is redrawn, but only the changed elements. That is why I believe that, if possible, it is worth confining to only those operations.

Still you should not forget that in the template parameters you can specify the afterRender callback. With it, you can finish building in the template that it is inconvenient to build by the forces of the windings, well, or to perform some additional actions. We in the afterRender initiate widgets, if any, should be in the template. Two parameters are passed to the callback. The first is the renderedNodesArray, a fragment of the DOM that rendered the template. The second is data - the data passed to the template.

Why did I like this framework?


Largely due to declarativeness. I noticed a long time ago that the more my code of conditions, cycles and nesting levels, the harder it is to understand, maintain and debug this code. In addition, if earlier I liked all this, now with experience the actions themselves seem boring to me - the tasks and their solutions are much more interesting, and the higher the level of description of the solution, the easier it is to understand and the less chance there is to make a mistake in it.

UI code is very difficult to debug. On the one hand, it does not seem particularly complicated compared to the same business logic. On the other hand, it is difficult to approach him, it is difficult to tear him out of context, it is difficult to place him in a unit test, for example. Knockout allows me to describe UI not with code, but with rules. This is a huge plus.

The fact that the rules are described in the markup is also a plus - in my opinion. Here is the section responsible for the toolbar, and here are the rules for its behavior. Very comfortably! Special bold plus for the fact that markup remains understandable for design tools - no custom tags, no XML neimspaces. Any editor will open your HTML without problems.

Knockout vs. Backbone


An alternative to Knockout today for many is Backbone. There is a lot of good in it. Collections work with underscore - this is a big plus when writing conversion logic, filtering, etc. There is support for data synchronization with the server, there is history management. But when working with UI, I have to prescribe all the connections of the model with the DOM with my hands. It is not difficult, but very tiring. The code turns out a little, especially if you compare the unobstrusive Knockout binding option. But the fact that this monotonous code has to be written by hand is very upsetting.

On the other hand, in Knockout there is nothing on the model side outside the event in ViewModel. There is something backbone and strong. In fact, your choice in favor of a particular library should be based on how large the UI or Model component in your code is. Imagine that you are writing a project from scratch without using any of these libraries. If you foresee that most of the problems will arise on the DOM side, then probably it is better to take a Knockout so that it will make your life easier. If the main difficulty arises on the integration side, then Backbone will probably suit you.

I personally see the main advantage of Backbone in front of Knockout in Sync - the ability to synchronize data with the server. However, Sync works well only when the server API obeys REST rules. Many people think supposedly great, now I will make it work. However, they overlook the fact that REST and JSON-over-HTTP are not the same thing! If with the first one Backbone works out of the box - you just indicate the URL of the resource, then with the second one you need to file it. And then you ask yourself: is it really better than Sync than a simple $ .ajax call?

In our project we had to deal with JSON-over-HTTP. In addition, the number of types of messages that we send and receive from the server is small, while only four. Only one of them is data synchronization. Everything else is a one-time user action. Even though you have to write these calls yourself, it’s not too difficult for my team.

By the way about the team. Of the four people, I alone have experience with JavaScript in general. To get started, I conducted a language and library course with them. It turned out that for all it is the DOM api that presents the greatest complexity, even in the guise of jQuery. This is partly due to CSS and the fact that they simply do not know which attribute is responsible for what. Therefore, they really liked that all work with it could be localized around a single object - ViewModel. Working on different nodes, they practically do not step on each other’s feet.

If you are interested in comparing two libraries, there is a series of posts . It describes the application for a simple script. But scenarios are different :)

Practice


Let me tell you better about those rakes that have to be stepped on, and those moments that cause difficulty.

1. Difference of ViewModel from Model.

Not everyone feels the difference. Simple rule:

Why am I doing this? And the fact that many are beginning to look at the ViewModel, as a model. Here they have a number of questions. For example: in the Presenter the list of goods, the selected goods and the index of the selected goods can be indicated. Immediately there will be someone who will say that it is not necessary to store both the selected product and the index - they say, the data are duplicated. However, unlike the model, it does not matter to us whether there is any kind of redundancy in the ViewModel. If it allows us to significantly simplify the application code, then why not?

Many also ask if you can use multiple ViewModel. It is possible, but is it necessary? We made such an attempt, but our mistake was that we shared responsibilities between ViewModels in accordance with their content - that is, we worked with them as with model entities. Whereas ViewModels represents page sections and each of them works only with its own section. Then, when you need to display data from one ViewModel on another part of the page, nothing will come out directly through the data-bind.

So my advice to you: use one ViewModel for the entire page - everything will just work. There is a risk that the object will become large, but nobody prevents you from writing several times in the code.

_.extend(ViewModel, {
// <br>
})


* This source code was highlighted with Source Code Highlighter .


Such blocks can be divided into functional modules.

2. Despite the fact that the examples for the Knockout Model are completely absent, and the supporters themselves declare to you with a smile that she is on the server , do not believe them.

There is a model on the client too, and it will have to be written with pens - unlike Backbone, Knockout will not help you with this. On the other hand, it is not so scary, and your common sense and general rules of hygiene will help you. Remember two things: events and modularity. If ajax code becomes a problem for you, look towards AmplifyJS .

3. Unfortunately, even in those rare cases when Knockout wants to help us in model making, it doesn’t work well.

The ko.toJSON method does not know how to work with dates , and writes null in them. There are ways around this, but they are all a bit clumsy. The easiest is to add another dependentObservable property with the value JSON.stringify (date ()) - there it will just be a string that can be sent to the server. But ugly! - two fields for each date, and marshalling works only in one direction. Therefore, we went the other way: we just wrote our own crawler for the ViewModel, who carefully handles dates. The blessing ko offers the unwrapObservable method which returns a simple object. It works, however, only at the top level, so then you have to bypass all the properties of the received object in case there are still observables somewhere

4. Despite templating, sometimes you have to create DOM using other methods.

This is especially true for widgets and third-party components. In such cases, ko can also interfere: for example, when a component requires a simple array, and in our case it is observableArray, i.e. in the component code:

items.pop()

* This source code was highlighted with Source Code Highlighter .


and we would like it to be there

items().pop()

* This source code was highlighted with Source Code Highlighter .


It all depends on the situation. Sometimes we patch components for internal use - especially if they are small or if some changes have already been made to them. But in most cases we pass unwrapped array there, and we have to work with change subscriptions, most often, write two processors — to change the data in the component and to change the data in the ViewModel.

5. Knockout works very well under traditional business scenarios.

Forms, data output, tables - if this is exactly what your application does, then it will suit you 100%.

If you have something exotic, for example, working with SVG and Canvas, many DOM elements that are dynamically created and modified without your control, as in our project, then you should have doubts. We chose ko, because in our project, both from it and from Backbone, there is not much sense in general - two thirds of the code is completely unrelated to the tasks that these libraries perform. But even in the remaining third of Knockout, it seemed to us more useful.

Knockout and backbone


Many of you in the process of reading the article might have an idea to combine Backbone and Knockout. Unfortunately, this is easier said than done. Framework functionality overlaps in many ways; it is difficult to determine where to draw a line between them. We tried to do this between the Knockout ViewModel and the Backbone Model, but the code became too cumbersome as a result: we had to write a data synchronizer, which in size combined with the Backbone model became more than what we wrote to connect the ViewModel to the server. Debugging also became more complicated, and Sync was not so convenient to use for our services as we had hoped.

So for now you have to choose between one or the other. In the future, there will probably be a backbone banding and an analogue of the Sync in Knockout. If you feel like a pioneer, are an ardent supporter of Backbone, and you want to see some kind of binging in it, I will give you one more piece of advice. Take a closer look at jQuery-Link . This is another plugin from Microsoft, which they also sluggishly modify, like jQuery-Tmpl. But nevertheless, it can become the basis for the realization of binding.

Microsoft itself seems to have been waiting. Steve Sunderson - the author of Knockout - works for them, and the video from his presentation on MIX11 has become the most popular, overtaking all keyouts and all IE10 presentations, performance optimization and future HTML5, EcmaScript and any other technologies of the company. Now Steve with the .net star Scott Guthrie travels around the world and shows everyone his creation. And this week he published new educational materials. If the framework takes off, the company will support it in the same way as jQuery.

Backbone is also not going anywhere, there is a big interest in it among rubists, NodeJS programmers and the entire avant-garde of the JavaScript community. So choosing any of them you can be sure - you will not lose!

Source: https://habr.com/ru/post/124731/


All Articles