(Archive) Matreshka.js v0.1

  • Tutorial
The article is out of date. See current version history .


(All previous articles have been updated to the current state)
Repository

Site (there is also documentation)

Matreshka Logo Hello everyone. 5 months have passed since the last publication of a series of articles about Matryoshka. Since then, a small number of errors have been fixed, several convenient features have appeared, including under the influence of your comments, the project has found a sponsor in the person of Shooju , received a logo and a normal, non-bootstrap website.



Let me remind you that 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 makes it quite easy to relate data and presentation elements (for example, the 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:

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

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

Change data
mk.x = 2;

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

Another important feature of a nested doll is events (including custom ones). For example, Matryoshka can catch a change in the value of a property:
mk.on( 'change:x', function( evt ) {
	alert( 'x изменен на ' + evt.value );
});

The code will output "x изменен на Привет":
mk.x = 'Привет';

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


A bit about article design
I will use jsdoc syntax to denote methods and properties, in particular - a lattice (#) for example, MK#addDependencemeans that I'm talking about a method addDependence, an instance of a class MK.


Most delicious


Dependencies ( MK # addDependence method )


This coolest method allows you to establish the dependence of some data on others. The first argument is the key of the dependent property, the second is an array of keys on which the property depends (or a string with keys enumerated by a space), the third is a handler function that should return a new value for the property. This is a kind of replacement for getter, with the difference that the getter is called every time you get a property, andaddDependencecalculates the value of the property in advance, when changing the data on which the property depends. You need to work with getters very carefully, since relatively heavy calculations can greatly affect the performance of your code. "Dependencies", in turn, in terms of resource consumption, are no different 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.

Say we need a property to falways be the sum of the properties a, b, c, d, e.
This is what pure event code looks like:
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', 'a b c d e', function() {
	return this.a + this.b + this.c + this.d + this.e;
});

Or even with this:
this.addDependence( 'f', 'a b c d e', function( a, b, c, d, e ) {
	return a + b + c + d + e;
});


Firstly , we have to do less body movements (no heap needed 'change:')
Secondly , by the name of the method and arguments, we clearly understand why the corresponding lines of code are created. Translating into human language, the first method can be voiced as follows: "when changing properties, a, b, c, d, edo something", and the second way: "add the dependence of the property fon the properties a, b, c, d, e." Feel the difference?
Thirdly , if one of the properties on which the other property depends is changed with a flag silent, 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 pit will not change, in the second - it will change.

Please note that if you hung an event handler on a change p, and one of the properties on which it depends pchanged with a flag silent, then, as expected, the change handler pwill not be called.
this.on( 'change:p', function() { /* ... */ } );
this.set( 'a', 12, { silent: true }); // для войства "p" изменение тоже будет "тихим"

In the next version, it is planned to add the dependence of the property on data located in other classes (a common task in applications where data dominates the appearance). This is already implemented, but not documented due to language syntax limitations. It is assumed that the dependence on other classes will look like this:
this.addDependence( 'a', [
	instance1, 'b c d',
	instance2, 'e f g',
	this, 'h i j'
], function() { /* ... */ });

Where the odd array element from the second argument is an instance, the even one is a list of keys. It looks specific, I will be glad for other options.

On the page with examples, you can see the method live .

Regarding Rendol 's comment : mapping can be implemented using dependencies.

Mediators (intermediaries)


MK Method # setMediator


Quite often, the task of validating and converting data is involved. Let's say your class has a property athat should always be a string and nothing else. Let's try to solve this problem using standard tools:
// обработчик-конвертер
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;

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

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

Method SyntaxMK#setMediatorit 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 mediator key object is passed to the method for the case if you want to attach several mediators to the class at once.

For example, a property ashould always be a string, and a property bshould 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 possibilities. Nothing prohibits making any calculations, but only carefully so as not to harm the performance of the application.

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

Take a look at a live example from the page with examples.
mk.setMediator({
	percentageValue: function( v ) {
		return v > 100 ? 100 : v < 0 ? 0 : v;
	},
	stringValue: String,
	integerValue: parseInt
});

We have set picks for three properties. The first is a percentage property: the value of the property can be from 0 to 100, what goes beyond the boundaries of this range is automatically converted to a valid value (if less than 0, then the value becomes 0, if more than 100, then the value becomes 100). The second value is a string, the property should always be a string. The third should always be an integer (or NaN). Developing the idea, 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 kind of mediator: the mediator of an array element. When you install such a mediator, it will transform each added element the way you want. Take a look at the 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" ]

This creates a typed array: an array of strings. Yes, of course, such a typed array differs in performance from built-in typed arrays for the worse. But the ability to validate and transform data now knows no bounds.

Do not forget that the mediator of an array element may be the only one in the whole class. And you can remove the 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?

"According to numerous applications"


After the publication of the first series of articles, habrayuzers criticized some features of Matryoshka. Criticism was justified, and it was decided to review the controversial issues and make corrections. Here are some of them:

1. MK.Array # pop and MK.Array # shift return the deleted item, instead of “yourself”. Starfall : Comment on the article , winbackgo : Comment on the article
2. Default binders for input[type="text"]and textareanow listen to the event 'paste', and not just 'keyup'. safron : Comment on article
3. Default binders for input[type="checkbox"]and input[type="radio"]now listen to the event'keyup'. This means that you can work with these elements from the keyboard when binding data to them (same comment).

Balalaika

Balalaika


In addition, it was decided to remove the hard dependence on jQuery. In my opinion, it jQuery’s a wonderful library, but losing relevance in new browsers. Now, if it’s not on the page jQuery, it is replaced by a mini library, which I called “Balalaika” (note: only if not; if jQueryconnected, it is still used jQuery).

Balalaika inherits Array.prototype, so developers have access to all methods kotorue have the array, plus jQuerycompatible methods for working with classes ( addClass, removeClass, hasClass), events ( on, off), parsing the HTML ( parseHTML), and others.

To use 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 a wish
jMas : Another comment

Other innovations


MK # select method


Selects the first element that matches the selector inside the bound to this(key "__this__", see previous articles). The method was created for simplified work with individual elements, and not with collections of elements.
this.select( '.blah-blah' );


Method MK # selectAll


Selects all elements matching the selector inside bound to this. Does the same as the method $(dollar sign). MK#selectAllcreated to complete the set of methods: if there is a “select” ( MK#select) method, then there must be a “select all” method.
this.selectAll( '.blah-blah' );
// то же самое, что и
this.$( '.blah-blah' );


MK.Array # pull method


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 ];


