View calling controller image

How to Listen to Events with Controllers in Sencha Touch



·

Creative Commons: Image modified from Ognian Mladenov

Handling events in Sencha Touch was one of the more confusing aspects for me – it requires some knowledge of the MVC (Model-View-Controller) architecture to understand why we build things the way we do in Sencha Touch.

In this post I’m going to try to explain how to trigger and listen for an event in Sencha Touch in the simplest way I can. If you take a look at a common controller setup, it will probably look something like the following:

Ext.define('MyApp.controller.App', {
	extend: 'Ext.app.Controller',
	config: {
		refs: {
			main: 'main'		
		},
		control: {		
			main: {
				back: 'onMainBackButtonTap'
			},
		}
	},

    onMainBackButtonTap: function(btn){
        var me = this,
            activeItem = me.getActiveItem();
            
        me.fireEvent('back', me, activeItem, btn);
    }

});

Under the config options we have ‘refs’ and ‘control’. Everything listed under ‘refs’ are the views we want to listen to events for. Under ‘control’ we specify each view and what we want to do when we hear specific events from that view. In the above example we’re listening on the ‘main’ view for the ‘back’ event and when we hear it we want to execute the ‘onMainBackButtonTap’ event. For reference, I have also included the ‘onMainBackButtonTap’ function in the code above – the functions specified in the ‘control’ can be listed after the config options.

Now we’ll have some view that is going to trigger some event that will then trigger some function in the controller. Take the following code for example:

Ext.define('MyApp.view.Main', {
    extend: 'Ext.Container',
    requires: ['MyApp.view.SomeView'],
    xtype: 'main',
    config: {
        layout: 'card',
        control: {
            'button[ui=back]': {
                tap: 'onMainBackButtonTap'
            }
        },
        items: [
            {
                xtype: 'toolbar',
                docked: 'top',
                title: 'MyApp',
                items: [
                    {
                        xtype: 'button',
                        ui: 'back',
                        text: 'Back'
                    }
                ]        
             },
        ],

    },

    onMainBackButtonTap: function(btn){
        var me = this,
            activeItem = me.getActiveItem();
            
        me.fireEvent('back', me, activeItem, btn);
    }

});

The goal of this view is to trigger the ‘onMainBackButtonTap’ function we listed in the controller above when the button is tapped. So of course, we add the button:

items: [
    {
        xtype: 'button',
        ui: 'back',
        text: 'Back'
    }
]        

and we’re also going to need to fire an event when that button is tapped. So we add a function to do that:

onMainBackButtonTap: function(btn){
    var me = this,
        activeItem = me.getActiveItem();
        
    me.fireEvent('back', me, activeItem, btn);
}

This will fire an event called ‘back’. Now we just need to trigger that function when the button is tapped. To do this, we can add a ‘control’ to the view:

control: {
    'button[ui=back]': {
        tap: 'onMainBackButtonTap'
    }
},

Similarly to how we can add a ‘control’ to our controller for each view, we can also create a ‘control’ in the view itself. You will notice in the above view that I’m not listening for the name of a specific event, but rather an event from a button with a ‘ui’ of ‘back’. Rather than specifying the ui on the button, this will also work if you change it to another parameter like the ‘id’ of the back button if it is set for example:

control: {
    'button[id=theButtonsId]': {
        tap: 'onMainBackButtonTap'
    }
},

As well as creating a ‘control’ in the view, we could also just specify a handler on the back button, and have that trigger the function:

items: [
    {
        xtype: 'button',
        ui: 'back',
        text: 'Back',
        handler: this.onMainBackButtonTap,
        scope: this
    }
]       

Why not just have the code directly in the view, instead of creating a function that has the sole purpose of calling another function? You could do exactly this if you wanted to, but it’s not really following best practices. Sencha Touch implements an MVC (Model-View-Controller) architecture, and one of the primary purposes of the MVC architecture is to separate logic from presentation. For simple applications you may get away with cutting some corners, but before long you may find that your application becomes a jumbled mess that is unmaintainable. So in most cases, it’s best to pass these events to our controller.

Now that the function has been triggered, the ‘fireEvent’ function will fire an event called ‘back’. Since our controller is listening for an event called ‘back’ from this view it will run the ‘onMainBackButtonTap’ function in the controller. I’d also like to point out that it may be confusing that both the function in the view and the function in the controller are called ‘onMainBackButtonTap’, this is not necessary.

It may seem like a bit of work and jumping around the place to execute a little bit of code in the controller, and it may be tempting to just place the code directly in a listener function on the button. This will lead to terribly messy code though with low re-usability. One obvious advantage of doing it this way is we can now simply fire the ‘back’ event from any view, and as long as we add a ‘ref’ and ‘control’ for it in the controller, we can reuse the same ‘onMainBackButtonTap’ code.

What to watch next...