Sencha Ext JS 7.7 is Here – Discover What’s New and Exciting – LEARN MORE

Declarative Listeners in Ext JS 5

September 10, 2014 107 Views
Show

Declarative Listeners in Ext JS 5In a previous article, Using ViewControllers in Ext JS 5, we touched briefly on a feature that has been greatly improved in Ext JS 5 — declarative event listeners. In this article, we’ll dive in deeper and explore how you can use declarative listeners to simplify your application’s Views and reduce boilerplate code in your custom Components.

NOTE: this article assumes you are using Ext JS 5.0.1 or later.

What Are Declarative Listeners?

When we say “declarative listeners,” we are referring to listeners that are declared on the body of a class or the configuration object of an instance using the listeners config. The ability to declare listeners in this manner is not new to Ext JS 5. In Ext JS 4, you could declare listeners on a class, but only if the handler function or scope had already been defined. For example:

Ext.define(‘MyApp.view.User’, {
extend: ‘Ext.panel.Panel’,

listeners: {
// function must inline or previously defined:
collapse: function() {
// respond to panel collapse here
}
},

// This method cannot be declared as the collapse handler:
onCollapse: function() {
}
});

Because the desired handler function is not usually available at class definition time, declarative listeners had limited usefulness in Ext JS 4. Developers typically added listeners by overriding initComponent and using the on method:

Ext.define(‘MyApp.view.User’, {
extend: ‘Ext.panel.Panel’,

initComponent: function() {
this.callParent();

this.on({
collapse: this.onCollapse,
scope: this
});
},

onCollapse: function() {
console.log(this); // the panel instance
}
});

Scope Resolution

We improved the listeners config in Ext JS 5 by allowing event handlers to be specified as strings that correspond to method names. The framework resolves these method names to actual function references at run time (any time an event is fired). We refer to this process as listener scope resolution.

In Ext JS 4, you could only resolve string handlers if an explicit “scope” was given. In Ext JS 5, we’ve added some special rules for default scope resolution when a “string” listener is declared without an explicit scope.

Scope resolution has two possible outcomes: a component or a ViewController. Whichever the result, the search begins with the component. It could be that the component or its ViewController is the scope, but if not, the framework will “climb” the component hierarchy until it finds a suitable component or ViewController.

Resolving Scope to Components

The first way that the framework resolves scope is to look for a component with the defaultListenerScope config set to true. For listeners declared on the class, the search begins with the component itself.

Ext.define(‘MyApp.view.user.User’, {
extend: ‘Ext.panel.Panel’,
xtype: ‘user’,
defaultListenerScope: true,

listeners: {
save: ‘onUserSave’
},

onUserSave: function() {
console.log(‘user saved’);
}
});

This listener is declared on the “class body” of the User view. This means that the framework will check the User view itself for defaultListenerScope before ascending the hierarchy. In this case, because the User view has defaultListenerScope set to true, the scope for this listener will resolve to the User view.

For listeners declared on an instance config, the component itself is skipped and the framework searches upward starting with the parent container. Consider the following example:

Ext.define(‘MyApp.view.main.Main’, {
extend: ‘Ext.container.Container’,
defaultListenerScope: true,

items: [{
xtype: ‘user’,
listeners: {
remove: ‘onUserRemove’
}
}],

onUserRemove: function() {
console.log(‘user removed’);
}
});

This listener is declared on the “instance config” for the User view. This means that the framework will skip the User view (even though it was declared with defaultListenerScope:true) and resolve upward to the Main view.

Resolving Scope to ViewControllers

In Ext JS 5, we introduced a new type of Controller, Ext.app.ViewController. We covered ViewControllers in detail in Using ViewControllers in Ext JS 5, so we’ll focus only on event listeners as they relate to ViewControllers in this discussion.

Unlike Ext.app.Controller, which can manage many views, each ViewController instance is bound to a single View instance. This one-to-one relationship between View and ViewController allows the ViewController to serve as the default scope for listeners declared on its View or its View’s items.

The same rules applies to ViewControllers as to defaultListenerScope. Class-level listeners always look for a ViewController on the component itself before searching upward in the component hierarchy.

Ext.define(‘MyApp.view.user.User’, {
extend: ‘Ext.panel.Panel’,
controller: ‘user’,
xtype: ‘user’,

listeners: {
save: ‘onUserSave’
}
});

Ext.define(‘MyApp.view.user.UserController’, {
extend: ‘Ext.app.ViewController’,
alias: ‘controller.user’,

onUserSave: function() {
console.log(‘user saved’);
}
});

The above listener is declared on the “class body” of the User view. Because the User view has its own controller, the framework will resolve scope to the the UserController. If the User view did not have its own controller, then scope would resolve upwards in the hierarchy.

On the other hand, instance-level listeners skip the component and resolve to a ViewController upward in the hierarchy starting with the parent Container. For example:

Ext.define(‘MyApp.view.main.Main’, {
extend: ‘Ext.container.Container’,
controller: ‘main’,

items: [{
xtype: ‘user’,
listeners: {
remove: ‘onUserRemove’
}
}] });

Ext.define(‘MyApp.view.main.MainController’, {
extend: ‘Ext.app.ViewController’,
alias: ‘controller.main’,

onUserRemove: function() {
console.log(‘user removed’);
}
});

Listener Config Merging

In Ext JS 4, listeners that were declared on a base class would be completely overwritten by a listeners config declared on a subclass or instance. In Ext JS 5, we improved upon the listeners API by enabling proper merging of declared listeners between base classes, subclasses and instances. To see this in action, let’s look at a simple example:

Ext.define(‘BaseClass’, {
extend: ‘Ext.Component’,
listeners: {
foo: function() {
console.log(‘foo fired’);
}
}
});

Ext.define(‘SubClass’, {
extend: ‘BaseClass’,
listeners: {
bar: function() {
console.log(‘bar fired’);
}
}
});

var instance = new SubClass({
listeners: {
baz: function() {
console.log(‘baz fired’);
}
}
});

instance.fireEvent(‘foo’);
instance.fireEvent(‘bar’);
instance.fireEvent(‘baz’);

In Ext JS 4, the above example would just output “baz,” but in Ext JS 5 the listeners configs are correctly merged and the output is “foo bar baz.” This allows classes to declare only the listeners they need without concern for what listeners their superclass might already have.

Conclusion

We think declarative listeners are a great step forward in simplifying the way you configure event listeners in your applications. Combine them with ViewControllers for handling application logic and ViewModels for two-way data binding, and you should have a much-improved application development experience. Give it a try and let us know what you think.

coming soon

Something Awesome Is

COMING SOON!