API Development

Supercharging Alloy Controllers

Developing Titanium apps with Alloy means following a Model View Controller (MVC) pattern — views are XML-based, Controllers contain JavaScript code and data is handled using Models and Collections.

In this post, I’ll talk about how you can enhance Alloy, by customising the function that creates Controllers — so you can add additional event handling, custom methods & properties, write less code, and speed up cross-platform app development.

Typically in Alloy, you create an instance of a controller by doing the following:

 var myController = Alloy.createController("hello"); 

This populates myController with an instance of the controller — you can interact with elements within the view that have an id property. For example, if the View had a Label called myLabel you could change it’s value like this:

 myController.myLabel.text = "Hello to you!"; 

If you want to open the Window, or interact with the View like a classic Titanium component, you would use the getView() method — in this example we’re using it to return the View itself, and then using the open() method:

 myController.getView().open(); 

When you create a controller, you’re not dealing with a classic Titanium Window / View etc — you’re dealing with an Alloy controller object which contains the “classic” View.

One thing you can’t do in a controller is know when the view is opened since the controller code runs the moment it’s created — the only way to do that is use the getView() method to access the View, and then add event listeners to it, like this:

 var myController = Alloy.createController("settings"); 
 myController.getView().addEventListener("open", function(){ 
  alert("opened!"); 
 }); myController.getView().open(); 

Doing this you now have a Controller and View created and you can execute some code when the View is opened. The issue here is there’s quite a bit of code just to trap an open event, and you’re creating variable pointers to the controller that aren’t used after it’s opened.

There is another way

I’ve already mentioned that Alloy.createController takes care of creating a controller — so let’s override this method, make it do what it’s normally does but add some special sauce to make it even better.

This is what the Alloy.createController method normally does:

 Alloy.createController = function(name, args){ 
   return new(require("alloy/controllers/" + name))(args); 
 } 

Yep, a controller is treated like any CommonJS module — the only difference here is that it’s created with the new keyword, so that we don’t get sharing between controllers — each one is a new instance. Typically, you might create a controller, open the view either standalone or part of a navigation stack (such as a Navigation Window or TabGroup on iOS), and in doing that you have to manage closing the view.

A Navigation Window, TabGroup, back button or the built-in back button on Android will all do that for you — but you could also add your own code to close a view like this:

 function close(){ 
  $.getView().close(); 
  $.off(); 
  $.destroy(); 
 } 

In this example, we’re creating a function we can associate with an event handler or Alloy event — we’re closing the view, turning off any controller triggers and cleaning up models / collections.

But this only works if you run that function — if the Window is closed by the Android back button, or the default back button on a TabGroup or Navigation Window, this won’t run — to do that you’d need to trap the “close” event on the Window:

 $.getView().addEventListener("close", close); 

or, add and event handler in the XML View.

This is fine and works — but it’s more code and more duplication of code and more importantly, if you forget to do this somewhere you could end up with memory leaks.

Let’s look at a solution where we go right to the source — to the Alloy.createController method itself and put everything we need in place there, so there’s no need to have custom close / cleanup event handlers or lots of controls in each controller.

Firstly, we’re going to override the createController method — so add this to alloy.js:

Alloy.createController = function(name, args) {
 var controller = new(require("alloy/controllers/" + name))(args);
 var view = controller.getView();

 // add a once event handler
 controller.once = function(name, callback){
 controller.on(name, function(){
  controller.off(name);
  callback();
 });
 return controller;
}

// fire an open event on open
view.addEventListener(“open”, function open(e){
 view.removeEventListener("open", open);
 controller.trigger(“open”, e);
 console.log(“View/Controller “ + name + “ was opened”);
});

// fully clean up the view and controller on close
view.addEventListener(“close”, function close(e){
 view.removeEventListener("close", close);
 view = null;
 controller.off();
 controller.destroy();
 controller = null;
 console.log(“View/Controller “ + name + “ was cleaned up!”);
});
}

We’re doing a few things here:

  1. we’re keeping the same pattern as the normal createController method — so there’s no need to change any code elsewhere — this is a simple thing to add to any project
  2. we’re console logging the fact we created the controller — handy for debugging
  3. we’re adding open and close events to the view AND triggering an event on the controller itself (and removing them once fired).
  4. we’re using the close to clean up, remove events and destroy models etc
  5. we’re adding a new .once event handler that can react to an event once and then ignore it.

With this simple override function in place we can now do stuff like this:

 Alloy.createController("settings").on("open", function(){
   alert("Opened!"); 
 }).getView().open();

So let’s look at what we’ve just done here. Firstly, notice that we haven’t created a pointer to the controller — there is no variable left.

Secondly, we’re responding to events that don’t exist in a normal controller, and even using a new “once” handler that self-destructs after being used. It’ll never fire again.

Finally, the other thing to note is that you can *chain* the events just like you can with controllers now. So, an app you built to do a login screen could do this:

 Alloy.createController("login").once("open", function(){
  // handle the open event 
 }).on("forgot", function(){ 
  // handle forgot password 
 }).on("submit", function(){ 
  // handle login process 
 }).getView().open(); 

Now, think about what more you could do here — how about adding tracking, analytics or more logging to the process? — you can do all this in the same place, the createController method — which means no more tracking code in individual controllers and no more forgetting to add it, because it’ll be added based on the name of the controller you’re creating!

If you wanted to do more, you could also have a callback event that could be fired on the close handler, so you could pass back the close event to your main app, run some additional code and then continue or not, allowing it to clean up itself after you’ve intercepted it.

Overriding the Alloy.createController is a really simple way to put repeating functionality and logic in one place and reduce the amount of code you’re adding into each controller.

I’ve created a repo for an enhanced version of this code over at https://github.com/jasonkneen/AlloyXL and will be posting a video soon talking through all this and showing some demos of how to use this.

Update: As pointed out in one of the comments, the example code above is relevant to using a Window as a controller, but can be adapted to ignore the open / close events if the controller / view isn’t a Window. See the AlloyXL repo above for a more enhanced version that accommodates other controllers / views.