Skip to content

Matreshka.js: Events

Andrey Gubanov edited this page Dec 3, 2015 · 7 revisions

Basics

Custom events

Let’s start with the simplest thing. In Matreshka events can be added with the help of on method.

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

Where the list of events separated by spaces can be passed to.

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

Static MK.on method is used to declare an event handler for a custom object (which is or is not Matreshka instance) (the difference is only that the target object is the first argument but not “this”).

var object = {};
MK.on(object, 'someevent', handler);

Events can be fired with trigger method.

this.trigger('someevent');

Use the static alternative of the method for custom objects.

MK.trigger(object, 'someevent');

At the same time, you can pass some data to the handler having determined the first and the following arguments.

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

Or

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

You can notice Backbone syntax here. That’s right: the first lines of Matreshka’s code were being written under the (negative) impression of Backbone (even the code has originally been borrowed from it, though it has undergone substantial transformation later).

Hereafter, in this post, I will show alternative methods which use “this” key-word (except the examples of delegated events). Just remember that on, once, onDebounce, trigger, set, bindNode and other Matreshka’s methods have got static alternatives which accept a custom target object as the first argument.

Besides “on” method, there are two more: once and onDebounce. The first one adds a handler that can be called only once.

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

The second one «debounces» the handler. When an event fires out, the timer with the specified delay by a programmer starts. If no event with the same name is called upon the expiry of the timer, a handler is called. If an event fires before the delay is over, the timer updates and waits again. This is the implementation of a very popular “debounce” micropattern which you can read about on this page resource or on Matreshka website.

this.onDebounce('someevent', function() {
  alert('yep');
});
for(var i = 0; i < 1000; i++) {
  this.trigger('someevent');
}
// it will show ‘yep’ once in a very short space of time

Remember the method can accept a delay.

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

Events of property changing

When a property is changed, Matreshka fires an event: change:KEY.

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

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

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

You can change a property without calling an event handler in this way:

// changing doesn’t fire an event
this.set('x', 9000, {silent: true});

“set” method supports some more flags, the description of which would make us go beyond the theme of the article, so I refer you to the documentation of the method.

Events which are being fired before a property changing

In 1.1 version another event: beforechange:KEY has appeared which is being fired before a property changing. The event can be useful in cases you define change:KEY event and want to call the code which precedes this event.

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

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

this.on('beforechange:x', function(evt) {
  alert(evt.someData);
});
this.set('x', 42, {someData: 'foo'});
// changing doesn’t fire an event
this.set('x', 9000, {silent: true});

Events of a property removing

On removing properties with remove method, delete:KEY and delete events are fired.

this.on('delete:x', function() {
  alert('x is deleted');
});
this.on('delete', function(evt) {
  alert(evt.key + ' is deleted');
});
this.remove('x');

Binding events

On the binding declaration two events: bind and bind:KEY are fired, where KEY is a key of a bound property.

this.on('bind:x', function() {
  alert('x is bound');
});
this.on('bind', function(evt) {
  alert(evt.key + ' is bound');
});
this.bindNode('x', '.my-node');

This event can be of use, for example, when another class controls bindings and you need to launch your code after some binding (for instance, sandbox binding).

The events of event adding/removing

That’s right. When an event is added, addevent and addevent:NAME events are fired, and when an event is removed, removeevent and removeevent:NAME events are fired, where NAME is an event name.

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

Originally, addevent:NAME event was implemented for inner optimizations, but then it was decided to put it into the public API.

One of the ways of its application can be the use of Matreshka and the event engine of the third-party library together. Let’s say, you want to place all handlers for the class only in one on call, having made the code more readable and compact. With the help of addevent you catch all the following event initializations, and in the handler you check an event name against some conditions and initialize an event using API of the third-party library. In the example below there’s a code from a project written in ECMAScript 2015, which uses Fabric.js. addevent handler checks an event name for the presence of fabric: prefix and if checking is passed, it adds the corresponding handler to the canvas with the help of Fabric API.

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

Hidden events

For the sake of completeness, it should be mentioned that Matreshka’s core also uses events for implementing cool features. On property changing, for example, two events are fired: _runbindings:KEY which calls the mechanism of synchronization with DOM element and _rundependencies:KEY which calls linkProps mechanism.

Delegated events

Now let’s get down to the most interesting: event delegations. The syntax of delegated events is as follows: PATH@EVENT_NAME, where PATH is the way (properties are separated by a dot) to the object which EVENT_NAME event needs to be added to. Let’s consider the examples.

Example 1

You want to add an event handler in “a” property which is an object.

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

The handler will be called only when someevent event has fired in the “a” object.

// if a is an instance of Matreshka
this.a.trigger('someevent'); 
// if a is an ordinary object or an instance of Matreshka
MK.trigger(this.a, 'someevent');

Also, the handler can be declared before “a” property is declared. If “a” property is rewritten into another object, Matreshka’s inner mechanism will catch this change, remove the handler from the previous property value and add it to a new value (if the new value is an object as well).

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

The handler will be called again.

Example 2

What if our object is a collection inherited from Matreshka.Array or Matreshka.Object (MK.Object is a collection of a key-value type , instances of this class have each method which iterates over all properties responsible for data and a support of for..of cycles)? We don’t know beforehand in which item of the collection an event will be fired (in the first or in the tenth one). That’s why, instead of a property name for these classes we can use an asterisk “*” meaning that an event handler must be called only when an event is fired upon one of the elements included into the collection.

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

If the included element is an instance of Matreshka:

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

Or, in case the included element is an ordinary object or an instance of Matreshka:

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

Example 3

Let’s go deeper. Suppose we have “a” property that contains an object with “b” property, in which someevent event must be fired. In this case properties are separated by a dot:

this.on('a.b@someevent', handler);
this.a.b.trigger('someevent');
//or
MK.trigger(this.a.b, 'someevent');

Example 4

We have “a” property which is a collection. We want to listen to someevent event that must be fired in some element included in this collection. Combine examples (2) and (3).

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

Example 5

We have a collection containing objects with “a” property which is an object. We want to add a handler to all objects we have with “a” key in each element of the collection:

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

Example 6

We have a collection which items have “a” property that is a collection. In its turn, the “a” includes items containing “b” property which is an object. We want to catch someevent in all “b” objects:

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

Example 7. Various combinations

Besides custom events, you can use the ones which are built in Matreshka as well. Instead of someevent you can use change:KEY event described above or modify which allows to listen to any changes in MK.Object or MK.Array.

// in “a” object there’s “b” object,
// in which we listen to changes of “c” property.
this.on('a.b@change:c', handler);
// “a” object is a collection of collections
// we want to catch changes
//(adding/removing/resorting of elements) of the last-named.
this.on('a.*@modify', handler);

Let me remind you that delegated events are added dynamically. On declaring a handler any branch of the way may be absent. If anything is overridden in the object tree, the binding to the old value is disrupted and a new one is created with a new value:

this.on('a.b.c.d@someevent', handler);
this.a.b = {c: {d: {}}};
MK.trigger(this.a.b.c.d, 'someevent');

DOM events

Matreshka is known to allow the binding of DOM element on the page to some Matreshka’s instance property or an ordinary object implementing one-way or two-way data binding:

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

More detailed information about bindNode method.

Before or after the declaration of the binding you can create a handler that listens to DOM events of the bound element. The syntax is as follows: DOM_EVENT::KEY, where DOM_EVENT is DOM or jQuery event, and KEY is a key of a bound property. DOM_EVENT and KEY are separated by a double colon.

Clone this wiki locally