📜 ⬆️ ⬇️

(Archive) Matreshka.js v0.2

The article is outdated. See current version history .


Hello. I present the next update of the Matreshka.js framework to version 0.2. Let me remind you: Matryoshka is a general-purpose open source framework, the ideology of which predominates data over appearance: you set the rules for how the interface should synchronize with the data, then work exclusively with the data, except when the interface event does not concern the data (for example , clicking on the button or submitting the forms themselves do not change the data, but trigger a function which, in turn, works with the data)


')
Example
Matryoshka allows you to quite simply connect data and presentation elements (for example, a property of an object and the value of an input field), without worrying about further synchronization of data and presentation. For example, the simplest binding looks like this:
<select class="my-select"> <option>1</option> <option>2</option> <option>3</option> </select> 

Create an instance:
 var mk = new Matreshka(); 

Associate the x property with the .my-select element:
 mk.bindElement( 'x', '.my-select' ); 

Change data
 mk.x = 2; 

After we assign a different value to property x, the state of the element changes accordingly.
Take a look at a live example.

Another important feature of nesting dolls are events (including custom ones). For example, the Matryoshka can catch a change in the value of a property:
 mk.on( 'change:x', function( evt ) { alert( 'x   ' + evt.value ); }); 

The code will print "x " :
 mk.x = ''; 

For more information about these and other features, see the links above.

Link to the site Matryoshka . Link to github repository .

AMD support


Matryoshka now supports specification of the definition of asynchronous modules, Asynchronous Module Definition. In other words, Matryoshka is compatible with libraries, such as requirejs . This means that you can now write a true code that does not read into the global namespace. Two types of connection are supported: a request for a named module and a request for an anonymous module.

