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.
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);
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 .
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 }); //
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');
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).
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 => { /* ... */ } });
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.
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.
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');
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');
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');
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');
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');
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');
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(); });
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);
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);
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.
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
:
Array.prototype
, the corresponding event is raised ( "push"
, "splice"
, "pop"
...)"add"
and "addone"
events are generated. Using the first, an array of inserted elements gets into the "added"
property. Using the second, the inserted element gets into the "addedItem"
property, and the event is generated as many times as there are elements added."remove"
generated by passing an array of deleted items to the "removed"
property of the event object, and "removeone"
generated on each removed item, passing the removed item to the "removedItem"
property."modify"
event. That is, it is not necessary to catch the events "remove"
and "add"
separately.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