Event listeners in Sencha Touch 2, the Ext JS approach

I have been developing Ext JS for a long time now and recently I have started with Sencha Touch 2. And while I am at it, I have the feeling that the Ext JS team and the Touch team are lately not in the same building.

When you only have experience with Sencha Touch maybe this article is not for you. When you only have experience with Sencha Touch you don’t know any different. But personally I like to see the good parts from Ext JS in Sencha Touch.

I know that you can build classes like in Ext JS. But since I also use Sencha Architect, I like to keep my classes syntax close to the Architect. I am using Architect mostly for prototyping and not as program generator.

Event listening the Ext JS way

In Ext JS a listener to a button looks like this:

1

2

3

4

5

6

7

8

9

10

items:[

{

xtype:'button',

text:'Nice Label',

scope:this,

handler:function(b,e){

this.onNiceButtonPressed();

}

}

]

The trick is in the scope. The scope is on this, the main object where this button is part of, like a panel or a toolbar. So calling this.onNiceButtonPressed, is simply a function within this object.

Leaving the scope out, it would have the scope on the button. It works the same with listeners.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

items:[

{

xtype:'button',

text:'Nice Label',

listeners:{

scope:this,

'click':function(b,e){

this.onNiceButtonPressed();

},

'anotherevent':function(b,e){

...

}

}

}

]

The listeners are calling functions within the object (this) which also can be called by other objects (public). This is helpful if you are using controllers that also have to call these functions. Such a function could be reloading a store, what could happen by pressing a button or as an additional action with a change of a another panel listened by the controller.

Event listening Sencha Touch 2 way

What I like about the Ext JS approach is that I have my listeners always close to the actual component. But Sencha Touch is different. It works with listeners on the main object that delegate to the component where you want to add a listener to. This sounds confusing but it looks like this:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

Ext.define('Mobile.view.MyFormPanel',{

extend:'Ext.form.Panel',

alias:'widget.myformpanel',

config:{

items:[

.....formitems....

{

xtype:'toolbar',

docked:'top',

layout:{

align:'start',

pack:'center',

type:'hbox'

},

items:[

{

xtype:'button',

itemId:'somebutton',

text:'Button 1'

},

{

xtype:'button',

docked:'right',

itemId:'anotherbutton',

text:'Button 2'

}

]

}

],

listeners:[

{

fn:'onSomeButtonTap',

event:'tap',

delegate:'#somebutton'

},

{

fn:'onAnotherButtonTap',

event:'tap',

delegate:'#anotherbutton'

}

]

},

onSomeButtonTap:function(button,e,eOpts){

varme=this;

me.fireEvent('SomeButtonClicked');

},

onAnotherButtonTap:function(button,e,eOpts){

varme=this;

me.fireEvent('AnotherButtonClicked');

}

});

What you see above is that the listeners are all put below the config in listeners array. In this sample I want to create the simple result of firing events on the main object (in Ext JS the this object), that I listen for in a controller. I don’t like it when my listeners are "drifted" away from the actual component.

Bad according to Sencha

Sencha says that it is bad to put your listeners at config time. Nevertheless I am doing it, to get closer to what I am used to with Ext JS. The result is not so gently as Ext JS, but close enough.

Item configuration

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

items:[

{

xtype:'button',

docked:'right',

text:'Button 1',

itemId:'btnButton1',

listeners:{

tap:function(button,e,eOpts){

varme=button.up('WidgetName');

me.fireEvent('Button1Clicked',button,e);

}

}

},

{

xtype:'button',

text:'Button 2',

listeners:{

tap:function(button,e,eOpts){

varme=button.up('WidgetName');

me.fireEvent('Button2Clicked',button,e);

}

}

}

}

]

What I am doing is simply use the up method on the button to get to the object where I want to fire my event on. I am using the widget name of the object that I want. The up function works fine, because the component is always subordinate to the main object I choose. You don’t have to use the itemId on the panel for this.

Controller

The controller has the following event listeners:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

config:{

refs:{

'WidgetName':'WidgetName'

},

control:{

'WidgetName':{

'Button1Clicked':'onButton1Clicked',

'Button2Clicked':'onButton2Clicked'

}

}

},

onButton1Clicked:function(b,e){

..dosomethinghere..

},

onButton2Clicked:function(b,e){

..doanotherthinghere..

},

....

Adding events at initialization

It is also possible to add new events at initialization of the object. For this you add the initialize() function to the definition of your class.

1

2

3

4

5

6

7

8

initialize:function(){

varme=this;

me.callParent();

me.down('#btnButton1').on('tap',function(b,e){

console.log('button 1 tapped');

},me);

},

Who is firing and who is listening

I don’t like it when I have to browse through a lot of different sources to find related functions. I like to share a couple of rules that apply when I design applications.

Controllers manage more objects

Controllers manages more objects – only a controller may trigger functions in other objects. So when action in object A leads to a change in object B, it will be handled by a controller. Object B doesn’t listen to events of object A. The controller handles this.

Objects can take care of themselves

All functions unique for an object are handled by that object. When in a dataview a searchfield is changed, the dataview object will handle this event and updates the store. There is no request going to a controller. I use "exit" events that are fired when an action is completed. For example when an Ajax call is successfully completed I could fire and event like "ArticleStoreLoaded".

Supply fire events with enough parameters

When firing an event, add the right parameters. It is annoying when the event is listened for and the function executed has to look up all the information that was available at firing time.

Controllers listen to events

I never put listeners in controllers to components in other objects. A controller listens only to events that have been fired by other objects.

Separate classes

Separate all panels lists and dataviews in different view classes to keep the sources small. That doesn’t count for toolbars.

Fire events on the main object

Fire all events on the parent (Ext JS this object) to limit my references in the controller.

Stick to the framework

Stick to the rules of the original framework. Exceptions are most of the time the reason of bugs when an upgrade is installed.

Good software is all about reliability, stability and performance, less about the syntax

Related posts:

Dedicated to professional software development since 1985. Has worked since 1992 as IT manager in several international operating companies. Since 2007 CEO and Sencha Ext JS web application developer at Enovision GmbH.