API Development

Using Base Controllers With Alloy

There are plenty of ways to re-use components with Alloy: widgets, CommonJS, and even redefining or creating new Alloy tags. One way that’s not often mentioned is base controllers.

The easiest way to think about a base controller is to think of it like a kind of inheritance.

If you’re finding that you’re repeating a lot of code in a controller – maybe creating elements or validating values of controls – you can move this kind of functionality into a single base controller and then inherit this into other controllers that can then use these methods and properties.

To demonstrate how base controllers can help, let’s assume I have an iOS application that has a series of subscreens that are selected from a slide-in menu with each using a NavigationWindow as its root view.

I might have have a Settings or Register screen, and I want each main window to open using a Navigation Window, so that any subwindows can be opened from that.

So, in my setting screen, I might have a TableView that contains some options the user can select from, and when they select one, I want to open a subwindow that will slide in.

Without using base controllers, my settings.xml file might look like this:

<Alloy>
<NavigationWindow>
<Window title="Settings">
<TableView>
<TableViewRow title="My Details"/>
<TableViewRow title="Push Notifications"/>
<TableViewRow title="Logout"/>
</TableView>
</Window>
</NavigationWindow>
</Alloy>

By adding an event handler to the TableView and a reference to each row, I could work out what was picked and then open the relevant subwindow — pretty straight-forward.

This is fine if I only need to do this once, but if I have an app that requires me to have several of these Navigation Windows, I’d be duplicating a lot of this XML and code.

An ideal setup here is to have one Navigation Window that I could re-use whenever I need it — and have it expose some methods or properties to help me open up subwindows so I’m not duplicating code throughout my app.

To get started and solve this, I need a base controller that sets up the root view. So, I’ve created a new controller called “baseNavigationWindow” and dropped the following code in the XML view:

<Alloy>
<NavigationWindow>
<Window id="window" title="Base Window">
<View id="contentTemplate"/>
</Window>
</NavigationWindow>
</Alloy>

Then in baseNavigationWindow.js, I’ve added the following:

$.getView().addEventListener("open", function(){
    $.contentTemplate.add($.content);
});

exports.openController = function(controller){
    $.getView().openWindow(Alloy.createController(controller).getView());
};

I now have a base controller-view that creates the Navigation Window and exposes a public method to open new subwindows.

Next, I want to inherit this for my settings screen, so all that contains is its content and everything else is “wrapped” around it.

I’ve created a new controller / view / style file called settings and in the XML added the following:

<Alloy>
<View id="content" layout="vertical">
<TableView onClick="openWindow">
<TableViewRow title="My Details" hasChild="true"/>
<TableViewRow title="Logout" hasChild="true"/>
</TableView>
</View>
</Alloy>

I’ve also added some code to the settings.js file:

$.window.title = "Settings";

function openWindow(){
    $.openController("subWindow");
}

exports.baseController = "baseNavigationWindow";

OK, let’s take a look at what’s going on here.

First, we have this line in the settings.js file:

exports.baseController = “baseNavigationWindow”

This is important as it tells the controller to inherit the view-controller called baseNavigationWindow. This means that if my base controller had a Label, or Buttons, or other elements, they would appear in the Settings screen automatically and all of the public methods defined with exports would be accessible to the settings controller code.

In this example though, I don’t want all the content to be in the base controller, because I want this Navigation Window to be re-usable for different screens. What I need is a template that can wrap the content of my other screens.

In order to do this, I have this important block of code in the baseWindowController.js file:

$.getView().addEventListener("open", function(){
    $.contentTemplate.add($.content);
});

This is basically saying that when the controller-view is opened, copy the $.content from the inheriting controller-view and add this to the base controller-view. The result is that the base controller view “wraps” the view that inherited it.

Nice!

To open settings, all we have to do is open it like any other view:

Alloy.createController("settings").getView().open();

At this point, the base controller opens, and wraps the settings view-controller with the NavigationWindow and Window from the base view-controller. In settings.js, we’re setting $.window.title to our preferred title and because we’ve defined window as an id in the base controller, it just works.

The last piece is the method we created once in the base controller with exports.openController — this is now exposed to the inheriting controller as $.openController which when called, will open a new window as a subwindow to the root Navigation Window.

Now, I can create new view-controllers for “register” etc, and apply the same logic, allowing the register screen to inherit the same Navigation Window we used in the settings view-controller!

Have fun playing with the code and find out more about base controllers in the Titanium documentation.

Happy coding!