Objective
In this chapter, you will learn how to capture and fire events. You'll also learn the difference between component level and app level events.
Contents
Events are actions that can be detected by JavaScript. Such actions might
be user-initiated, like taps or swipes, or system-initiated, like when
an app is paused. Your app can capture and react to any of these events.
You specify which components in your app should "listen" for
events with the addEventListener()
function. Assigning an event listener follows this pattern:
1
2
3
4
5
|
element.addEventListener( 'event_type' , function (e) { // code here is run when the event is fired // properties of the event object 'e' describe the event and object that
received it Ti.API.info( 'The ' +e.type+ ' event happened' ); }); |
As seen above, the first parameter specifies the type of event for which
we are listening. In this case, event_type
on the element
component. The second parameter is a callback function that will be executed
whenever the event is fired.
Each Titanium component is capable of listening for a particular subset
of event types. As shown in the code sample above, within an event listener's
function, an event object, e
, is created. The properties of that object vary by component and event
type. These are listed on the API docs page for any given module or component.
Some common event types include:
click
and its synonymsingletap
swipe
which is a left or right touch and drag the finger actionscroll
which is a touch and drag actiondblclick
and its synonymdoubletap
touchstart
,touchmove
, andtouchend
which define the three stages of an action where the user touches the screen, moves his or her finger in an arbitrary direction, then lifts the finger
In addition to the common event types, some modules have their own special
events. For example, the location services API includes events for heading
and location
changes. The gestures module enables you to listen for the shake
event. See the API docs for more information.
Common event object properties include:
x
andy
which describe the x/y coordinates of the event (such as a tap) in the view's coordinatesglobalPoint
which is a dictionary withx
andy
properties describing the event's location in screen coordinatestype
which is the name of the event typesource
which is a reference to the object on which the event was fired
You can assign more than one event listener to a component. For example, a table could listen for taps, swipes, and scrolls.
While the general form above uses an anonymous (unnamed) function, you can use a named function as well. Typically, you would do so if you wanted to reuse the function in more than one event handler or in response to more than one type of event. Let's say you had two buttons that when clicked would perform identical operations. Rather than writing duplicate anonymous functions for each button, you could create a named function that is called instead.
1
2
3
4
5
6
7
8
|
function doSomething(e) { // This function will be called by multiple handlers // The event object is accessible within this function Ti.API.info( 'The ' +e.type+ ' event happened' ); } button1.addEventListener( 'click' , doSomething); button2.addEventListener( 'click' , doSomething) |
Tip: A UI element must have its touchEnabled
property set to true
for it to react to touch-related events (click, singletap, etc.). In most
cases, UI components default to this value being true
. However, if you find an element that won't respond to an event, try setting
touchEnabled=true
to see if that helps. If a view has touchEnabled
set to false
, touch events pass to the next view in the stack (for example, an underlying
sibling view or parent view).
UI event bubbling
UI events can bubble up from the view that was actually touched through
parent views (objects that inherit from Ti.UI.View
), as well as certain "view-like" objects which act as containers
for views.
For example, a TableViewSection
object acts as a container for TableViewRow
objects. However, in iOS, the TableViewSection
is not itself a view. Since it serves as a logical container for views,
it takes part in the bubbling chain. From the point of view of event bubbling,
the section acts as parent to the rows it contains, and the table acts
as parent for its rows.
Some special containers, such as windows, have no parents in the view hierarchy, so event bubbling ends when it reaches a special container. These special containers include:
- NavigationWindow
- SplitWindow
- Tab
- TabGroup
- Window
Event bubbling ends when it reaches the special containers.
Bubbling and non-bubbling events
Events that represent user input (click, touchmove, swipe) are bubbling events. Other events, such as focus and scroll, are view-specific. They represent views reacting to user input, and they do not bubble.
The following events bubble:
- click
- dblclick
- doubletap
- longclick
- longpress
- pinch
- singletap
- swipe
- touchcancel
- touchend
- touchmove
- touchstart
- twofingertap
That is, all of the events defined by Titanium.UI.View
except postlayout
.
Managing event bubbling
Release 3.0 adds several features to allow more control over event bubbling.
The Titanium.Event
object has two new properties:
API |
Type |
Description |
---|---|---|
|
boolean |
Indicates whether the event can bubble. This property is set at creation
time and cannot be changed. For events fired by the Titanium SDK, this
property is set to ensure that event-bubbling behavior matches the 2.X
event bubbling behavior. |
|
boolean |
If |
In addition, all views and view-like objects have a new bubbleParent
property, which determines whether the view bubbles to its parent. This
property is read/write. By default, bubbleParent
is set so that event bubbling behavior for a given view matches its Release
2.X event bubbling behavior. This property is defined as a property of
Titanium.Proxy
--the parent of all Titanium objects. However, the property is only used
on views and view-like objects that act as containers for views.
Event bubbling notes
- Event bubbling happens after native event handling. That is, native event handling (such as highlighting a view) has already happened before any event fires.
- Multiple events of different types are treated separately. That is, if
the user lifting their finger triggers 'touchend', 'singletap', and 'click'
events, any actions done on the 'touchend' event (such as setting
cancelBubble
) do not affect the bubbling or firing of either 'singletap' or 'click' events.
Firing events
You can fire events rather than waiting around for the user or system to
initiate them. For example, you could simulate a button press by firing
that button's click
event. You'd use code like this:
someButton.fireEvent( 'click' ); |
or if you wanted to pass data along with the event:
someButton.fireEvent( 'click' , {kitchen: 'sink' }); |
As shown, you can also pass JSON-serializable data when firing events.
You simply include an object (without methods) as the second parameter
of the fireEvent()
function. That parameter is optional and can be omitted. Members of the
passed object become available via the event object in the listener. For
example, the following code:
someButton.addEventListener( 'click' , function (e){ Ti.APP.info( 'The value of kitchen is ' +e.kitchen); }); |
would output this to the Titanium console:
[INFO] The value of kitchen is sink |
Custom events
Manually firing a click event demonstrates the flexibility of Titanium's event system, but it's probably not something you'll do that often. You can (and probably will) fire your own custom events frequently. For example, you might fire a custom event when when the database is updated. Any components that depend on the database – a table, for example – could listen for that event and update themselves. This promotes loose coupling of our components and makes for more scalable and maintainable Javascript code.
1
2
3
4
5
6
7
8
9
10
|
deleteButton.addEventListener( 'click' , function (e){ // when something happens in your app database.doDelete(e.whichRecord); // fire an event so components can react theTable.fireEvent( 'db_updated' ); }); // ... elsewhere in your code theTable.addEventListener( 'db_updated' , function (e){ theTable.setData(database.getCurrentRecords()); }); |
An event fired and listened for in this manner is associated with one particular component. Sometimes you'll want more than one component to be able to react to custom events. You could fire a whole bunch of component-level events. Or, you can use application-level events.
Application-level events
App-level events are global to your app. They are accessible in all contexts,
functional scopes, CommonJS modules, and so forth. You fire them and listen
for them via the Ti.App
module. They're tailor made for sending custom events across contexts.
Updating the previous code sample to use app-level events gets us this:
1
2
3
4
5
6
7
8
9
10
11
|
deleteButton.addEventListener( 'click' , function (e){ // when something happens in your app database.doDelete(e.whichRecord); // fire a global event so components can react Ti.App.fireEvent( 'db_updated' ); }); // ... elsewhere in your code Ti.App.addEventListener( 'db_updated' , function (e){ theTable.setData(database.getCurrentRecords()); someOtherComponent.doSomethingElse(); }); |
Keep in mind that app-level events are global, which means they remain in context the entire time your app is running (unless you remove them). This also means that any objects they reference will also remain in scope while your app runs. This could prevent those objects from being garbage collected. See the Managing Memory and Finding Leaks chapter for more information.
Removing event listeners
You can remove an event listener, thereby preventing the associated component from reacting to the event in the future. Let's say you have a delete button that should only be active when one or more items are checked. When the users checks the first item, you could add the click event listener to the delete button. If the user clears the last of the check marks, you'd remove the event listener from the delete button.
To remove an event listener, you have to pass a reference to the function
that was specified when you added the event listener. In other words, the
function signature passed to addEventListener()
and removeEventListener()
must match. The easiest way to do this is to use a named function in the
addEventListener()
statement so you can also pass that same function name to remove the listener.
1
2
3
4
5
6
7
|
function doSomething(e) { // do something } deleteButton.addEventListener( 'click' , doSomething); // ... elsewhere in your code ... deleteButton.removeEventListener( 'click' , doSomething); }); |
Special events
Android devices feature various "hardware" buttons, like Back, Home, Search, and Menu in addition to the volume up/down rocker and the camera button. With Titanium, you can monitor each of these buttons for presses with event listeners added to a heavy weight window.
Event |
Fired when ... |
---|---|
|
The back button is released |
|
The home button is released |
|
The search button is released |
|
The camera button is released |
|
Fired when the camera button is pressed halfway and then released. |
|
The volume-up rocker button is released |
|
The volume-down rocker button is released |
See the Window reference page for more information on these special events. The Menu button is handled specially as an event fired against the Android activity. See this Kitchen Sink example for sample code for creating and displaying a menu.
Considerations and best practices
Event listeners must be defined before their respective events are likely to be fired, otherwise they are not guaranteed to be called. For example, a window's open and focus listeners must be defined before the window is opened. Evaluating events as late as possible while bearing the above consideration in mind, however, can significantly improve application responsiveness. For example, an event listener for a click event may be defined after the parent window has been opened.
Global event listeners, such as the Ti.App events described above or the Geolocation or Gesture listeners, remain in scope the entire time your app is running. This means that any local variables you reference within those listeners will remain in scope while your app is running. This can cause memory leaks – objects you no longer need and think have been destroyed remain in memory. See the Managing Memory and Finding Leaks chapter for more information. The gist of it is that you must remove global event listeners when you're done with them. When a window closes, for example, you might no longer need to listen for a certain global event; remove it.
Hands-on practice
Goal
In this activity, you will fire and listen for events to test how events propagate – whether they bubble up or trickle down. You will also fire and listen for an app-level event.
Steps
Determine if events bubble up or trickle down and do the platforms match...put a button in a view in a window then put a click listener on all of them and see the order in which they're fired. Perhaps also compare the x/y and globalPoint coordinates to see how they differ.
- Create a new Titanium project named Events
- Remove all of the code from app.js. Write new code to define the following
elements:
- A window with a white background color. Add a custom property named
whichObj
with the value 'window' to your window object. - A view that fills 80% of the width and height of the window. It should
have a yellow background. Add a custom property named
whichObj
with the value 'view' to your view object. Make sure to add the view to the window. - A button centered vertically and horizontally within the view. Add a custom
property named
whichObj
with the value 'button' to your button object. Make sure to add the button to the view.
- A window with a white background color. Add a custom property named
- Add a click event listener on the window object. It should log two messages
to the console:
- Message 1: Simply output the word 'window' to the log so you know the window received the click event
- Message 2: Log the
whichObj
property value to the console so you know which object actually received the user's click. Keep in mind the event object properties that you'll need to reference to access this property.
- Add a click event listener to the view object. It should log the word 'view' to the console so you know the view received the click event.
- Add a click event listener to the button. It should take two actions:
- It should print the word 'button' to the log so you know the button received the click event.
- It should fire an app-level event named
changeBG
and pass an anonymous object defining a color.
- Add an app-level event listener which watches for the
changeBG
event. When received, it should update the view's background color with the value passed via the event object. - Build and test the app by clicking the button. Do events bubble up (button receives the click first, then the view, then the window) or do events trickle down (window, then view, then button)? If you can build for both Android and iOS, compare the output on the two platforms. Do they react the same?
References and further reading
Summary
In this section, you learned how to capture and fire events. You learned the difference between component level and app-level events, as well as how and when to use them. Finally, you tested event propagation to determine that on both platforms, events bubble up though only a single event object is created and passed among the interested event listeners. Next we'll move on to handling one of the most used UI components in today's apps: the TableView.