In ExtJS 4, you can do some pretty powerful stuff inside your controllers using ComponentQuery. In the following example, I have a simple grid panel view, and I’m using ComponentQuery in my controller to handle itemcontextmenu events within the grid:

views/Bookmarks.js

Ext.define("ME.view.bookmarks.List", {
   extend: "Ext.grid.Panel",
   alias: "widget.bookmarks",
   store: "Bookmarks",
   scroll: "vertical",
   title: "My Bookmarks",
   initComponent: function() {
      this.columns = [
         {
            text: "Bookmark",
            dataIndex: "title",
            flex: 1,
            renderer: function(value,meta) {
               meta.tdAttr = 'data-qtip="' + value + '"';
               return value;
            }
         },
         {
            text: "Created",
            dataIndex: "created",
            xtype: "datecolumn",
            format: "m-d-y",
            hidden: true
         }
      ];
      this.callParent(arguments);
   }
});

controllers/Bookmarks.js

Ext.define("ME.controller.Bookmarks", {
   extend: "Ext.app.Controller",
   stores: ["Bookmarks"],
   models: ["Bookmark"],
   views:  ["bookmarks.List"],
   init: function() {
       this.control({
           "bookmarks": {
              itemcontextmenu: function(view,record,item,index,e) {
                 var me = this;
                 var r  = record.data;
                 e.stopEvent();
                 if (!item.ctxMenu) {
                    item.ctxMenu = new Ext.menu.Menu({
                       items : [
                          {
                              text: "Delete Forever",
                              icon: "images/delete.png",
                              handler:function(btn) {
                                 me.deletebookmark(record,item);
                              }
                          }
                       ],
                       defaultAlign: "tl"
                    });
                 }
                 item.ctxMenu.showBy(item);
              }
           }
      });
   }
});

First things first. You’ll notice this bit in the view:

alias: "widget.bookmarks"

This allows me to define my class as a custom xtype. This is important because you can use ComponentQuery to select components by xtype. With this in mind, I can now use the alias of my view (“bookmarks”) as a selector in the “control” section of my Controller.

this.control({
   "bookmarks": {
      ...events for this xtype...
   }
});

By using “bookmarks” in ComponentQuery, we can listen on any of the events defined for this xtype. Since this particular xtype is an extension of a GridPanel, all the GridPanel events will be available.

Digging Deeper

This approach is pretty straightforward when your ComponentQuery is dealing with top-level events of a single, common xtype. Where it gets a little trickier–and more powerful–is when you use ComponentQuery to drill down into child components.

What do I mean by this? Consider that I’ve defined a number of columns with headings for my grid panel, and that I want to do some custom action when a grid column’s visibility is changed. I could, of course, try to set up some custom listener in the view. While that would certainly work, it breaks my MVC pattern quite a bit, since I now have some events being handled inline in my view (column heading actions) and others in my controller (itemcontextmenu actions). It would be much better if I could handle this in my controller as well.

Thanks to ComponentQuery, this is a snap. Remember, we already have a custom xtype for the GridPanel: “bookmarks.” Since ComponentQuery allows us to not only select xtypes, but children of xtypes as well, we can easily get at the column headings of our grid panel.

"bookmarks headercontainer": {
    columnhide: function(ct,col,opts) {
       ...do my columny stuff
    }
}

In this example, we begin the ComponentQuery with our custom xtype selector “bookmarks” as before, but then append “headercontainer” to it. This tells ComponentQuery to retrieve all the xtypes of “headercontainer” (a default xtype) that are within our “bookmarks” xtype. Now that these are selected, we can do the same as above, setting up listeners on all of the events that belong to the “headercontainer” xtype.

Finding XTypes

Now you might wondering, “how the heck do I find the xtype selectors?” This is a good question. When I first started with ComponentQuery, I was using the documentation page for Component. While this has a lot of the common xtypes, it does not list *all* of them (“headercontainer” being a prime example).

So where can you find these? It’s so obvious that I overlooked it for the longest time. All you have to do is go to the documentation page for the class. Next to the giant green class name at the top of the page, you’ll see the xtype in smaller, light grey text to the right of it.

A Word of Caution

As you can see, ComponentQuery is incredibly powerful, especially when used inside controllers to bind views and their events together. It is important, however, to remember to be as specific as possible when using ComponentQuery, since it will return *everything* that matches your selectors, whether or not you expect it to.

For example, let’s say that I did this for listening for events when GridPanel columns are hidden:

"headercontainer": {
    columnhide: function(ct,col,opts) {
       ...do my columny stuff
    }
}

While it will certainly give me the functionality I want, it will also apply the same to any other grid columns that might exist in my application. While I might actually want that, chances are that I will want to be a bit more granular about it, so using the previous example (“bookmarks headercontainer”) would be a much better practice.

Another example:

"bookmarks *": {
    click: function(el,e,opts) {
       ...do my clicky stuff
    }
}

In this case, I’ve been specific (good) about the component within which I want to listen for events, but I’ve used a wildcard (bad) to listen for a click event. As above, any proper click event (buttons, menu items, etc.) will trigger this, so you may receive unexpected behavior. Again, while this might initially seem beneficial (“hey, I can control ALL my buttons from one central place!!”), it will become increasingly unmanageable as the size and complexity of your application increases.

The moral of the story? Be explicit about your selectors so that you can tap into the events–and ONLY the events–that you want :)