
(Archive) Matreshka.js - Inheritance
- Tutorial
The article is out of date. The new documentation contains the most relevant information from this post. See Class .
Greetings to all readers and writers of Habr.
In the previous article, we talked about the basics of working with Matryoshka. In this, I want to tell you how to inherit Matryoshka and how to build small applications based on it for now.
Matryoshka is arranged in the form of a class constructed using a custom function
So why classes? A class is just a word that does not contradict the paradigm of prototype programming. If you look at the documentation of the same Backbone.js, you will see that they also use the word "class" without any hesitation. We can argue that there are no classes in Javascript, there are constructors, and I agree with you, but, in fact, does this argument make sense? If the constructor looks like a class, swims like a class, and quacks like a class, then this is probably the class?
From lyrics to action. So, Matryoshka is created as a class:
The class argument is a prototype constructor, which can be defined as follows:
One class can be inherited from another class (in this case it is
(
When you inherit Matryoshka, there is an important rule: the constructor should always be called in it a method
Let's write a trivial application that works with login form.
(you can immediately look at the result: jsbin.com/qohilowu/1/edit )
We have two text fields: login and password. There are two checkboxes: “show password” and “remember me”. There is one button: enter. Let's say that the form validation is completed when the login length is at least 4 characters and the password length is at least 5 characters.
Create a class
The first thing we must do is declare that our class inherits from Matryoshka:
Second - declare the constructor:
Next, bind the elements to the corresponding properties. Personally, I always put the bindings into a separate method that I call
(1) What does a string mean
equivalent to this:
Why do we need special meaning
Documentation for the method
(2) Here we attach the element
Add
Now we will declare the events, and we will also put it into a separate method, which I usually call
(1) We get the form element (bound to
Documentation for the method
(2) Bind the context of the event handler using the Function.prototype.bind method . This is necessary in order to replace the standard execution context of the event handler (element) with our instance. IE8 does not support this method, but I strongly recommend that you always use the es5-shim library (in the following articles you will see how good this is). I get the element itself using
(3) Here the comment speaks for itself, with only one remark. Note the last argument passed to the method
(4) When changing the properties of
Add to the constructor:
Now we make the .login method, which, so far, does not send anything:
And create an instance of the resulting class:
Result: jsbin.com/qohilowu/1/edit
I noticed an interesting effect. Despite four years of programming experience in Javascript, every time I run into someone else’s code and library for a certain period of time (from several seconds to several hours), I don’t understand what is going on in this code. But then, after reading more lines or studying the documentation, what seemed incomprehensible to me becomes completely transparent.
With Matryoshka, my code has become several orders of magnitude more stable. Errors made by inattention almost disappeared. My client is glad that I began to do more in less time.
Surely experienced programmers noticed an absurd data collection in the method
Wouldn't it be better to create a tool that knows where we have the state of the application and where is the data? Sending all the data that we have in the class is not very reasonable. The database is not at all interested in whether the password is shown to the user or not. The database does not need to know whether the data sent is valid, since the server will still check it again and send a response. I will describe the solution to this question in the next article, in which I will talk about the class
Thank you so much for your attention. Good coding.
Greetings to all readers and writers of Habr.
In the previous article, we talked about the basics of working with Matryoshka. In this, I want to tell you how to inherit Matryoshka and how to build small applications based on it for now.
Matryoshka is arranged in the form of a class constructed using a custom function
Class
. This is a slightly modified version of the function that I wrote about on the javascript.ru forum ( link to the dock ).So why classes? A class is just a word that does not contradict the paradigm of prototype programming. If you look at the documentation of the same Backbone.js, you will see that they also use the word "class" without any hesitation. We can argue that there are no classes in Javascript, there are constructors, and I agree with you, but, in fact, does this argument make sense? If the constructor looks like a class, swims like a class, and quacks like a class, then this is probably the class?
From lyrics to action. So, Matryoshka is created as a class:
window.MK = window.Matreshka = Class({ ... });
The class argument is a prototype constructor, which can be defined as follows:
var MyClass = Class({
constructor: function() { ... }
});
... which then returns from the Class function. If the constructor is not defined, then it will become an empty function. One class can be inherited from another class (in this case it is
MyClass
inherited from Matryoshka):var MyClass = Class({
'extends': MK
});
(
'extends'
Quotation marks are needed not only to avoid syntax errors (extends is a reserved word), but also to highlight syntax. Other properties can be without quotes.) When you inherit Matryoshka, there is an important rule: the constructor should always be called in it a method
.initMK
that, in this case, initializes pseudo-private properties: __id
(instance identifier for internal use), an object .__events
(event object) and an object .__special
(storing the values of "special" properties, their accessors and associated elements). The same rule is true for classes, which will be explained in the following articles: MK.Array
and MK.Object
.Let's write a trivial application that works with login form.
(you can immediately look at the result: jsbin.com/qohilowu/1/edit )
(Here I removed the extra blocks and classes that are responsible only for the pleasant appearance; in the resulting example, Bootstrap is used). We have two text fields: login and password. There are two checkboxes: “show password” and “remember me”. There is one button: enter. Let's say that the form validation is completed when the login length is at least 4 characters and the password length is at least 5 characters.
Create a class
LoginForm
. In the work, I adhere to the rule: one class for one complex element (form, widget ...). Now it LoginForm
will be the only class in our tiny application.var LoginForm = Class( ... );
The first thing we must do is declare that our class inherits from Matryoshka:
var LoginForm = Class({
'extends': MK
});
Second - declare the constructor:
var LoginForm = Class({
'extends': MK,
constructor: function() {
this.initMK(); // инициализируем объект событий и "специальных" свойств
}
});
Next, bind the elements to the corresponding properties. Personally, I always put the bindings into a separate method that I call
.bindings
. This is a matter of taste and you can, without problems, bind elements in the constructor (but only after the call .initMK
). ...
bindings: function() {
return this
// привязываем к нашему инстанцу форму (см. ниже (1))
// форма, как и многие другие элементы, имеющие .innerHTML в качестве значения, не имеет опций привязки по умолчанию
// это значит, что привязка не включает никакой логики по обмену значений между элементом и свойством класса
.bindNode( this, '.login-form' )
// в следующем вызове не указываем опции привязки (on, getValue, setValue),
// так как Матрешка их найдет самостоятельно
.bindNode({
userName: this.$( '.user-name' ), // привязывается, как текстовое поле с событием 'keyup'
password: this.$( '.password' ), // привязывается, как текстовое поле с событием 'keyup'
showPassword: this.$( '.show-password' ), // привязывается, как чекбокс с событием 'click'
rememberMe: this.$( '.remember-me' ), // привязывается, как чекбокс с событием 'click'
})
.bindNode( 'isValid', this.$( '.submit' ), { // кастомная привязка (см. ниже (2))
setValue: function( v ) {
$( this ).toggleClass( 'disabled', !v );
}
})
;
},
...
(1) What does a string mean
".bindNode( this, '.login-form' )"
? If the .bindNode
current instance is passed in as the first argument, Matryoshka actually binds a special property "__this__"
. This means that the recordthis.bindNode( this, '.login-form' );
equivalent to this:
this.bindNode( '__this__', '.login-form' );
Why do we need special meaning
"__this__"
? In order to use a method .$
that allows you to set the context of the elements to be attached (sandbox). This binding is optional but desirable. With it and using the method, .$
you can avoid theoretical conflicts of binding the same element in different classes. Documentation for the method
.$
: http://finom.github.io/matreshka/docs/Matreshka.html#$ (2) Here we attach the element
this.$( '.submit' )
to the property 'isValid'
(which will be described below) this way: if isValid == false
, then we add a class to this element 'disabled'
if no, then we remove this class.Remark from the author
This article was written before the release of the shortcut for the binding that sets the class to an element. Starting with version 0.1, the Matryoshka contains the static method MK.binders.className Documentation
link: finom.github.io/matreshka/docs/Matreshka.binders.html#className
This code:
You can replace it with this:
link: finom.github.io/matreshka/docs/Matreshka.binders.html#className
This code:
.bindNode( 'isValid', this.$( '.submit' ), {
setValue: function( v ) {
$( this ).toggleClass( 'disabled', !v );
}
})
You can replace it with this:
.bindNode( 'isValid', this.$( '.submit' ), MK.binders.className( '!disabled' ) )
About code design
Pay attention to how the method is framed. In the process of developing applications based on Matryoshka. I am a great connoisseur of chain method calls ( Method chaining ), so if the method does not return anything specific, then we increase
This makes it easy to find the beginning and end of the chain.
this
. The method .bindNode
, like many other methods (.set, .defineGetter ...) does just that. In addition, I made the rule of block chain blocks:объект (открытие блока)
[табуляция].метод1()
[табуляция].метод2()
...
[табуляция].методN()
точка с запятой (закрытие блока)
This makes it easy to find the beginning and end of the chain.
Add
.bindings
to the constructor: ...
constructor: function() {
this
.initMK()
.bindings() // все привязки должны следовать за вызовом .initMK()
;
},
...
Now we will declare the events, and we will also put it into a separate method, which I usually call
.events
. ...
events: function() {
this.boundAll().on( 'submit', function( evt ) { // привязывание обработчика к форме (см. ниже (1))
this.login();
evt.preventDefault();
}.bind( this ) ); // указываем контекст вызова коллбека (см. ниже (2))
return this
// привязываем обработчик события изменения свойства "showPassword" (см. ниже (3))
.on( 'change:showPassword', function() {
this.bound( 'password' ).type = this.showPassword ? 'text' : 'password';
}, true )
// привязываем обработчик события изменения свойств "userName" и "password" (см. ниже (4))
.on( 'change:userName change:password', function() {
this.isValid = this.userName.length >= 4 && this.password.length >= 5;
}, true )
;
},
...
(1) We get the form element (bound to
this
or '__this__'
), or rather its jQuery
instance, and call the method jQuery.fn.on
with the event 'submit'
. Documentation for the method
.boundAll
: http://finom.github.io/matreshka/docs/Matreshka.html#boundAll (2) Bind the context of the event handler using the Function.prototype.bind method . This is necessary in order to replace the standard execution context of the event handler (element) with our instance. IE8 does not support this method, but I strongly recommend that you always use the es5-shim library (in the following articles you will see how good this is). I get the element itself using
event.target
, event.delegatedTarget
so it's easy giving up the standard context.About future versions (Already implemented)In future versions of Matryoshka, most likely, an alternative way to add a DOM event to the bound element will appear. In version 0.1, this method is already implemented.
Syntax:
or
I want to improve a little. It is planned to do so:
Syntax:
this.boundAll().on( 'submit', function() {
...
}.bind( this ) );
or
this.boundAll( 'key' ).on( 'click', function() {
...
}.bind( this ) );
I want to improve a little. It is planned to do so:
this.on( 'submit::__this__', function() {
...
});
this.on( 'click::key', function() {
...
});
(3) Here the comment speaks for itself, with only one remark. Note the last argument passed to the method
.on
(with value true
). This is not the context of the handler (because the type is - boolean
), this argument tells us that the handler should be started immediately, immediately after the declaration (that is, it does not need to be called .trigger
). (4) When changing the properties of
"userName"
or, "password"
we set the property 'isValid'
to true
or false
, checking the length of the login and password. Add to the constructor:
...
constructor: function() {
this
.initMK()
.bindings()
.events()
;
},
...
Now we make the .login method, which, so far, does not send anything:
...
login: function() {
var data;
if( this.isValid ) {
data = {
userName: this.userName,
password: this.password,
rememberMe: this.rememberMe
};
alert( JSON.stringify( data ) );
}
return this;
}
...
And create an instance of the resulting class:
var loginForm = new LoginForm();
Whole code
var LoginForm = Class({
'extends': MK,
rememberMe: true,
constructor: function() {
this
.initMK()
.bindings()
.events()
;
},
bindings: function() {
return this
.bindNode( this, '.login-form' )
.bindNode({
userName: this.$( '.user-name' ),
password: this.$( '.password' ),
showPassword: this.$( '.show-password' ),
rememberMe: this.$( '.remember-me' )
})
.bindNode( 'isValid', this.$( '.submit' ), {
setValue: function( v ) {
$( this ).toggleClass( 'disabled', !v );
}
})
;
},
events: function() {
this.boundAll().on( 'submit', function( evt ) {
this.login();
evt.preventDefault();
}.bind( this ) );
return this
.on( 'change:showPassword', function() {
this.bound( 'password' ).type = this.showPassword ? 'text' : 'password';
}, true )
.on( 'change:userName change:password', function() {
this.isValid = this.userName.length >= 4 && this.password.length >= 5;
}, true )
;
},
login: function() {
var data;
if( this.isValid ) {
data = {
userName: this.userName,
password: this.password,
rememberMe: this.rememberMe
};
alert( JSON.stringify( data ) );
}
return this;
}
});
var loginForm = new LoginForm();
Result: jsbin.com/qohilowu/1/edit
In conclusion
I noticed an interesting effect. Despite four years of programming experience in Javascript, every time I run into someone else’s code and library for a certain period of time (from several seconds to several hours), I don’t understand what is going on in this code. But then, after reading more lines or studying the documentation, what seemed incomprehensible to me becomes completely transparent.
With Matryoshka, my code has become several orders of magnitude more stable. Errors made by inattention almost disappeared. My client is glad that I began to do more in less time.
What's next?
Surely experienced programmers noticed an absurd data collection in the method
.login
.data = {
userName: this.userName,
password: this.password,
rememberMe: this.rememberMe
};
Wouldn't it be better to create a tool that knows where we have the state of the application and where is the data? Sending all the data that we have in the class is not very reasonable. The database is not at all interested in whether the password is shown to the user or not. The database does not need to know whether the data sent is valid, since the server will still check it again and send a response. I will describe the solution to this question in the next article, in which I will talk about the class
MK.Object
. Thank you so much for your attention. Good coding.