⬆️ ⬇️

(Archive) Matreshka.js v0.1

The article is outdated. See current version history .





(All previous articles updated to current status)

Repository



Site (ibid, and documentation)

')

Matreshka Logo Hello. 5 months have passed since the last publication of the series of articles on the Matryoshka. Since then, a small number of bugs was fixed, several handy features appeared, including under the influence of your comments, the project found a sponsor in the face of Shooju , received a logo and a normal, non-bootstrap site.







Let me remind you, Matryoshka is a general purpose framework in which the importance of data dominates the appearance, and the interface is automatically updated when the data is updated.
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.





A little bit about the design of the article
I will use the jsdoc syntax to denote methods and properties, in particular - the grid (#) for example, MK#addDependence means that I’m talking about the addDependence method, an instance of the MK class.




Most delicious



Dependencies ( MK method # addDependence )



This coolest method allows you to set the dependence of some data from others. The first argument is the key of the dependent property, the second is the array of keys that the property depends on (or a string with the keys listed separated by spaces), the third is the handler function, which must return a new value for the property. This is a kind of replacement for the getter, with the difference that the getter is called every time you get a property, and addDependence calculates the value of the property in advance, when you change the data on which the property depends. You need to work very carefully with the getter, since relatively heavy calculations can greatly affect the performance of your code. “Dependencies,” in turn, are no different in terms of resource consumption from the usual processing of data change events and are, in fact, syntactic sugar over them. In addition, the method is another step towards self-documenting code.



Let's say we need the property f always be the sum of the properties a, b, c, d, e .

Here is the code based on pure events:

 this.on( 'change:a change:b change:c change:d change:e', function() { this.f = this.a + this.b + this.c + this.d + this.e; }); 


Now compare it with this:

 this.addDependence( 'f', 'abcd e', function() { return this.a + this.b + this.c + this.d + this.e; }); 


Or even with this:

 this.addDependence( 'f', 'abcd e', function( a, b, c, d, e ) { return a + b + c + d + e; }); 




First , we have to make fewer gestures (no need for 'change:' heaps)

Secondly , by the name of the method and the arguments we understand well why the corresponding lines of code were created. Translating into human language, the first method can be voiced like this: “when changing the properties of a, b, c, d, e do something,” and the second way: “add the dependence of property f on properties of a, b, c, d, e ". Feel the difference?

Third , if one of the properties on which another property depends is changed with the silent flag, the first option will not work.



For example, there is the task of calculating the perimeter.



Option 1, on events:

 this.on( 'change:a change:b', function() { this.p = ( this.a + this.b ) * 2; }); 


Option 2, using dependencies:

 //  2 this.addDependence( 'p', 'a b', function() { return ( this.a + this.b ) * 2; }); 


Now, if we call:

 this.set({ a: 2, b: 3 }, { silent: true }); 
... then in the first version, p will not change, in the second, it will change.



Note that if you hooked the event handler to change p , and one of the properties that p depends on changed with the silent flag, then, as expected, the change handler p will not be called.

 this.on( 'change:p', function() { /* ... */ } ); this.set( 'a', 12, { silent: true }); //   "p"    "" 


In the next version, it is planned to add a dependency of a property on data from other classes (a common problem in applications where data dominates appearance). This is already implemented, but not documented due to limitations in the syntax of the language. It is assumed that dependence on other classes will look like this:

 this.addDependence( 'a', [ instance1, 'bc d', instance2, 'ef g', this, 'hij' ], function() { /* ... */ }); 


Where the odd element of the array from the second argument is an instance, even is the list of keys. Looks specifically, I will be glad to other options.



On the examples page, you can see how the method works live .



Regarding the comment Rendol : mapping can be implemented using dependencies.



Mediators (intermediaries)



MK method # setMediator



Quite often, the task of validating and converting data is embedded. Let's say in your class there is a property a , which should always be a string and nothing else. Let's try to solve this problem using the standard toolkit:

 // - this.on( 'change:a', function() { if( typeof this.a !== 'string' ) { this.a = String( this.a ); } }); // - 1 this.on( 'change:a', function() { if( typeof this.a === 'string' ) { /* ... */ } }); // - 2 this.on( 'change:a change:b', function() { if( typeof this.a === 'string' ) { /* ... */ } }); //   this.a = 123; 


See what's going on here? The first handler converts a to a string, and the last two handlers are forced to check whether a is a string, since the converter handler starts all the handlers (including itself) again. Wandering around in search of a solution, such as the event beforechange:%% , it was decided to introduce a new concept into the framework - “intermediary”.



The mediator (or mediator) changes the value of the property before any event associated with a change in this property is triggered.



The syntax of the MK#setMediator is simple: the first argument is the key for which you want to set the mediator, the second argument is the function that should return the new value of the property. Alternative syntax: a key-mediator object is passed to the method in case you want to attach several mediators to a class at once.



For example, the property a should always be a string, and the property b should always be an integer (or NaN )

 this.setMediator( 'a', function( value ) { return String( value ); }); this.setMediator( 'b', function( value ) { return parseInt( value ); }); 


And for those who know Javascript well:

 this.setMediator( 'a', String ); this.setMediator( 'b', parseInt ); 


Mediators are not limited to such simple features. Nothing forbids performing any calculations, but only carefully, so as not to harm the performance of the application.



The method is mega-friendly and cool, including when the server accepts any particular type of value. There can be only one mediator. The next call to MK#setMediator with the same property will block the old pick. In order to remove the "intermediary", instead of the function, you can pass null .



Take a look at a live example from the examples page.

 mk.setMediator({ percentageValue: function( v ) { return v > 100 ? 100 : v < 0 ? 0 : v; }, stringValue: String, integerValue: parseInt }); 


We have established mediators for three properties. The first is the percentage property: the value of the property can be from 0 to 100, then what goes beyond this range is automatically converted to a valid value (if less than 0, then the value becomes 0, if greater than 100, then the value becomes 100). The second value is a string, the property must always be a string. The third must always be an integer (or NaN ). Developing a thought, you can create a property that is always true or false, you can create a property that will always be an instance of some class ...



MK.Array method # setItemMediator



There is another type of mediator: an array element mediator. When installing such a mediator, it transforms each added element as you want. Take a look at an example from the documentation:

 var mkArray = new MK.Array( 1, 2, 3, 4, 5 ); mkArray.setItemMediator( function( value ) { return String( value ); }); mkArray.push( 6, 7 ); mkArray.unshift( true, {} ); 


 console.log( mkArray.toJSON() ); // [ "true", "[object Object]", "1", "2", "3", "4", "5", "6", "7" ] 


Here you create a typed array: an array of strings. Yes, of course, such a typed array differs in performance from the built-in typed arrays for the worse. But the possibilities for validation and data conversion now have no boundaries.



Do not forget that the mediator of an array element can be the only one for the whole class. And you can remove a mediator by setting it to null .

 mkArray.setItemMediator( null ); 


By the way, the example above can be fixed for advanced programmers:

 mkArray.setItemMediator( String ); 


Cool?



"By numerous requests"



After the publication of the first series of articles, habrauys criticized some of the features of the Nested doll. The criticism was reasonable, and it was decided to reconsider controversial points and make corrections. Here are some of them:



1. MK.Array # pop and MK.Array # shift return the deleted item, instead of “itself”. Starfall : Comment on the article , winbackgo : Comment on the article

2. Default binders for input[type="text"] and textarea now listening to the 'paste' event, not just the 'keyup' . safron : article comment

3. Default binders for input[type="checkbox"] and input[type="radio"] now listening to the 'keyup' event. This means that you can work with these elements from the keyboard when you bind data to them (the same comment).



Balalaika

Balalaika



In addition, it was decided to remove the hard dependency on jQuery . In my opinion, jQuery is a great library, but it loses its relevance in new browsers. Now, if there is no jQuery on the page, it replaces the mini library, which I called “Balalaika” (note: only if it is missing; if jQuery connected, then jQuery is still used).



Balalaika inherits Array.prototype , so the developer has access to all the methods that the array has, plus jQuery compatible methods for working with classes ( addClass , removeClass , hasClass ), events ( on , off ), parsing HTML ( parseHTML ) and others.



To use the Balalaika directly, the global variable $ b is used :

 $b( 'div' ).forEach( function(){ /* ... */ } ); $b( '.blah-blah', context ).is( 'div' ); $b( 'input[type="text"]' ).on( 'keydown', handler ); 




(It is planned to write a separate post about Balalaika)



banzalik : Comment with the wish

jMas : Another comment



Other innovations



MK method # select



Selects the first available element corresponding to the selector inside bound to this (key "__this__" , see previous articles). The method was created for simplified work with individual elements, but not with collections of elements.

 this.select( '.blah-blah' ); 




MK method # selectAll



Selects all elements that match the selector within the bound to this . Does the same thing as the $ method (dollar sign). MK#selectAll created for completeness of the set of methods: if there is a “select” method ( MK#select ), then there must be a “select all” method.

 this.selectAll( '.blah-blah' ); //   ,   this.$( '.blah-blah' ); 




MK.Array method # pull



Deletes and returns the item with the specified index.

 var x = this.pull( 3 ); 


It is syntactic sugar over splice .

 var x = this.splice( 3, 1 )[ 0 ]; 




The isMK , isMKArray and isMKObject properties



Properties that are always true in instances of the corresponding classes

 var mkObject = new MK.Object(); alert( mkObject.isMK && mkObject.isMKObject ); // true 




Fixes



In addition, several bugs were fixed. Here is a list of fixes:





Renamed methods and properties



Following the recommendations of semver.org, outdated methods and properties will not be removed until release 1.0, but warnings and requests to use new methods will be displayed in the console.





Smart array



The MK.Array plug-in was MK.DOMArray , which was mentioned in the article about MK.Array . That is, the functionality that reflected the "attention drawing" gif works out of the box. Let me remind you that the MK.DOMArray plugin changes the DOM automatically when the array is changed (add, delete, sort ...).



Take a look at an example from the site of Matryoshka . A more detailed description of the smart array is planned a little later.



"Road map"



  • Implement a replaceable Model property (which will be similar to the model from Backbone ). This feature will be syntactic sugar over the mediator of the array element.
  • Lazy initialization. Now, with inheritance, you must always call the initMK method. Not kosher.
  • Rewrite the event engine. The basis may be taken DOM interface EventTarget .
  • Update the MK#addDependence for dependencies on other instances of classes (as described above).
  • Optimize code for minifikator.
  • Correct texts on the site. The site, as you can see, is in English, and there are errors in the text. You can help correct errors using the Ctrl + Enter combination after highlighting text in which there is an error (so as not to make a pool of requests). I'll be very grateful.




In version 1.0 (which is planned approximately in a year) it is planned, first, to remove outdated methods, and secondly, to remove support for the eighth Donkey. All kittens inteta will be happy when no one else will support IE8.



In conclusion



Despite the apparent silence around the project, Matryoshka is developing. All features are not only thoroughly tested, but also work in live applications.



Another goal of the Matryoshka is to make the developer using the framework a “data god” who completely, 100% control what happens in the model, of course, not caring about the interface. For example, it is planned to implement an ascent of a data change event on a tree of objects and arrays, and this will be very cool. Further more…



The space that the Matryoshka core provides, based on accessors (getters and setters), provides the widest field for programmer creativity. Agree, the idea of ​​intermediaries and dependencies lay on the surface. Say, for an input property, the value property is always a string, so that we don’t put it there, the valueAsNumber property valueAsNumber always a number that depends on the string value ...



Thank you for reading (or scrolling) the post to the end. All the rays of good.

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



All Articles