Documentation in Russian
Github repository
Hello! In this article I will tell you how to use Matreshka.js on three simple examples. We will look at the basic capabilities of the framework, learn how to work with data and analyze the collections.
A post is a brief compilation of translations from this repository .
To get started is to familiarize yourself with the application Hello World, which is rendered from the post on the site .
// ... // x this.bindNode('x', '.my-input'); // x my-output this.bindNode('x', '.my-output', htmlBinder()); // x this.on('change:x', () => console.log(`x "${this.x}"`)); // ...
When updating the x
property, three things happen:
x
changedWhen entering text in a text field:
x
updatedx
changedAs you can see, you do not need to manually catch the input event in the text field; when changing the value of the property, you do not need to manually set HTML values ​​for the nodes; no need to declare the handle yourself.
The next example is the implementation of the authorization form on the site. We have two text fields: login and password. There are two checkboxes: “show password” and “remember me”. There is one button: “enter”, which is active only when the form is valid. Let's say that the form validation is passed if the login length is at least 4 characters, and the password length is at least 5 characters.
A bit of theory: Matreshka.Object
plays the role of a class that creates objects of key-value type. In each instance of a class, you can separate the properties responsible for the data (what is not transferred to the server, for example) from other properties (what the server does not need, but determines the behavior of the application). In this case, the login, password, and “remember me” are the data that we send to the server, and the property that says whether the form is valid is not.
Detailed and current information about this class is in the documentation .
So, let's create a class that inherits from Matreshka.Object
.
class LoginForm extends Matreshka.Object { constructor () { // ... } }
Since the “application” is very small, all the logic can be placed in the class constructor.
First of all, we will declare the data by default.
super(); this.setData({ userName: '', password: '', rememberMe: true })
The setData method not only sets values, but also declares the properties responsible for the data. That is, userName
, password
and rememberMe
should be transferred to the server (in this example, we simply display JSON on the screen).
Since when developing applications, it is recommended to use all the power of ECMAScript 2015, and since the constructor Matreshka.Object
calls setData
with the argument passed to it, we initialize the defol data with a single call to super
(which would make the same family as Matreshka.Object.call(this, { ... })
) so that the code becomes prettier. The code below does the same as the previous one:
super({ userName: '', password: '', rememberMe: true })
We declare a property, isValid
, which depends on the userName
and password properties. If you change any of these properties (from code, console, or by using a bound element), the isValid
property also changes.
.calc('isValid', ['userName', 'password'], (userName, password) => { return userName.length >= 4 && password.length >= 5; })
isValid
will be true
if the length of the username is at least four, and the length of the password is at least five. The calc method is another cool feature of the framework. Some properties may depend on others, others on third, in others, on the properties of another object. At the same time, you are protected from cyclical links. The method stops working if it encounters dangerous dependencies.
Now, link the object properties and elements on the page. First of all we declare the sandbox. The sandbox is needed in order to limit the impact of the instance to one element on the page and to avoid conflicts (for example, if there are two elements on the page with the same class). Then bind the rest of the items.
// - , // .bindNode({ sandbox: '.login-form', userName: ':sandbox .user-name', password: ':sandbox .password', showPassword: ':sandbox .show-password', rememberMe: ':sandbox .remember-me' })
As you can see, for the remaining elements, a non-standard selector is used :sandbox
, which refers to the sandbox (to an element with the class .login-form
). In this case it is not necessary, as the page contains only our form. Otherwise, if there are several forms or other widgets on the page, it is strongly recommended to restrict the elements to be selected by the sandbox.
Then, we connect the button responsible for submitting the form and the isValid
property. When isValid
is true
, we add the class "disabled" to the element, when false
, we remove. This is an example of one-way binding, or rather, the value of an object property affects the state of the HTML element, but not vice versa.
.bindNode("isValid", ":sandbox .submit", { setValue(v) { this.classList.toggle("disabled", !v); } })
Instead of such a record, you can use a shorter one:
.bindNode('isValid', ':sandbox .submit', Matreshka.binders.className('disabled', false))
See the documentation for the binders object .
We associate the field with the password and the showPassword
property (“show password”) and change the input type depending on the value of the property ( :bound(KEY)
is the last non-standard selector).
.bindNode("showPassword", ":bound(password)", { getValue: null, setValue(v) { this.type = v ? "text" : "password"; } })
getValue: null
means that we redefine the standard behavior of the framework when binding form elements.
Add a form submission event.
.on("submit::sandbox", evt => { this.login(); evt.preventDefault(); })
submit
is a regular, arbitrary DOM or jQuery event, sandbox
is our form ( .login-form
). Such an event and key must be separated by a colon. This is the syntax sugar of the DOM event, i.e. the event can be hung in any other way, including using addEventListener
:
this.nodes.sandbox.addEventListener("submit", evt => { ... });
In the handler, we call the login
method, which we declare below, and prevent the page from reloading, canceling the standard browser behavior using preventDefault
.
The final touch is the login
method. For example, the method displays the resulting object if the form is valid. In a real application, the content of the function should obviously be an ajax request to the server.
login() { if(this.isValid) { alert(JSON.stringify(this)); } return this; }
At the very end, create an instance of the class.
const loginForm = new LoginForm();
You can open the console again and change the properties manually:
loginForm.userName = "Chuck Norris"; loginForm.password = "roundhouse_kick"; loginForm.showPassword = true;
With the data type key-value sorted out. Consider the collection. Let's say the task sounds like this: display a list of some people in a table.
In order not to complicate the example, we place the previously prepared data into the data
variable.
const data = [{ name: 'Ida T. Heath', email: 'ida@dayrep.com', phone: '507-879-9766' }, { name: 'Robert C. Burkhardt', email: 'rburkhardt@teleworm.us', phone: '321-252-5698' }, { name: 'Gerald S. Reaves', email: 'gsr@rhyta.com', phone: '765-431-5347' }];
(names and phone numbers obtained using random data generator)
For starters, as usual, create HTML markup.
<table class="users"> <thead> <th>Name</th> <th>Email</th> <th>Phone</th> </thead> <tbody><!-- --></tbody> </table>
Let's declare the Users
collection, which is inherited from Matreshka.Array
.
class Users extends Matreshka.Array { }
We itemRenderer
property, which is responsible for how the elements of the array are rendered on the page.
get itemRenderer() { return '#user_template'; }
In this case, a selector is specified as the value that refers to the template in the HTML code.
<script type="text/html" id="user_template"> <tr> <td class="name"></td> <td class="email"></td> <td class="phone"></td> </tr> </script>
The itemRenderer property can take other values, including a function or an HTML string.
And we indicate the value of the Model property, defining the class of elements contained in the collection.
get Model() { return User; }
We will create the User
class a little later, to begin with, we define the constructor of the newly created collection class.
constructor(data) { super(); this .bindNode("sandbox", ".users") .bindNode("container", ":sandbox tbody") .recreate(data); }
When creating an instance of a class
sandbox
property and the '.users'
element are '.users'
creating a sandbox (the boundaries of the class influence on HTML).container
property and the ':sandbox tbody'
element ':sandbox tbody'
, defining an HTML node where the drawn elements of the array will be inserted.Fine. But we are going to use as many ECMAScript 2015 features as possible to improve the code. Therefore, we will use the super
call to populate the array.
constructor(data) { super(...data) .bindNode('sandbox', '.users') .bindNode('container', ':sandbox tbody') .rerender(); }
super
(which does the same thing as calling Matreshka.Array.apply(this, data)
).sandbox
property and the '.users'
element are '.users'
container
property is ':sandbox tbody'
element ':sandbox tbody'
container
after we added data).Now we declare “Model”: the User
class, which is inherited from the already familiar to us Matreshka.Object
.
class User extends Matreshka.Object { constructor(data) { ... } }
Set the data passed to the constructor by the setData method, or, as usual, call super
.
super(data);
Then, we wait for the render
event, which is triggered when the corresponding HTML element has been created but not yet inserted on the page. In the handler, we bind the corresponding properties to the corresponding HTML elements. When the value of the property changes, the innerHTML
specified element also changes.
this.on( 'render', function() { this .bindNode({ name: ':sandbox .name', email: ':sandbox .email', phone: ':sandbox .phone' }, Matreshka.binders.html()) ; })
It is possible to replace listening to the "render"
event by creating a special onRender
method (see the dock ), but in order not to complicate this example, let’s leave as follows.
At the end, create an instance of the Users
class, passing the data as an argument.
const users = new Users(data);
Everything. When you refresh the page, you will see a table with a list of users.
Now open the console and write:
users.push({ name: 'Gene L. Bailey', email: 'bailey@rhyta.com', phone: '562-657-0985' });
As you can see, a new item has been added to the table. Now call
users.reverse();
Or any other array method ( sort
, splice
, pop
...). Matreshka.Array
, besides its own methods, contains all the methods of the standard JavaScript array, without exception . Then,
users[0].name = 'Vasily Pupkin'; users[1].email = 'mail@example.com'
As you can see from the examples, you do not need to manually follow the changes in the collection, the framework itself catches changes to the data and changes the DOM.
Do not forget that Matreshka.Array
supports its own set of events. You can catch any change in the collection: adding, deleting, re-sorting the elements using the on method.
users.on("addone", evt => { console.log(evt.addedItem.name); }); users.push({ name: "Clint A. Barnes" });
(will display the name of the added user to the console)
As stated in the documentation for itemRenderer, you can define a renderer at the Model
class level. This is the answer to the frequently asked question: why should I define a renderer at the collection level? Instead of defining an itemRenderer
for the Users
class, you can define the renderer
property for the User
class.
class User extends Matreshka.Object { get renderer() { return '#user_template'; } constructor(data) { ... } }
In fact, there are several ways to implement such an application. It is not necessary to define a Model
if the objects in the array have no serious logic. The application described above can be implemented using one single class.
class Users extends Matreshka.Array { get itemRenderer() { return '#user_template'; } constructor(data) { super(...data) .bindNode('sandbox', '.users') .bindNode('container', ':sandbox tbody') .rerender(); } onItemRender(item) { // item - , Matreshka, // bindNode Matreshka.bindNode(item, { name: ':sandbox .name', email: ':sandbox .email', phone: ':sandbox .phone' }, Matreshka.binders.html()); } }
You can also use the parser of banding , which by default is used by the Matreshka.Array
class, defining the renderer directly in the class and not adding anything to the HTML.
class Users extends Matreshka.Array { get itemRenderer() { return ` <tr> <td class="name">{{name}}</td> <td class="email">{{email}}</td> <td class="phone">{{phone}}</td> </tr>`; } constructor(data) { super(...data) .bindNode('sandbox', '.users') .bindNode('container', ':sandbox tbody') .rerender(); } }
Thanks to all those who reported typos on the site. All good!
Source: https://habr.com/ru/post/254889/