📜 ⬆️ ⬇️

Matreshka.js 2: events

image


Documentation in Russian
Github repository


Hello!


The functionality of the events in Matreshka.js has become so rich that she undoubtedly deserved a separate article.


This article is in English.


Basics: arbitrary events


Let's start with the simplest. events in the framework are added using the on method.


const handler = () => { alert('"someeevent" is fired'); }; this.on('someevent', handler); 

To which you can send a list of events separated by spaces.


 this.on('someevent1 someevent2', handler); 

To declare an event handler in an arbitrary object (whether or not it is an instance of Matreshka ), use the static Matreshka.on method (the only difference is that the target object is the first argument, not this ).


 const object = {}; Matreshka.on(object, 'someevent', handler); 

Events can be generated using the trigger method.


 this.trigger('someevent'); 

For arbitrary objects, you can use the static analogue of the method .


 Matreshka.trigger(object, 'someevent'); 

In this case, you can pass some data to the handler, specifying the first and subsequent arguments.


 this.on('someevent', (a, b, c) => { alert([a, b, c]); // 1,2,3 }); this.trigger('someevent', 1, 2, 3); 

Or


 Matreshka.on(object, 'someevent', (a, b, c) => { alert([a, b, c]); // 1, 2, 3 }); Matreshka.trigger(object, 'someevent', 1, 2, 3); 

Here you can see the Backbone syntax. That's right: the first lines of the Matreshka.js code were written under the impression of Backbone (even the code was originally borrowed from there, although it suffered great changes later).


Further, in this post, I will give a variant of the methods using the keyword this (with the exception of examples of delegated events). Just remember that on , once , onDebounce , trigger , set , bindNode and other methods of Matreshka.js have static analogs taken by an arbitrary target object as the first argument.


In addition to the on method, there are two more: once and onDebounce . The first hangs up the handler, which can only be called once.


 this.once('someevent', () => { alert('yep'); }); this.trigger('someevent'); // yep this.trigger('someevent'); // nothing 

The second "eliminates the rattle" handler. When an event is triggered, a timer starts with a delay set by the programmer. If the timer with the same name is not raised after the timer expires, the handler is started. If the event worked before the end of the delay, the timer is updated and waits again. This is an implementation of the very popular debounce micro pattern, which can be read on Habré , on an English-language resource .


 this.onDebounce('someevent', () => { alert('yep'); }); for(let i = 0; i < 1000; i++) { this.trigger('someevent'); } //        'yep' 

Do not forget that the method may take a delay.


 this.onDebounce('someevent', handler, 1000); 

Property change events


When the property changes, Matreshka.js generates a change:KEY trip.


 this.on('change:x', () => { alert('x is changed'); }); this.x = 42; 

In case you want to pass some information to the event handler or change the property value without calling the "change:KEY" event, instead of the usual assignment, use the Matreshka # set method (or the static Matreshka.set method), which takes three arguments : key, value and object with data or flags.


 this.on('change:x', evt => { alert(evt.someData); }); this.set('x', 42, { someData: 'foo' }); 

And here is how you can change a property without calling an event handler:


 this.set('x', 9000, { silent: true }); //     

The set method supports a few more flags, the description of which would make it go beyond the scope of the article, therefore, please refer to the documentation for the method .


Events generated before property change


In version 1.1, another event appeared: "beforechange:KEY" , generated before the property was changed. An event can be useful in cases where you define a "change:KEY" event and want to call the code preceding this event.


 this.on('beforechange:x', () => { alert('x will be changed in few microseconds'); }); 

You can pass some data to the handler or cancel the event generation.


 this.on('beforechange:x', evt => { alert(evt.someData); }); this.set('x', 42, { someData: 'foo' }); this.set('x', 9000, { silent: true }); //     

Property deletion events


When properties are removed by the remove method, the delete:KEY and delete events are generated.


 this.on('delete:x', () => { alert('x is deleted'); }); this.on('delete', evt => { alert(`${evt.key} is deleted`); }); this.remove('x'); 

Binding events


When a binding is declared, two events are generated: "bind" and "bind:KEY" , where KEY is the key of the associated property.


 this.on('bind:x', () => { alert('x is bound'); }); this.on('bind', evt => { alert(`${evt.key} is bound`); }); this.bindNode('x', '.my-node'); 

This event can be useful, for example, when the banding is controlled by another class, and you need to run your code after some sort of binding (for example, a sandbox binding).


Events add and delete events


image

When an event is added, the events "addevent" and "addevent:NAME" are generated, when they are deleted - "removeevent" and "removeevent:NAME" , where NAME is the name of the event.


 this.on('addevent', handler); this.on('addevent:someevent', handler); this.on('removeevent', handler); this.on('removeevent:someevent', handler); 

One of the ways to use it is to use the framework events in conjunction with the events engine of the third-party library. Let's say you want to place all the handlers for a class in a single on call, making the code more readable and more compact. With the help of "addevent" you intercept all subsequent event initiations, and in the handler check the event name for any conditions and initialize the event using the API of a third-party library. In the example below, the code is from a project that uses Fabric.js . The "addevent" handler checks the event name for the presence of the "fabric:" prefix and, if validated, adds the appropriate handler to the canvas using the Fabric API.


 this.canvas = new fabric.Canvas(node); this.on({ 'addevent': evt => { const { name, callback } = evt; const prefix = 'fabric:'; if(name.indexOf(prefix) == 0) { const fabricEventName = name.slice(prefix.length); // add an event to the canvas this.canvas.on(fabricEventName, callback); } }, 'fabric:after:render': evt => { this.data = this.canvas.toObject(); }, 'fabric:object:selected': evt => { /* ... */ } }); 

Delegated Events


Now let's get down to the most interesting: the delegation of events. The syntax of the delegated events is as follows: PATH@EVENT_NAME , where PATH is the path (properties separated by a dot) to the object on which the EVENT_NAME event is attached. Let's look at examples.


Example 1


You want to hang up an event handler in the "a" property, which is an object.


 this.on('a@someevent', handler); 

The handler will be called when the "someevent" event occurred in "a" "someevent" .


 this.a.trigger('someevent'); //  a -  Matreshka Matreshka.trigger(this.a, 'someevent'); //  a -     Matreshka 

In this case, the handler can be declared before the property "a" declared. If the property "a" overwritten by another object, the internal mechanism Matreshka.js will catch this change, remove the handler from the previous value of the property and add a new value.


 this.a = new Matreshka(); this.a.trigger('someevent'); // this.a = {}; Matreshka.trigger(this.a, 'someevent'); 

The handler will be called again.


Example 2


And what if our object is a collection inherited from Matreshka.Array or Matreshka.Object ( Matreshka.Object is a collection, such as key-value)? We do not know in advance in which element of the collection an event will occur (in the first or tenth). Therefore, instead of the name of the property, for these classes, you can use the asterisk "*", indicating that the event handler should be called when the event is called on one of the members of the collection.


 this.on('*@someevent', handler); 

If the incoming item is an instance of Matreshka :


 this.push(new Matreshka()); this[0].trigger('someevent'); 

Or, in case the incoming element is either a regular object or an instance of Matreshka :


 this.push({}); Matreshka.trigger(this[0], 'someevent'); 

Example 3


We go deeper. Let's say we have the property "a" , which contains an object with the property "b" , in which the event "someevent" should occur. In this case, the properties are separated by a dot:


 this.on('ab@someevent', handler); this.abtrigger('someevent'); // Matreshka.trigger(this.ab, 'someevent'); 

Example 4


We have property "a" , which is a collection. We want to catch the event "someevent" , which should occur in any element included in this collection. Combining examples (2) and (3).


 this.on('a.*@someevent', handler); this.a[0].trigger('someevent'); // Matreshka.trigger(this.a[0], 'someevent'); 

Example 5


We have a collection of objects containing the property "a" , which is an object. We want to hang the handler on all objects contained under the key "a" for each element of the collection:


 this.on('*.a@someevent', handler); this[0].a.trigger('someevent'); // Matreshka.trigger(this[0].a, 'someevent'); 

Example 6


image

We have a collection whose elements contain property "a" , which is a collection. In turn, the latter includes elements containing the property "b" , which is an object. We want to catch "someevent" on all objects "b" :


 this.on('*.a.*.b@someevent', handler); this[0].a[0].b.trigger('someevent'); // Matreshka.trigger(this[0].a[0].b, 'someevent'); 

Example 7. Various combinations


In addition to arbitrary events, you can use the built-in Matreshka.js. Instead of "someevent" you can use the "change:KEY" event described above or "modify" , which allows you to listen to any changes in Matreshka.Object and Matreshka.Array .


 //   "a"   "b",       "c". this.on('ab@change:c', handler); //  "a" -   //     (// ) . this.on('a.*@modify', handler); 

I remind you that delegated events are hung dynamically. When declaring a handler, any branch of the path may be missing. If something in the object tree is redefined, the link with the old value is broken and a link is created with the new value:


 this.on('abcd@someevent', handler); this.ab = {c: {d: {}}}; Matreshka.trigger(this.abcd, 'someevent'); 

DOM events


As you know, Matreshka.js allows you to associate a DOM element on a page with some property of an instance of Matreshka or a regular object, implementing one or two-way binding:


 this.bindNode('x', '.my-node'); // Matreshka.bindNode(object, 'x', '.my-node'); 

Learn more about the bindNode method .


Before or after a binding declaration, you can create a handler that listens for the DOM event of the bound element. The syntax is: DOM_EVENT::KEY , where DOM_EVENT is a DOM or jQuery event (if jQuery is used), and KEY is the key of a bound property. DOM_EVENT and KEY separated by a double colon.


 this.on('click::x', evt => { evt.preventDefault(); }); 

The object of the original DOM event is the domEvent of the event object passed to the handler. In addition, several properties and methods are available in the object in order not to access the domEvent each time: preventDefault , stopPropagation , which , target and several other properties.


This feature is syntactic sugar, over ordinary DOM and jQuery events, and the code below does the same as the previous one:


 document.querySelector('.my-node').addEventListener('click', evt => { evt.preventDefault(); }); 

DOM delegated events


Announcing the events from the example above requires a binding declaration. You must perform two steps: call the bindNode method and, in fact, declare the event. This is not always convenient, as there are often cases where the DOM node is not used anywhere except in a single DOM event. For such a case, another variant of the DOM event syntax is provided, which looks like DOM_EVENT::KEY(SELECTOR) . KEY , in this case - a certain key associated with a certain DOM element. and SELECTOR is the DOM selector of the element that is included in the element associated with the KEY .


 <div class="my-node"> <span class="my-inner-node"></span> </div> 

 this.bindNode('x', '.my-node'); this.on('click::x(.my-inner-node)', handler); 

DOM delegated events inside the sandbox


If we need to create a handler for some sandbox element, we use a slightly simplified syntax DOM_EVENT::(SELECTOR) .


Let me remind you that the sandbox limits the effect of a Matreshka instance or an arbitrary object to a single element in a web application. For example, if there are several widgets on a page, and each widget is controlled by its class, it is highly desirable to set a sandbox for each class that points to the root element of the widget affected by this class.


 this.bindNode('sandbox', '.my-node'); this.on('click::(.my-inner-node)', handler); 

This code does the exact same thing:


 this.on('click::sandbox(.my-inner-node)', handler); 

Events class Matreshka.Object


Let me remind you, Matreshka.Object is a class responsible for data, such as key-value. Read more about this class in the documentation .


Each time the properties responsible for the data change, a "set" event is generated.


 this.on('set', handler); 

Each time the properties responsible for the data are deleted, a "remove" event is generated.


 this.on('remove', handler); 

Each time you change or delete the properties responsible for the data, a "modify" event is generated.


 this.on('modify', handler); 

In this simple way, you can listen to all changes in the data, instead of manually eavesdropping the properties.


Events class Matreshka.Array


With an array, everything is much more interesting. Matreshka.Array includes a lot of useful events that make it possible to find out what happened in the collection: insert an element, delete an element, re-sort.


Let me remind you, Matreshka.Array is the class responsible for implementing collections in the framework. The class completely repeats the methods of the built-in Array.prototype , and the programmer does not need to think about which method to call in order to add or remove something. What you need to know about the events Matreshka.Array :



Some examples from the documentation:


 this.on('add', function(evt) { console.log(evt.added); // [1,2,3] }); //   , //         this.on('addone', function(evt) { console.log(evt.addedItem); // 1 … 2 … 3 }); this.push(1, 2, 3); 

In order not to copy the contents of the documentation in full, I suggest that you familiarize yourself with the documentation for Matreshka.Array yourself.


Thanks to everyone who stays with the project. All good.


')

Source: https://habr.com/ru/post/267513/


All Articles