📜 ⬆️ ⬇️

Matreshka.js 2: From simple to simple

image


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 .



1. Hello World!


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:



When entering text in a text field:



As 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.


Demo


2. Authorization form. Meet Matreshka.Object


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.


image

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; 

Demo


3. List of users. We understand the collections ( Matreshka.Array )


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



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(); } 


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.


Demo


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/


All Articles