(Archive) Matreshka.js 1.1: even more cool
- Tutorial

- Matreshka.js: From Simple to Simple
- 10 reasons to try Matryoshka
- Matreshka.js 1.1: even more cool
- Matreshka.js: events
Documentation in Russian
Github repository
Hello everyone. Today, September 28, marks two years since the first commit to the Matryoshka repository. It so happened that by this time a new release arrived with all sorts of goodies for any JavaScript developer (even for those who do not want to use Matryoshka as a framework).
Matryoshka is a JavaScript framework (or, if you want, a library) based on accessors, and squeezed from them incredible, at first glance, possibilities. Remember the time when getters and setters just appeared in JavaScript? How much noise was around them ... Articles, conversations ... Then, everything calmed down: many did not understand how to use these opportunities, except in simple examples. Matryoshka is an excellent answer to the question of why accessors are needed in JavaScript.
By tradition, let me remind you what this framework can do with the help of a small piece of code.
Previously, you could only do this:
// this - экземпляр Матрешки
// связываем свойство "x" с элементом на стрнице
this.bindNode('x', 'input.my-node');
// если изменилось, выводим alert
this.on('change:x', function() {
alert(this.x);
});
// меняем свойство, вызывается обработчик
// меняется и привязаннык к "x" элемент
this.x = 'Wow!';
Now you can also do this:
var object = {};
// связываем свойство "x" с элементом на стрнице
MK.bindNode(object, 'x', 'input.my-node');
// если изменилось, выводим alert
MK.on(object, 'change:x', function() {
alert(object.x);
});
// меняем свойство, вызывается обработчик
// меняется и привязаннык к "x" элемент
object.x = 'Wow!';
Due to the fact that the latest versions of Chrome and NodeJS finally began to support most of the elements of the ES6 syntax, all the examples below in this post will be written in ES6. In such a simple way, I want to congratulate everyone who considers these innovations incredibly cool and draw attention to ES.next for those who are not familiar with them.
Support for native objects
The most important innovation was Matryoshka's support for arbitrary objects. Yes, yes, in order to declare a binding or do something else cool, it is not necessary to create an instance of Matryoshka.
let object = {};
//связываем свойство "x” с элементом '.my-node'
MK.bindNode(object, 'x', '.my-node');
// "y” всегда будет суммой значений свойств x и z
MK.linkProps(object, 'y', 'x z', (x, z) => x + z);
//”z” - это всегда число, независимо то того, какой тип мы ему присвоим
MK.mediate(object, 'z', Number);
// ...
As you can see from the example, the new static methods completely repeat the dynamic ones with one small difference: they need to pass the original object as the first argument:
//было
this.bindNode('x', '.my-node');
//стало
MK.bindNode(object, 'x', '.my-node');
In addition, collections that can render themselves can no longer require specifying a model that should have been inherited from the Matreshka class .
class MyArray extends MK.Array {
itemRenderer() {
return '';
}
constructor() {
super().bindNode('sandbox', '.some-node');
}
}
let arr = new MyArr();
arr.push(someData);
A complete list of new static methods is listed in the MatreshkaMagic section .
MatreshkaMagic Library
Thanks to the support of native objects, it became possible to take all the "magic" functions into a separate, more compact library that does not include the Matreshka , Matreshka.Array , Matreshka.Object classes and the Class function . An object
MatreshkaMagicor a shorter version is available for the developer magic, containing all the static methods of the class Matreshka. The library is located in the magic / repository folder .
magic.bindNode(object, 'x', '.my-node');
magic.linkProps(object, 'y', 'x z', (x, z) => x + z);
// и т. д.
Read more about the library in the documentation .
"Deep binding"
Another cool feature that appeared due to the support of native objects is the so-called “deep binding”. Having an object of arbitrary nesting, you can associate the DOM node with any property somewhere deep in the object.
this.a = {b: {c: {d: 41}}}
this.bindNode('a.b.c.d', '.my-node');
The Matryoshka kernel monitors the entire branch of objects and re-installs binding if one of the objects in the branch is overridden
this.a.b = {c: {d: 42}};
Deep Links
Matryoshka has long included the linkProps method , which allows you to set the dependence of some properties on others.
You can set the dependence on your own properties:
this.linkProps('a', 'b c d', (b, c, d) => b + c + d);
this.b = 1;
this.c = 2;
this.d = 3;
alert(this.a); // 6
You can set the dependence on the properties of other objects:
this.linkProps('a', [
externalObject, 'b',
externalObject2, 'c',
this, 'd'
]);
externalObject.b = 1;
externalObject2.c = 2;
this.d = 3;
alert(this.a); // 6
Now
linkPropssupports specifying the property path:this.linkProps('a', 'b.c.d e.f', (d, f) => d + f);
this.b = {c: {d: 40}};
this.e = {f: 2};
alert(this.a); // 42
When something changes in the chain of the path to the property, Matryoshka captures this change, breaks the connection with the old sub-chain and creates a dependence on the new chain.
this.b.c = {d: 1};
As before, you can create a dependency on the properties of other objects, and, as mentioned above, any object can act as the source object:
let targetObject = {},
o1 = {b: {c: {d: 40}}},
o2 = {e: {f: 2}};
MK.linkProps(targetObject, 'a', [
o1, 'b.c.d',
o2, 'e.f'
], (d, f) => d + f);
alert(targetObject.a); // 42
Transparent delegate event syntax
Let me remind you that in previous versions it was possible to hang an event not only on the current object (
this), but also on an object of arbitrary nesting. But the syntax was poor. Let me give you a small example. Say, an instance of Matryoshka has or should have some property, which, in turn, is also an instance of Matryoshka.this.a = new Matreshka();
You could, before or after assigning a property, create a handler for any event that relates to this property. For this, the syntax with the "dog" was used.
this.on('a@someevent', handler);
this.a.trigger('someevent');
For arrays and objects (which in the Matryoshka are key-value collections), it was possible not to specify the target property, since the event is listened to in all elements included in the collection:
this.on('@someevent', handler);
this.push(new Matreshka());
this[0].trigger('someevent');
At first glance, it looks simple. But what if our tree of objects is a little more complicated? For example, a property
"a"contains a collection:this.a = new MK.Array();
this.a.push(new Matreshka());
How to catch an event inside such a collection? You can combine two dogs that say: "to
"a"catch an event in an object "@someevent"-> to catch an event in an array element "someevent"".this.on('a@@someevent', handler);
this.a[0].trigger('someevent');
This can still be experienced (if you drink enough coffee). But what if we want to go deeper? Then the number of “dogs” will increase and coffee will not help ...
Agree, the potential of this feature is very large. We can listen to data events of any nesting, for example, learn about changing the properties of an object contained in an array of arrays, etc. Therefore, it was decided to slightly change the syntax of delegated events. “Doggie” remained, but as the only separator of the path to the object and the name of the event. If the event touches a nested object, the dogs are replaced by dots. If we want to know about something in the collection, instead of a faceless dog, we use an asterisk. Here I probably need to stop and give a couple of examples.
If we want to attach a handler to a property
"а" then the syntax remains the same:this.on('a@someevent', handler);
If we want to catch an event on a collection item, then instead of this:
this.on('@someevent', handler);
We write like this:
this.on('*@someevent', handler);
An asterisk means “any property responsible for data in MK.Object ” or “any element of the MK.Array collection ”.
We go deeper. We need to comb the following example described above:
this.on('a@@someevent', handler);
Now we write like this:
this.on('a.*@someevent', handler);
The syntax is much cleaner. You just need to specify the path to the object before @, and after it specify the name of the event.
A detailed article about the events.
setClassFor
setClassFor is another incredibly cool feature. It indicates which class the given property should be an instance of. When trying to overwrite a property, the internal interceptor, instead of being assigned, updates it with new data. Let's look at an example.
// задаём свойству изначальные данные (не обязательно)
this.x = {a: 41};
// устанавливаем класс для свойства
this.setClassFor('x', MyClass);
// проверяем, является ли свойство экземпляром класса MyClass
console.log(this.x instanceof MyClass); // true
// проверяем свойство "a” экземпляра
console.log(this.x.a); // 41
// теперь самое интересное
// сохраняем значение "x” в одноименную переменную
var x = this.x;
// пытаемся перезаписать свойство
this.x = {a: 42};
// проверяем, обновились ли данные
console.log(x.a); // 42
// проверяем, перезаписаось ли свойство на самом деле
console.log(x === this.x); // true
// Wow! Экземпляр класса не изменился, а данные обновились!
If you have a deep structure of objects and are also running in nested objects
setClassFor, you can do interesting things. For example, save the presentation of tiered data in local storage.localStorage.x = JSON.stringify(this.x);
And then restore them with a wave of a magic wand:
this.x = JSON.parse(localStorage.x);
Or, drive back and forth to the server.
There are a lot of cases where such logic may be needed. As another example, I will give the code from the documentation (for brevity, class properties from ECMAScript 7 are used):
// app.js
class App extends MK {
constructor(appData) {
this.appData = appData;
this.setClassFor('appData', AppData);
}
}
// app-data.js
class AppData extends MK.Object {
constructor(data) {
super(data)
.setClassFor({
friends: Friends,
settins: Settings
});
}
}
// friend.js
class Friend extends MK.Object {
constructor(data) {
super(data);
}
}
// friends.js
class Friends extends MK.Array {
Model = Friend;
constructor(data) {
super(...data);
}
}
// settings.js
class Settings extends MK.Object {
constructor(data) {
super(data)
.setClassFor('credentials', Credentials);
}
}
// credentials.js
class Credentials extends MK.Object {
constructor(data) {
super(data);
}
}
// app-init.js
var app = new App({
settings: {
name: 'Vasiliy Vasiliev',
credentials: {
email: 'vasia.vasia@gmail.com'
}
},
friends: [{
name: 'Yulia Zuyeva',
id: 1
}, {
name: 'Konstantin Konstantinopolsky',
id: 2
}, {
name: 'nagibator3000',
id: 3
}]
});
// данные можно сериализировать и передать на сервер
JSON.stringify(app.appData);
// потом просто присвоить новые данные свойству appData
// при этом, структура классов не изменится
app.appData = {
settings: {
name: 'Petr Petrov',
credentials: {
email: 'petr.petrov@gmail.com'
}
},
friends: [{
name: 'Yulechka Zuyeva',
id: 1
}, {
name: 'Konstantin Konstantinopolsky',
id: 2
}]
};
More details in the documentation for the method .
DOM template engine
Matryoshka is a framework that professes the idea that logic should be contained in JS files, as opposed to frameworks that implement the MVVM pattern, which forces you to describe logic in HTML code.
Implementing logic in JS files is really very convenient. But, sometimes, situations arise when the description of all the bindings is too expensive in terms of the number of lines of code.
Therefore, it was decided to improve and speed up the DOM template engine, which was absent until recently in the official API. What is he doing? It takes a DOM node, a collection of DOM nodes, HTML code or the sandbox of the current object, parses it to find an angular-like type construct,
{{KEY}}and creates anchors where these constructs are found.Look at the {{info.title}}this.parseBindings();
this.website.domain = 'example.com';
this.category = 'foo';
this.page = 42;
this.info.title = 'cool stuff';
The method is described in more detail in the documentation .
The method does not contradict the Matryoshka ideology, since there can be no logic (loops, conditions, handlers) in the template.
In addition to publishing the API of the method itself, the template engine for collections is now enabled by default (no more writing
useBindingsParser: true).class MyArray extends MK.Array {
itemRenderer = 'Hello, {{name}}';
...
}
More sugar for ECMAScript 2015
You
setClassForcan see from the example k that the methods are launched immediately after super(). This possibility became a reality thanks to a very simple change: all three constructors ( Matreshka, Matreshka.Array, Matreshka.Object) is returned thisinstead undefined.class MyObject extends MK.Object {
constructor(data) {
super(data)
.bindNode('x', '.my-node');
}
}
// создаст экземпляр MyObject со свойствами a и b,
// отвечающими за данные
myObject = new MyObject({a: 1, b: 2});
class MyCollection extends MK.Array {
constructor(data) {
super(...data)
.bindNode('x', '.my-node');
}
}
// создаст коллекцию, состоящую из 5 элементов
myCollection = new MyCollection([1,2,3,4,5]);
Support for event-type objects in on , once , onDebounce methods
In previous versions, the only syntax for declaring event handlers was available to the developer.
this.on('eventname1', handler1);
this.on('eventname2', handler2);
Now you can declare several handlers by calling the appropriate method only once:
this.on({
'eventname1': handler1,
'eventname2': handler2
});
This news would not be mentioned in this post, if not for one thing: using ECMAScript 2015, you can greatly shorten the code in microtasks.
this.on({
'eventname1': evt => this.x = 42,
'eventname2': evt => doSomethingElse()
});
Against the “old” syntax:
this.on({
'eventname1': function(evt) {
this.x = 42
},
'eventname2': function(evt) {
doSomethingElse();
}
});
Overriding itemRenderer
itemRendererIs a virtual property of the collection ( Matreshka.Array), which talks about how to draw the elements of the collection.// для краткости, синтаксис ES7
class MyCollection extends MK.Array {
itemRenderer = "Hi there!";
constructor() {
super()
.bindNode('sandbox', '.array-sandbox')
.push({a: 1}, {a: 2});
}
}
See the itemRenderer documentation for more details .
Starting with the new version, when overridden
itemRenderer, the collection is automatically redrawn.//каждый элемент коллекции - span
this.itemRenderer = 'I'm a span';
//каждый элемент коллекции - div
this.itemRenderer = 'I'm a div';
You can come up with several user cases: you want to change the design of the collection with one button or your template is located on the server (in the example below, the Fetch API is used ).
fetch('templates/my-template.html')
.then(resp => resp.text())
.then(text => this.itemRenderer = text);
You can work with the collection as usual: insert, delete, sort items, while not having to wait until the server gives the template. Upon returning from the server, the collection itself will be drawn on the page.
New binders
Let me remind you that a binder is an object that indicates how to associate a property with an element on the page. The binder is used by the bindNode method , which implements one or two-way binding.
this.bindNode('x', '.my-node', binder);
this.x = 42; // элемент на странице тоже изменился
A detailed description can be found in the documentation for the method .
MK.binders.progress - Associates a property with the HTML5 state of the progress element . The binder does not need to be called manually, as it is part of the collection of standard binders.
this.bindNode('x', '.my-progress');
this.x = 42; // меняет значение прогресса на 42
MK.binders.innerText - Associates a property with the text value of any element that has the
textContentor property innerText.this.bindNode('x', '.my-node', MK.binders.innerText());
this.x = 'Some Text'; // задаст ноде содержимое "как есть”, в виде текста
MK.binders.style - Binds an object property to an
styleelement object property .this.bindNode('x', '.my-node', MK.binders.style('color'));
this.x = 'red'; // изменит цвет текста на красный
And the most interesting: MK.binders.file . This new binder will not only catch the user’s change in the content
input[type=”file”], but will also read the file in the format you need:this.bindNode('x', '.my-file', MK.binders.file('dataURL'));
// событие изменения генерируется, когда файл прочитан
this.on('change:x', function() {
console.log(this.x.readerResult); // "data:image/png;base64,iVBO..."
});
More details in the documentation for the binder .
getValue for innerHTML , className , property and attribute binders
Now, using the above binders, the Matryoshka kernel will check if the current property has a corresponding property and, if the answer is no, it will extract the value from the element and assign it to the property.
Some data// скажем, что this.x не определен.
this.bindNode('x', '.my-div', MK.binders.innerHTML());
alert(this.x); //"Some data"
onItemRender
A
Matreshka.Arraynew virtual method has appeared onItemRender. It is called when one of the elements in the collection has been drawn. The method makes the code flatter, avoiding listening to the event "render". An event
"render"has always been a standard pattern, allowing you to add the necessary bindings when rendering.class MyCollection extends MK.Array {
Model: MyModel;
itemRenderer = '';
constructor() {
super()
.bindNode('sandbox', '.array-sandbox')
.on('*@render', evt => {
evt.self.bindNode(...);
});
}
}
Now you can do this:
class MyCollection extends MK.Array {
Model: MyModel;
itemRenderer = '';
constructor() {
super()
.bindNode('sandbox', '.array-sandbox');
}
onItemRender(item, evt) {
item.bindNode(...);
}
}
In "models" appeared like a virtual method:
onRender. It used to be like this:
class MyModel extends MK.Object {
constructor() {
super()
.on('render', evt => {
this.bindNode(...);
});
}
}
Now you can write like this:
class MyModel extends MK.Object {
constructor() {
super()
}
onRender() {
this.bindNode(...);
}
}
Nodes and $ nodes properties
After declaring data binding and DOM nodes, the developer could access the connected nodes using the bound and $ bound methods .
bound returns the first anchored element, $bound- all elements in the form of a jQuery or Balalaika collection.this.bindNode('x', '.my-node');
var boundNode = this.bound('x');
var allBoundNodes = this.$bound('x');
Properties
nodesand $nodesallow you to do the same, but almost for free, in terms of performance, since these properties are ordinary objects.this.bindNode('x', '.my-node');
var boundNode = this.nodes.x;
var allBoundNodes = this.$nodes.x;
Some more new methods
Matreshka.to converts an arbitrary object into instances
MK.Objectand MK.Array. MK.Array.of , which works the same as Array.of but returns an instance
MK.Array. MK.Array.from , which works the same as Array.from but returns an instance
MK.Array. MK.trim for browsers that do not support String.prototype.trim .
MK.toArray , which converts an array-like array into a native one
Arraytwice as fast as Array.prototype.slice does .Increase in productivity
With the help of microoptimizations (for example, using a cycle
for..ininstead of a function each) and larger changes, it turned out to achieve excellent results. For example, in the benchmark with small collections (10 elements), Matryoshka lagged behind React by 10-20 percent in Chrome and Firefox (although it overtook the collection in the benchmarks with a large number of elements). Now in the same test, Matryoshka is 50 percent faster than React in Chrome, and 3 times faster in Firefox. Here is a list of benchmarks to see for yourself: 10 elements , 50 elements , 100 elements , 500 elements , 1000 elements .
Work on bugs: tests
Matryoshka is finally tested automatically. At the time of this writing, 148 tests have been implemented that test the methods before they get into the develop brunch. Particularly meticulously tested are delegated events that are required to work under a variety of different circumstances and not break anything at the same time.
Browser support
A warning appeared on the documentation site that the use of Matryoshka in Internet Explorer 8 was not recommended due to the mass of methods that could not be implemented for this version of the donkey. In fact, this is only a disclaimer for cases when a developer tries to use such methods thoughtlessly. Only one thing to remember: static methods that add “magic” to native objects do not work in IE8.
Such code will work in IE8, in case
this- an instance of Matryoshka.this.bindNode('key', '.node');
And this will work:
var mk = new Matreshka();
mk.bindNode('key', '.node');
And this code will work only in IE9 + and in other browsers (including the ancient WebKit and Opera Mini):
var object = {};
MK.bindNode(object, 'key', '.node');
If your hands itch very much to use static methods in the eighth donkey, you can first convert the object into an instance of Matryoshka:
var object = MK.to({});
MK.bindNode(object, 'key', '.node');
Thus, semantic versioning is respected.
Other changes
- The bindNode syntax has expanded a bit.
- Errors about missing nodes using the method are
bindNodemore informative: now in the text, except for the key, the selector is indicated (if the selector is passed). - The source code is broken into small components.
- Unnecessary spaces have been removed from the code and from examples on the site (
f(x)instead f( x )). - As already mentioned above, Matryoshka supports Opera Mini and old WebKit.
Other changes and a list of fixed errors can be found in the corresponding section on the site .
And further
You can look at what has been done and is planned to be done at Trello . You can also vote there, increasing the priority of cards.
In the Gitter chat quite often there are discussions of new features. Because of this, the questions that users ask and the answers to them get lost somewhere in the dark cellars of the Internet. Therefore, it was decided to launch, as an experiment, a forum based on Muut ( in Russian and in English ). If a question arises, feel free to ask it there (even if you think the question is stupid).
For example, here is one of Rendol ’s great questions with a detailed answer to it:
Greetings!
Again the same question that I already had before, but I still did not find a beautiful answer to it.
For example, correspondence as in VK:User = { id: 5, name: 'Fedor', online: true }
We place this user in different rooms: room1, room2, room3.
If User.online = false, then in all 3 rooms, the color should change, for example.
T.O. 3 collections that contain one object and at the same time this object is displayed in 3 places.
Note: it is not necessary that these collections will be of the same type (not only rooms), may be of different types and representations.
Is it possible to bind a single object to multiple views?
Answer:
Да, коллекции, содержащие объект могут быть разными. Для этого у него должно быть установлено свойствоbindRenderedAsSandbox: false, так как при вхождении в несколько коллекций, песочницей станет сразу несколько элементов (например, несколько разных строк из разных таблиц, а это сильно усложнит вам жизнь). Поэтому отключаем песочницу. При срабатывании событияrenderможно определить, в какую именно коллекцию вставлена модель и, исходя из этого, объявить привязки.
Вот небольшой пример накатал: jsbin.com/cidise/12/edit. Объектuserнаходится сразу в двух коллекциях (в таблице и списке ul), которые рендерятся по-разному. Можете написать в консолиtableUsers[0].name = 'xxx'and all nodes tied to this user will change. It turns out that you do not need to create many separate objects and synchronize their values.
Threat. With support for IE 8, to check if an object is an instance of a class, you need to use the method.instanceOfobject.instanceOf( MyClass ); // вместо object instanceof MyClass
Hope the post has been helpful. Thanks for attention.
Only registered users can participate in the survey. Please come in.
What about trying Matryoshka?
- 11.7% already using 11
- 7.4% I will definitely try in the next project 7
- 38.2% An interesting solution, but you need to think 36
- 23.4% I will not use 22
- 3.1% I do not use frameworks 3
- 15.9% Posted by equine Adam's apple 15