📜 ⬆️ ⬇️

(Archive) Matreshka.js - Introduction

The article is outdated. The new documentation contains the most current information from this post. See bindNode and on .

Greetings to all readers and writers of Habr.

I want to introduce you to a compact framework that allows you to somewhat change the view on the structuring of applications. In the "Matryoshka" there is a little bit of magic, which will be revealed by a series of articles entitled as follows:

')
Code to attract attention:
mk.on( 'change:x', function() { alert( 'x is changed to ' + this.x ); }); mk.x = 2; // alerts "x is changed to 2" 

And it works in ... IE8.

What is a matryoshka?

Matryoshka as a framework
Compact size and easy-to-learn architecture makes it possible to build large extensible applications. This will surprise no one today, but I will try.
Matryoshka as library
If the features provided by Matryoshka you like, then it is not necessary to change your code. Nested doll can be used as a set of classes with interesting methods.
Matryoshka as a platform for creating your own framework
Matryoshka is an extensible general purpose framework that is not positioned as MVC, MVVM or% your_design_pattern% framework, so the programmer has the opportunity to implement his own architecture, which will know how the desired set of design patterns.

What for?

I am sick of thinking about the idea and about changing it with the code I wrote. All the crutches of data synchronization and presentation in Javascript cause me negative feelings, and the ambitious future-oriented Matryoshka goal is to completely forget about the fact that we have a UI, using only data. Of course, this task cannot be solved 100%, but we, programmers, must squeeze the maximum out of the tools given to us in order to make the code cleaner, shorter and more flexible. It's time to shake up your code and get angry.
Gifka to attract attention


Data binding


It is known that client applications in the browser require synchronization between the data and the view. The classic task: there is data:
 var o = { x: 2 }; 

There is a select that should change this data:
 <select> <option>1</option> <option>2</option> <option>3</option> <select> 

If you use pure JS + jQuery code, we will write something like the following:
 $( 'select' ).on( 'change', function() { ox = +this.value; }); 

Then, when we want to change the data, we write:
 ox = 1; $( 'select' ).val( ox ); 

This is not very good, because it forces you to set the value for two "atoms" at once: the properties and the element.

This problem is solved in different ways: Backbone (MVC) divides the application into a model (Model, Collection), a view (HTML code) and a controller (View). Knockout and Angular (MVVM) bind data using the practice migrated from .NET to the client web: the part of logic that replaces the controller (in this case, the ViewModel) is written in HTML code (I apologize if the description is incorrect). There are a bunch of frameworks that you’re not hijacking.

Matryoshka binds the data to the presentation in such a way that, firstly, the programmer does not touch the HTML code (unlike MVVM), secondly, the programmer stops thinking about the events of the HTML element to change the model (unlike Backbone).
 var mk = new Matreshka; //  new MK; ,  MK === Matreshka,    mk.bindNode( 'x', 'select', { on: 'change', getValue: function() { return this.value; }, setValue: function( v ) { this.value = v; } }); 

If we want to change the data, then simply write:
 mk.x = 2; 

Such a code not only assigns a deuce to X, but also changes the state of the select, setting it to .value = 2 .
And this is without using methods like .set .
jsbin.com/jikewipi/2/edit

What happened here?
 mk.bindNode( 'x', 'select' ... ) 

The bindNode method, oddly enough, is responsible for binding an element to a property. The first argument is the key of the object, the second is the selector, in this case 'select' . The type of the second argument is any value that is accepted by jQuery: selector, pure element, jQuery object, NodeList, array of elements ... That is, 'select' can be replaced by $( 'select' ) or document.getElementsByTagName( 'select' ) or document.querySelector( 'select' ) .

The third argument is the most interesting one, let's sort it in order.
 on: 'change' 
Answers the question: “What event should happen on the element, so that we take the value from the element and assign it to the corresponding property?”

 getValue: function() { return this.value; } 
Answers the question: "How to extract the value of an element?", Returning the value.

 setValue: function( v ) { this.value = v; } 
Answers the question: “How do we set the value for an element?”, Where v is the value (in this case, 2 ).

Link to dock: finom.imtqy.com/matreshka/docs/Matreshka.html#bindElement

Let's complicate the task. Let's say we have a slider from jQuery UI: api.jqueryui.com/slider and we have a task to bind it to our data. We look at the documentation and see the event "slide" , which is triggered by dragging the slider.

First, before binding, we will announce the slider:
 <div class="slider"></div> 

 $( ".slider" ).slider({ min: 0, max: 100 }); 

Secondly, we declare a copy of the Matryoshka:
 var mk = new Matreshka(); 