Named modules:
 requirejs.config({ paths: { xclass: 'path/to/matreshka', matreshka: 'path/to/matreshka' } }); require(['xclass', 'matreshka'], function(Class, MK) { return Class({ 'extends': MK //... }); }); 

But this is rather a side effect of using the new project file structure. And the recommended method is to request an unnamed module:
 require(['path/to/matreshka'], function( MK ) { return MK.Class({ 'extends': MK // ... }); }); 

As you can see, Matryoshka contains a Class property that duplicates the function that creates classes: there is no need to request an additional module.

Matreshka # addDependency method : new name and additional features


1. The addDependence method was renamed to addDependency at the prompt of the buriy habrauser (thanks to him), the old method is marked as “outdated”.
2. The method now supports the promised ability to add dependencies on the properties of other classes. The syntax of the second argument is as follows: [ , "", , "", , "" ... ] is an array, with odd elements — instances of classes, even-numbered keys of these instances, on which the desired property depends. Take a look at an example:
 this.addDependency( 'a', [ anotherInstance1, 'b', this, 'c', anotherInstance2, 'd' ], function( b, c, d ) { return b + c + d; }); 

Here the property "a" depends on the property "b" object anotherInstance1 , on the property "d" object anotherInstance2 and on its own property "c" . The old syntax still works:
 this.addDependency( 'a', 'b c', function( b, c ) { return b + c; }); 

3. Secure dependencies. This clause does not affect the syntax: since this release the method avoids an infinite loop if addDependency used addDependency . Imagine a situation where property "a" depends on property "b" , property "b" depends on property "c" , and property "c" , in turn, depends on "a" . Abstract illustration for example:
 this.addDependency( 'a', 'b', function( b ) { return b * 2; }); this.addDependency( 'b', 'c', function( c ) { return c * 3; }); this.addDependency( 'c', 'a', function( a ) { return a / 5; }); 

Each dependency in this code caused the following, resulting in a dangling page. Now, protection against such errors has appeared: the code passes a special flag through the entire dependency chain, and when the framework reaches a potentially dangerous dependency, the chain stops. addDependency in a new form allows you to build mutual dependencies based on complex (or not) formulas, without fear of errors in the implementation of these formulas. An example of calculating the perimeter of a rectangle by the length of the sides, and calculating the lengths of the sides:
 this.addDependency( 'p', 'a b', function( a, b ){ return (a + b) * 2; }); this.addDependency( 'a', 'p b', function( p, b ){ return p/2 - b; }); this.addDependency( 'b', 'p a', function( p, a ){ return p/2 - a; }); 


Static method Matreshka.procrastinate


Imagine the following situation (taken from my practice). You have a form with certain text fields: checkboshes, etc. When the value of one of the form elements changes, the application must send a request to the server, which, in turn, returns data for rendering three graphs. Drawing graphs is hard for a processor and takes half a second on a weak computer ( Highcharts is that). Now imagine a user who is bored and he decided to repeatedly click on the checkbox. What will happen? A lot of requests will be sent, a bunch of answers will come back, which will also draw the schedule many times. What do they usually do in this case? Cancel the request to the server. The question is: why was this request to send, if you could wait until it calms down? :)

To solve this problem, I used the simplest function (possibly a bicycle), which takes another function as an argument and returns its modification, which can only be run once in a certain period of time. No project can do without it, so it was decided to include it in the Matryoshka code. Example:
 var doSomethingHeavy = function( i ) { console.log( 'Ok', i ); }; var procrastinateSomethingHeavy = MK.procrastinate( doSomethingHeavy ); for( var i = 0; i < 100; i++ ) { procrastinateSomethingHeavy( i ); } // >> Ok 100 

Function code (in case you want to use it outside the Matryoshka):
 var procrastinate = function ( f, d, thisArg ) { var timeout; if( typeof d !== 'number' ) { thisArg = d; d = 0; } return function() { var args = arguments, _this = this; clearTimeout( timeout ); timeout = setTimeout( function() { f.apply( thisArg || _this, args ); }, d || 0 ); }; }; 

The method, in addition to the "procraticizing" function, takes a delay, and the context as arguments. The delay is responsible for how many milliseconds the actual function call will be delayed during the next attempt to call it.

And here is an example of the case when the function will never be called (for better understanding).
 var procrastinateSomethingHeavy = MK.procrastinate( function() { console.log( 'Ok' ); }, 1000 ); setInterval( function() { procrastinateSomethingHeavy(); }, 500 ); //    


New key binder initialize


Binder ( binder ) - the third argument of the Matreshka # bindElement method . If you remember, this is an object consisting of three properties: on (for which DOM event the property is updated), getValue (how to extract the property value from the element), setValue (how to set the property value for the element). More details here (by the way, all the articles about Matryoshka are updated every release and are relevant material). Now there is one more optional property initialize .

initialize is a function that starts at the time of binding, or rather, before it. The task of the function is to sweeten the code. Take a look at an example from the first article :
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.bindElement( '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)? } }); 

The code is somewhat redundant: we refer twice to the element with the slider class (first, applying the plugin, then tying the element). Now this can be avoided:
 var mk = new Matreshka(); mk.bindElement( '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 ); } }); 


Matreshka method # defineSetter


This new method, it is not difficult to guess, determines the setter for the property.
 this.defineSetter( 'x', function( value ) { return alert( value ); }); 

When using the method, you need to remember that it messes up the built-in property setter (if it was) and property change events will not work.

 this.x = 1; this.on( 'change:x', function( evt ) { // ,    -   alert( 'x is changed to ' + evt.value ); }); this.defineSetter( 'x', function() { // ... }); this.x = 2; 


New syntax for event names: adding event handlers for properties and collection items


Perhaps the most important thing in this release is the ability to add an event handler to the internal contents of the instance.

Event "@_"

Now you can add a handler for the property inside any class inherited from the Nested Doll (including for MK.Object and MK.Array ), provided that the value of the property is an instance of the Nested Doll. Take a look at an example:
 var mk = new MK; mk.on( 'x@yeah', function() { alert( 'yeah' ); }); mk.x = new MK; mk.x.trigger( 'yeah' ); 

Note that the order of defining the property and hanging the handler is not important: you can first add an event handler, and then declare the property. Moreover, if the value of the property changes, then the handler works only for the new value, and for the old one, the handler is deleted.

Event "@_" for MK.Object

This event name allows you to add a handler for the JSON key of the instance of MK.Object (see the article about MK.Object for the JSON key or the key responsible for the data).
 var mkObject = new MK.Object; mkObject.on( '@yeah', function() { alert( 'yeah' ); }); mkObject.jset( 'x', new MK ); mkObject.x.trigger( 'yeah' ); 

The order of the property declaration and event handler is also not important.

Event "@_" for MK.Array

By analogy with MK.Object , MK.Object the same opportunity: the handler is hung on any of the array elements, provided that this element is inherited from the Matryoshka.
 var mkArray = new MK.Array; mkArray.on( '@yeah', function() { alert( 'yeah' ); }); mkArray.push( new MK ); mkArray[ 0 ].trigger( 'yeah' ); 

These three changes are not limited to just listening to the "yeah" event, you can safely listen to other events, for example, "change:"
 this.on( 'x@change:y', function() { /* ... */ } ); this.on( '@change:y', function() { /* ... */ } ); 

Theoretically, this feature allows you to build fancy event names while listening to other events in the depths of the data tree. Let's say we have a data structure that can be represented as an object:
 { a: [{ b: { c: { e: 1 } } }, { b: { d: { e: 2 } } }] } 

In order to get to the bottom of the changes to the "e" property, you can add this handler:
 this.on( 'a@@b@@change:e', function() { /* ... */ } ); 


Matreshka method # $ bound


Nested dolls have two methods that return bound elements: Matreshka # bound , which returns the first bound element or null and Matreshka # boundAll , which returns a collection of bound elements. There may be problems for newbies who work with jQuery and are not familiar with VanillaJS in understanding the term “collection” and are used to knowing the dollar. Therefore, the $bound method was added to the framework, which does the exact same thing as Matreshka # boundAll .

 this.bindElement( 'a', '#x, #y' ); this.$bound( 'a' ).animate( /* ... */ ); //   jQuery  


Other changes


Matreshka.useAs $ instead of usejQuery and useBalalaika

Let me remind you, starting from version 0.1, Matryoshka got rid of the hard jQuery dependency using the micro-library “Balalaika” if jQuery is not on the page. The effect of this change was to create two methods that, regardless of the presence of jQuery, forced Matryoshka to use one of the two libraries using the usejQuery methods (in case jQuery was connected after Matryoshka) and useBalalaika (in case jQuery was connected before Matrioshka , but you still want to use the built-in library). Now there was a method that allows using any jQuery-like library in general ( usejQuery and useBalalaika are marked as obsolete).

Examples of using:
 MK.useAs$( jQuery ); MK.useAs$( jQuery.noConflict() ); MK.useAs$( Zepto ); MK.useAs$( MK.$b ); //  

The consequence of this change was that Matryoshka, when loading, uses the dollar-sign library, if it exists and has certain methods, instead of using only jQuery. Which methods you can find out in the source code of one of the project files .

Xclass.same method

A slight change that adds sitaxic sugar to classes (see the article on nursing ). Often, when creating a class, the constructor of this class only needs to call the parent constructor in its own context:
 var MyClass = Class({ 'extends': AnotherClass, constructor: function() { AnotherClass.call( this, arguments ); }, someNewMethod: function() { /* ... */ } }); 

Now the same can be done more briefly:
 var MyClass = Class({ 'extends': AnotherClass, constructor: AnotherClass.same(), someNewMethod: function() { /* ... */ } }); 


Adding DOM event handlers (for example, "click::x" ) before the element has been bound

Matryoshka has the ability to hang event handlers on attached elements using the Matreshka # on method:
 this.bindElement( 'x', '.my-element' ); this.on( 'click::x', function() { alert( '.my-element is clicked' ); }); 

The problem is that it was impossible to add a DOM event handler before the element was bound. It was necessary to be perverted by waiting for the bind event and adding a handler upon the occurrence of this event:
 this.on( 'bind:x', function() { this.on( 'click::x', function() { alert( '.my-element is clicked' ); }); }); this.bindElement( 'x', '.my-element' ); 

Now the order of binding / adding DOM events is not important:
 this.on( 'click::x', function() { alert( '.my-element is clicked' ); }); this.bindElement( 'x', '.my-element' ); 


Bug fixes / refactoring




What's next?


1. In the next article I will introduce you to the implementation of TodoMVC. The article is ready, but requires editing. The implementation is also ready, but the documentation is completed for it.
2. After this, a large article is planned on MK.Array , replacing the previous one. There I will talk in more detail about the methods, how the elements of the array are rendered, about the “model” and how to pass options to the methods of the arrays.
3. Version 0.3 with a bunch of interesting changes that are already being tested. As usual, there will be an article.

Then, a big review of the documentation is coming, including combing texts and correcting errors related to the English language. The text of the main page and the page "Why Matryoshka?" Has already been corrected.

All good!

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


All Articles