the singularity of being and nothingness
Ext JS: Custom Event Domains and Complex Controller Event Selectors
A while back, I did a post on creating a custom Event Domain in Ext JS. If you’re new to the concept, Event Domains in Ext JS are simply classes that fire events which your controllers can listen to. The awesome part about domains, however, is that the “domain” of the events getting fired within your application are grouped together by type (read domain). This allows for very granular control over the event listeners that you construct in your controllers.
For example, the boilerplate for your controller since Ext JS 4.2.1 should now look something like this:
Ext.define('CustomController', { extend: 'Ext.app.Controller', init: function() { this.listen({ controller: {}, component: {}, direct: {}, global: {}, store: {} }) } })
Ext JS comes with the 5 domains bolded above, and as we saw in the last post that you can create your own rather easily. In the example I shared, I created a custom event domain for “proxy”. With this custom domain setup, Ext JS will now monitor all events fired by the Ext.data.proxy.Proxy class, and our controllers can easily listen to those events and do whatever we need them to do. Pretty slick.
A Little Fancier
As nice as this works, I recently came across a scenario where I wanted a bit more fanciness.
In my app, I created a custom Router class which handles…wait for it…routing! This custom class fires a number of events (addroute, dispatch, faileddispatch, etc.). In a few of my controllers, I found that it would be very beneficial (if not absolutely necessary!) to be able to listen to these events. So I decided that I’d try adding a custom Event Domain for my Router class. Having never tried it for a stand-alone class, I wasn’t sure it if would work. But happily enough, it did. I configured the custom event domain exactly as the documentation suggested, and it just worked. Nice. Now in my controllers, i can simply do something like so:
Ext.define('CustomController', { extend: 'Ext.app.Controller', init: function() { this.listen({ controller: {}, component: {}, direct: {}, global: {}, router: { '#router': { dispatch: this.callSomeControllerMethod } }, store: {} }) } })
This worked really well, just like it should have. However, I quickly ran into an issue. While my controllers were listening to the events quite nicely, they were *all* listening to the same events and, more importantly, matching on the same “selector” (the “#router”) bit above. This was not ideal, since I only wanted some controllers to react to particular routes, not all of them.
A Need for Event Selectors
In essence, what I wanted was to be able to define the “selectors” in my router domain listeners to be more like what you can do with the component domain. For example, let’s imagine you have a button component with an itemId of “save” that is a child of a grid panel with an itemId of “list”. In the component event domain, you can do something like:
Ext.create('Ext.grid.Panel', { itemId: 'list', ... dockedItems: [ { xtype: 'toolbar', items: [ { xtype: 'button', itemId: 'save' } ] } ] }); ... Ext.define('CustomController', { extend: 'Ext.app.Controller', init: function() { this.listen({ ... component: { 'grid#list' button#save': { click: this.doSomeAction } }, ... }) } })
This “complex” selector let’s me be very granular about which events I want to handle in this controller. It also allows me to setup multiple, unique listeners across controllers so that “button clicks” don’t conflict. Alternatively, if there are events that I’m interested in for the same components across multiple controllers, I can do that as well. The point is that the component event domain allows for dynamic, complex selectors which provides an incredible amount of flexibility.
Ultimately, this is along the lines of what I needed for my custom router event domain.
A Simple Solution
Fortunately, there is a very simple way to get this to work. The solution is completely out of the box and requires absolutely no overrides or other modifications to the core Ext JS library.
If you look at the source code, you’ll quickly see how Ext JS accomplishes controller listener matching with event domains. It goes something like this:
- Monitor all registered domain
- Loop over all controller listener selectors within that domain
- Run match() to compare the idProperty of the monitored class to the current controller listener selector
The last part is the most important bit. To understand how this fits together, let’s take a look at the custom Proxy Event Domain I created:
Ext.define('CarTracker.domain.Proxy', { extend: 'Ext.app.EventDomain', singleton: true, requires: [ 'Ext.data.proxy.Server' ], type: 'proxy', idProperty: 'type', constructor: function() { var me = this; me.callParent(); me.monitor( Ext.data.proxy.Server ); } });
Notice that the “idProperty” is set to “type”. In the case of proxies, each has a unique idProperty–e.g., “rest”, “ajax”, etc. So based on this, the match() method would compare the idProperty of the monitored class (“rest”) with the event domain selector (“rest”). Obviously, this still suffers from the “simple” selector problem I mentioned above, but illustrates how the match() method works.
But now that we know this, the way forward is very obvious. Since the only thing that match() is doing is comparing the value of some property in our monitored class to the controller listener selector, literally the only thing we have to do to have complex, dynamic controller listener selector matching is set an idProperty that matches a dynamic property in our monitored class! In other words, all that’s needed in our monitored Router class is to add a property (I called mine “activeRoute”) and make sure to update it with the correct value *before* our class fires any events. Once we do this, we can now add complex selectors to our controllers to match on more granular selectors:
Ext.define('CustomController', { extend: 'Ext.app.Controller', init: function() { this.listen({ ... router: { '#router path/subpath/:param': { dispatch: this.callSomeControllerMethod, addroute: this.callAddRouteMethod }, '#router anotherpath/anothersubpath': { addroute: this.callAnotherControllerMethod } }, ... }) } })
Wrapping Up
As we’ve seen, not only is creating a custom event domain super simple (even for custom, non-framework classes), but you can also easily configure your custom event domain to interact with your monitored class to match on complex, dynamic selectors, allowing for a huge amount of flexibility in how you setup event listeners in your controllers.
I hope this is helpful to others, and as always, I welcome any feedback or suggestions that you have for how to make this better. Thanks for reading!
Print article | This entry was posted by existdissolve on December 15, 2013 at 10:04 am, and is filed under ExtJS, JavaScript. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 10 years ago
Hi,
Do you have a working example or fiddle I can look at ?
Thanks,
Dave