Properties isMK , isMKArray and isMKObject


Properties that are always truein 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, obsolete 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.Arrayplugin MK.DOMArraywas mentioned, which was mentioned in the article about MK.Array. That is, the functionality that the "to attract attention" GIF reflected was working out of the box. Let me remind you that the plugin MK.DOMArraychanges the DOM automatically when the array changes (add, delete, sort ...).

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

"Road map"


  • Implement a replaceable property Model(which will be similar to modelfrom Backbone). This feature will be syntactic sugar over the mediator of the array element.
  • Lazy initialization. Now, when inheriting, you must always call the method initMK. Not kosher.
  • Rewrite the event engine. Perhaps, the DOM interface will be taken as the basis EventTarget.
  • Update the method MK#addDependencefor dependencies on other class instances (as described above).
  • Optimize the code for the minifier.
  • 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 by using the combination Ctrl + Enter after selecting the text in which there is an error (so as not to make the request pool). I'll be very grateful.


In version 1.0 (which is planned about a year later), it is planned, firstly, to remove obsolete methods, and secondly, to remove support for the eighth Donkey. All Internet kittens will be happy when no one else supports 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 Matryoshka is to make a developer using the framework a “data god”, who fully, 100% controls what is happening in the model, of course, without worrying about the interface. For example, it is planned to implement a popup event of a data change event on a tree of objects and arrays, and this will be very cool. More - more ...

The space that the Matryoshka kernel provides, based on accessors (getters and setters), provides a wide field for programmer's creativity. Agree, the idea of ​​intermediaries and dependencies lay on the surface. Say, at an input, a property is valuealways a string, so that we do not put it there, the propertyvalueAsNumberalways a number that depends on the string value ...

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

Only registered users can participate in the survey. Please come in.

Do you use Matryoshka?

  • 3.2% Yes, I use (I will certainly use) 8
  • 41.8% Ideas are interesting, but try 102
  • 32.3% Interesting, but dumb. I will follow the further development of the project 79
  • 22.5% I won’t even try. The framework has no future 55

Also popular now: