Skip to content
Titanium SDK

Controllers

Written for Titanium SDK 13.4.0 and later

A controller is the JavaScript beside a view. Everything with an id in the XML is on $, and the file runs when the controller is created.

// app/controllers/index.js
function doGo() {
  $.greeting.text = 'Going';
}

$.index.open();

$.index is the view's top-level element, named after the controller rather than after anything in the XML. $.greeting is the label with id="greeting".

doGo is reachable from the view as onClick="doGo" because it is a top-level function in this file. A function assigned to a variable is not.

Arguments

Alloy.createController('detail', { id: 42 }) passes an object, and the controller reads it from $.args:

// app/controllers/detail.js
const noteId = $.args.id;

$.title.text = `Note ${noteId}`;

$.args is also what a <Require> passes down, so an embedded controller takes arguments the same way.

Read $.args at the top of the file. It is the controller's signature, and scattering the reads makes it invisible.

Opening a window

A controller is not a window. Alloy.createController builds it; opening is still your call:

const detail = Alloy.createController('detail', { id: 42 });
detail.getView().open();

getView() returns the top-level element - the same object as $.index inside that controller.

For a controller that owns its own window, export an open:

// app/controllers/detail.js
$.index.open();

exports.open = () => $.index.open();

Anything assigned to exports in a controller is on the object createController returned.

Cleaning up

Alloy does not free a controller for you. Listeners you added to anything outside the controller, timers, and geolocation updates all outlive the window unless you remove them.

function onLocation(e) {
  /* ... */
}

Ti.Geolocation.addEventListener('location', onLocation);

$.index.addEventListener('close', () => {
  Ti.Geolocation.removeEventListener('location', onLocation);
  $.destroy();
});

$.destroy() removes the bindings Alloy created between models and views. It does not remove listeners you added, which is why both lines are there.

A controller created and dropped without either is the usual cause of a slow leak in a long session.

Where shared code goes

app/lib/ is plain CommonJS, required by path:

const format = require('/format');

The leading slash is against the app root, not the filesystem. Anything in lib is available to every controller and to widgets.

alloy.js runs once, before the first controller, and is where Alloy.Globals is set:

// app/alloy.js
Alloy.Globals.apiRoot = 'https://example.com/api';

Next

Models and collections covers the data layer and binding it to a list.

Edit this page on GitHubLast updated Nov 9, 2026