Next, call the binding:
 mk.bindNode( 'x', '.slider', { on: 'slide', // ,       getValue: function() { return $( this ).slider( 'option', 'value' ); //      (.  jQuery ui.slider)? }, setValue: function( v ) { $( this ).slider( 'option', 'value', v ); //      (.  jQuery ui.slider)? } }); 

Now that we call the following code ...
 mk.x = 44; 
... the position of the slider will change.

Conversely, when we drag the slider, our mk.x changes.

UPD : Starting from version 0.2, the third argument has a new key 'initialize' , which allows you to initialize an element before binding it. See article about 0.2 . The code now looks like this:
 mk.bindNode( 'x', '.slider', { initialize: function() { $( this ).slider({ min: 0, max: 100 }); }, on: 'slide', getValue: function() { return $( this ).slider( 'option', 'value' ); }, setValue: function( v ) { $( this ).slider( 'option', 'value', v ); } }); 

A little more complicate the task by adding an HTML element that displays the value:
 <div class="output">Value is <span class="x"></span></div> 

And we tie it to the 'x' :
 mk.bindNode( 'x', '.output .x', { setValue: function( v ) { this.innerHTML = v; } }); 

As you can see, the options for binding an element lack the "on" property and the method "getValue" . This means that we will not extract the value from the ".output .x" element, but only set.

We can bind several elements to one property. The converse is also true: we can bind many properties to one element. Just remember that the Matryoshka can bind elements according to the rule “many to many”.

Result: jsbin.com/bulobuhu/7/edit (open the Console tab and try changing x , for example, mk.x = 42; )

Great, we tied two elements, but got a bunch of redundant code (in my opinion). What if we have a lot of sliders? Every time to write ...
 mk.bindNode( property, element, { on: 'slide', getValue: function() { return $( this ).slider( 'option', 'value' ); }, setValue: function( v ) { $( this ).slider( 'option', 'value', v ); } }); 
... not very beautiful.

What to do? You need to remember the common features of the elements that will be checked when linking elements. It would be convenient to do so:
 mk.bindNode( 'x', '.slider' ); 

How to do it? The Nested Doll has the static property MK.elementProcessors MK.defaultBinders (renamed), which is an array of functions. The functions take the argument el , which is the element to be checked, and contain the condition: if the element matches a certain rule, then we return the options object, which in the examples .bindNode above are the third argument.

For any slider, there is one rule: each has a ui-slider class. Therefore, our condition function will look like this ( UPD : don't forget about the 'initialize' property):
 function( el ) { if( $( el ).hasClass( 'ui-slider' ) ) { return { on: 'slide', getValue: function() { return $( this ).slider( 'option', 'value' ); }, setValue: function( v ) { $( this ).slider( 'option', 'value', v ); } }; } } 

The example, I hope, is quite simple: we check if the element is a slider and, if so, return options.

Insert the function into the array MK.defaultBinders :
 MK.elementProcessors.push( function( el ) { if( $( el ).hasClass( 'ui-slider' ) ) { return { on: 'slide', getValue: function() { return $( this ).slider( 'option', 'value' ); }, setValue: function( v ) { $( this ).slider( 'option', 'value', v ); } }; } }); 

The dock link: finom.imtqy.com/matreshka/docs/Matreshka.html#defaultBinders

After that, having at least a hundred sliders, in the .bindNode function .bindNode we define only the property and the binding element:
 <div class="slider1"></div> <div class="slider2"></div> 

 $( ".slider1, .slider2" ).slider({ min: 0, max: 100 }); var mk = new Matreshka(); mk.bindNode({ x1: '.slider1', x2: '.slider2' }); 

Result: jsbin.com/celarefu/2/edit

Pay attention to how in the example of the link the output is attached:
 mk.bindNode({ x1: '.output .x1', x2: '.output .x2' }, MK.binders.innerHTML() ); 

MK.binders is a static property containing custom bindings functions that should return the object described above (with on, setValue, getValue ). The object can be extended with its own binder.
MK.binders.innerHTML is a function that returns the binding options to a regular html element, changing its innerHTML .

Doc: finom.imtqy.com/matreshka/docs/Matreshka.binders.html#innerHTML

Why not create a rule that changes .innerHTML for any element that does not match any other rule? In a word: convenience. Sometimes there is a need to bind elements for which no value is required, but this is in the next article.

By default, MK.defaultBinders contains one function that checks an element for compliance with trivial rules, making it possible to use simple elements ( select , textarea , input[type="text"] , input[type="checkbox"] , input[type="radio"] ).
For example,
 <select class="my-select"> ... </select> 

Using this code:
 mk.bindNode( 'x', '.my-select' ); 
... Matryoshka herself learns when, how to get, how to set the value of an element, since the most common select .

Example: jsbin.com/tepiyoso/2/edit (click, change the text of the input and look at the source)

Evaluation approach with class selector
Two days ago I posted the announcement of the Matryoshka on the javascript.ru forum. The user nerv_ reasonably described a critique of the approach with selectors, citing the code on AngularJS.
It turns out that you need to monitor at least the service selectors.
And it turns out that all the "binding" must be written in js code. And, as a rule, there are a lot of them, instead of asking declaratively - cheap and cheerful.

I agree that with a large number of bindable elements, you need to make sure not to change the classes of bound elements, therefore, in this case, it may be appropriate to specify the keys in the UI:
 <form> <select data-key="a"></select> <select data-key="b"></select> </form> 

Wooden way:
 $( '[data-key]' ).each( function() { this.bindNode( this.getAttribute( 'data-key' ), this ); }); 

The correct way (for more on the .each method, .each the article about MK.Object ):
 this.each( function( v, key ) { this.bindNode( key, this.$( '[data-key=' + key + ']' ) ); }); 

These two methods can be called the first steps to the MVVM pattern, which can be implemented on the basis of the Nested doll. Preparing proof in the form of a plug-in and an article about it.

I prefer to distinguish between "complex" elements of the application in such a way that each element is an elementary class (which I mean, will be clearer in the article on inheritance), in which there are not so many bindings. Perhaps my approach is severely deformed by the conviction that the JS code and HTML should be as demarcated as possible: the layout designer, and the programmer, if he makes, then only minimal and necessary changes.



Developments


Matryoshka contains an event generator that works in the same way as in Backbone.js. Initially, the framework was written for its own needs, so the part of the code responsible for the events was borrowed from this framework. But the code of these methods turned out to be very stable, so it was decided to leave it.
 var mk = new Matreshka(); mk.on( 'someevent', function( a, b ) { alert( a + ', ' + b ); }); mk.trigger( 'Hello', 'World!' ); // alerts "Hello, World!" 

Familiar?

Reference to documentation: finom.imtqy.com/matreshka/docs/Matreshka.html#on

One of the tasty features mentioned at the beginning of the post is tracking the property change event:
 var mk = new Matreshka(); mk.on( 'change:x', function() { alert( 'New x value is ' + this.x ); }); mk.x = 5; //alerts "New x value is 5" 

We can also bind a property to an element and track the change in its value:
 <select class="my-select"> <option>1</option> <option>2</option> <option>3</option> </select> 

 var mk = new Matreshka(); mk.bindNode( 'x', '.my-select' ); mk.on( 'change:x', function() { alert( 'x is now ' + this.x ); }); 

There is a huge difference in the approaches: we used to track changes in the DOM element, and now we track changes in the data, since for us only the data are important, the states of the elements change by themselves.
Try it yourself: jsbin.com/dadakeba/1/edit

Using the .set method


The .set method simply assigns a value to a given property. It is used for two purposes:
1. Passing properties to the change:*key* event object (for example, the "silent" flag). UPD: Flags became more.
 mk.on( 'change:x', function( evtOpts ) { alert( evtOpts.myFlag ); }); mk.set( 'x', 5, { myFlag: 'blah' } ); //      "change:x" mk.set( 'x', 42, { silent: true } ); //        
Note that even if the silent: true flag is passed, the value of the bound element will still change.
2. Abbreviation code. An object with properties can be passed to the .set method.
 mk.set( { x1: 1, x2: 2 } ); mk.set( { x1: 3, x2: 4 }, { silent: true } ); 


Where to get?


Repository on github: github.com/finom/matreshka (the framework code is in the build/ folder)
Matryoshka requires jQuery, although it is planned to remove the dependence on it. Starting with version 0.1, jQuery is not required.

In conclusion


I introduced you to the main features that the Matryoshka carries. The remaining functions can be found in the documentation . There you will find other equally important methods:
.off , .off off specified events
.remove removing property
.define , hanging custom accessories
.defineGetter , hanging a getter on an element
.bound , .boundAll , returning an element or collection of elements, respectively, bound to a property
.unbindElement breaking the link between a property and an element
… other.

Why version 0.0.X?

Just because the project, despite the fact that it has been developed for more than a year under a different name, is being presented for the first time just now (in this article).

What's next?

In the next article, I will tell you how to write my first application using the Nested Doll not just as a set of convenient functions, but as a full-fledged framework based on classes, and tell you why classes (I am familiar with the local indignation about this issue).
Further it will be told about the MK.Array and MK.Object classes responsible for data.
After that, if the reader is interested, I will tell you how magic works, and how I managed to implement Object.defineProperty support in IE8.

Many thanks to all who could read the article to the end. I wish you good luck and successful coding.

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


All Articles