Not so much is written about the
knockout.js library, but there is
some (and of course there is an
official tutorial and other materials on the
official website and a good resource in English
knockmeout.net , which articles I can translate if there is demand) . This article may turn into a cycle of articles on javascript and knockout if ufo does not kidnap me.
Initially I was preparing material for people already familiar with knockout and mvvm, but in the comments to other articles I was asked to tell how to prepare a knockout for dummies. I assume that you have already read previous articles on knockout on Habré. Go!
Why do I need Knockout.js?
- To be able to easily fill the interface with data from a json-like object (model):
var ViewModel = { attribute1: ”Hello”, attribute2: ”world!”, };
Layout:
<div> <span data-bind=”text: attribute1”></span> <span data-bind=”text: attribute2”></span> </div>
Binding:
ko.applyBindings( ViewModel );
Result:
<div> <span data-bind=”text: attribute1”>Hello</span> <span data-bind=”text: attribute2”>world!</span> </div>
But such a binding is often not used, because when the model changes, the DOM does not change and vice versa.
- Create a two-way observable interface and model binding, i.e. the interface will be updated in real time when the model changes, and the model when the interface changes (a working example when entering text in forms). There is one caveat, the update of the model in the input fields will occur only on the onblur event (remove the focus from the element), this situation can be corrected by subscribing to the
input
event, and the model must be updated manually. An example on jsfiddle . User m_z suggested a more convenient version of updating the model using valueUpdate: 'input'
( Example ). - Additionally subscribe to model changes. Code from the documentation:
myViewModel.personName.subscribe(function(newValue) { alert(" : " + newValue); });
Unsubscribe from changes:
var subscription = myViewModel.personName.subscribe(function(newValue) { });
What is the data-bind and bindings attribute?
This attribute is very similar to the json object, it allows us to bind data and event handlers to the current DOM node. Knockout.js parses this attribute and executes JavaScript expressions (described in detail in the
docks ). This construct runs in the context of the transferred data model. Accordingly, the syntax of the attribute must be correct in terms of javascript code.
Knockout.js offers a set of standard bindings that allow you to edit styles, content, handlers, etc., creating magical things. In addition, you can write your bindings, about this is perfectly told again in the
docks .
')
This concludes the description of why you need a knockout, everything else you yourself know on which site to find and move on to some not obvious newcomer things.
Work with observableArray
Suppose you need to dynamically change an array to which some handlers are subscribed (or an interface). For example, you received a new batch of data from the server and in a loop add it to the array.
If you do everything in the forehead, then the change handler will work as many times as you add elements. And it will affect performance. An example is taken from
knockmeout.netSo do not:
var items = ko.observableArray([]); for (var i = 0, j = newData.length; i < j; i++) { items.push( newData[i] ); }
Much better like this:
var items = ko.observableArray([]);
Full
jsfiddle example.
In the comments on
stackoverflow, they even recommend this function:
ko.observableArray.fn.pushAll = function(valuesToPush) { var underlyingArray = this(); this.valueWillMutate(); ko.utils.arrayPushAll(underlyingArray, valuesToPush); this.valueHasMutated(); return this; };
Templates
Knockout.js is needed in the project primarily for filling the interface with data. The most common scenario for this is getting an AJAX request for JSON data from the server and rendering, i.e. update content without reloading the page.
To do this, we need fragments of the page, which we can replace in the interface and fill with data - that is, we need templates for different data. Knockout.js can work with various types of templates (jQuery.tmpl, underscore, native) - let's stop on the latter (although we can even write our own).
Non-native template engines do not support the use of some bindings inside it (in particular, very pleasant
with , which replaces the context of the model for the template).
Therefore, in some cases, I want to use the native template from knockout. To do this, in the options you need to pass the
templateEngine: ko.nativeTemplateEngine.instance
. What is interesting (and not stated in the documentation) is that this parameter can be passed in the template binding.
For software rendering templates use the command:
ko.renderTemplate(templateName, viewModel, options, domNodeToRender);
It is convenient to call this command, for example, in the _create () method when creating jquery widgets that use templates, and this is done like this:
ko.renderTemplate('templateName', this, { templateEngine: ko.nativeTemplateEngine.instance }, this.element.get(0) );
To call a template from another template, use the standard
template binding:
<ul data-bind="template: { name: templateName, data: viewModel, templateEngine: ko.nativeTemplateEngine.instance }"></ul>
templateName
is the name of the template, in practice it is the
id
block containing the layout with the template:
<script type="text/html" id="templateName"> <h3 data-bind="text: name"></h3> <p>Credits: <span data-bind="text: credits"></span></p> </script>
In this case, the template engine is not “inherited” when invoking nested templates — if we call a template from a template processed by the native template engine, then the internal template (if you do not specify the template engine forcibly) still uses the default template engine (for example, jQuery.tmpl, if one was connected in project).
Therefore, if other template engines are used in the project, and you want to use the native, the engine must be specified each time. But this feature allows us to simultaneously use different template engines for nested templates.
Templates without root element
Sometimes you do not want to create an extra container for the contents of the template, so that it does not work out that only one element is embedded into it. In the template itself, the element may be necessary due to bindings or styles.
In this case, the standard option for knockout is suitable (virtual elements):
Although the documentation he is not described.
Debugging in templates
The most common operation in programming is debugging (although some may argue). The method below will allow you to display messages in the console using a comma operator and a grouping operator (
more about these operators ).
<div data-bind="html: ( console.log( details ), details )"></div>
Context change with with and foreach - pitfall
Suppose we have an array in which the strings that we want to track are stored.
The array initialization looks like this:
var obArray = ko.observableArray([ ko.observable(“Task One”), ko.observable(“Task Two”), ko.observable(“Task Three”) ]);
And we want to display a form consisting of input for editing it like this:
<div data-bind=”foreach: obArray”> <input type=”text” data-bind=”value: $data”/> </div>
In such a record, text fields will not work in two directions - changes to the text in the field will not affect the elements of the array, but changing the elements of the array will affect the contents of the text fields. An example on
jsfiddle .
The reason is that the template above is essentially equivalent to the following:
<div data-bind=”foreach: obArray”> <input type=”text” data-bind=”value: $data”/> </div>
with: $data
- here is the “unpacking” of
$data: unwrapObservable( $data )
in order for the calls like
$data.somefield
inside the template. But this unpacking substitutes
$data
instead of
$data
- its value. How to get around this problem - I did not invent it without thinking. But the user
lega suggested using the option with creating a wrapper object (
example ), if you have several fields, then this method is justified and will work.
Work with text editors
We have already learned how to get the value from input using the event of the same name in real time, but what about the editors where the content is html elements? We use
MutationObserver and This api does not work everywhere, we support ourselves with other DOM events and write not quite cross-browser (modern browsers work fine) code that you can embed in your favorite editor:
This is the function I use to update the model in one project, which uses a good
editor from imperavi , if anyone needs this code as a plug-in, I can write and put it on a github.
Conclusion
The article turned out to be of normal size, but the material still remained - working with trees, writing your own bindings, jQuery knockout widgets and some other tricks and examples. Vote in charge, comment, point out errors.
For errors in spelling and punctuation, I ask in a personal.