Closing all windows in a tab with Titanium

I am currently teaching myself Titanium for Android mobile development and one of the problems I immediately hit on was how to close all currently open windows in a tab. With my test application I am trying to write an app with a tab group that will navigate a ‘family tree’ type database, where each window displays a node and has many links to other windows, and you can traverse them in any order, leaving a trail that you can go back along using the back button. It is of course possible that the same node in the tree may appear more than once in the window hierarchy.

This is great (I think), but what if the user wants to start a new search? It would be tedious in the extreme to go back through all the windows to the beginning. I wanted to be able to just close all the currently open windows and go back to the root tab. My problem was that Titanium does not keep a list of the currently open windows. I could just close the tab group and then recreate it all but that seems a little inelegant. Besides, I was trying to refresh my knowledge of CommonJS and OO with Javascript too. 🙂 I was surprised about how little I could find about this on the web, (possibly it is something I shouldn’t be doing at all, but hey-ho), so I decided to just put together something for self-reference.

What I came up with was a globally held list of unique window IDs, but that is about as un-OO as it gets, the rest is done (hopefully) properly. Each time a new window was created it had its own GUID which was added to the list, and a global custom event handler was added for that window. When the ‘closeAll’ function is called it then fires the event for each window on the list. Possibly the code below will explain it a little better. Sorry for the mish-mash of styles, as I say, I am still learning!

‘app.js’ just sets up the tab group

// AppTabGroup just returns tab group - swiped from here: https://github.com/predominant/TItanium-OData-App-Example
require('lib/require_patch').monkeypatch(this);

(function() {
    "use strict";
    var globs = require('lib/globals'), GlobalEvents=require('lib/GlobalEvents');

    var AppTabGroup = require('ui/AppTabGroup'), FooSearch = require('ui/FooSearch');
    var BarSearch = require('ui/BarSearch');

    //create our global tab group
    globs.GV.tabs = new AppTabGroup({
        title : 'Foo'
        window : new FooSearch({
            title : 'Foo',
            exitOnClose:true
        })
    }, {
        title : 'Bar',
        window : new BarSearch({
            title : 'Bar',
            exitOnClose:true
        })
    });

    // Add a listener for the 'shake' event (code is below). Have to admit I've not tested
    // this bit yet - I don't currently have a device to shake, and the emulator doesn't do it!
    var ge = new GlobalEvents();
    ge.addGlobalListeners();
    globs.GV.tabs.open();
}());

‘globals.js’ just hold global variables for the current tabs and windows

exports.GV = {
    tabs : null,
    openWindows: []
};

‘GlobalEvents.js’ holds the functions for managing the list of windows

var globs;

// Fire the 'closeAllWindows' event for each open window on the list.
function closeCurrentWindows(e){
    "use strict";
    
    var i;
    
    for (i=0; i<globs.GV.openWindows.length; i++){
        // The name of the event to fire has the format 'closeAllWindows:<window_id>'.
        // Also send the actual ID through as part of the event data.
        Titanium.App.fireEvent("closeAllWindows:" + globs.GV.openWindows[i], {winId:globs.GV.openWindows[i]});
    }
    
    globs.GV.openWindows.length = 0;
    
}

// Add the passed ID to the list of windows currently open
function addToWindowList(winId){
    "use strict";
    globs.GV.openWindows.push(winId);
}

// Remove the passed ID from the list of currently open windows 
function removeFromWindowList(winId){
    "use strict";
     var i = globs.GV.openWindows.indexOf(winId);
       if (i !== -1){
           globs.GV.openWindows.splice(i, 1);
       }
}

// Add a listener for the shake event
function addShake() {"use strict";

    Ti.Gesture.addEventListener('shake', closeCurrentWindows);
    var gestureAdded = true;

    if (Titanium.Platform.name === 'android') {
        Ti.Android.currentActivity.addEventListener('pause', function(e) {
            // called when app is put into the background
            if (gestureAdded) {
                Ti.API.info("removing pinch callback on pause");
                Ti.Gesture.removeEventListener('shake', closeCurrentWindows);
            }
        });
        Ti.Android.currentActivity.addEventListener('resume', function(e) {
            if (gestureAdded) {
                Ti.API.info("adding pinch callback on resume");
                Ti.Gesture.addEventListener('shake', closeCurrentWindows);
            }
        });
    }
}


function addGlobalListeners(){
    "use strict";
    addShake();    
}


function GlobalEvents(){
    "use strict";
    globs = require('lib/globals');
    this.addGlobalListeners=addGlobalListeners;
    this.closeCurrentWindows=closeCurrentWindows;
    this.addToWindowList=addToWindowList;
    this.removeFromWindowList=removeFromWindowList;
}

module.exports=GlobalEvents;

I did not want the first search screens to also disappear, so they are not included on the window list, however the child window that they call (which displays a list of possible matches) is one that requires closing. Here is ‘FooSearch.js’

exports.FooSearch = function(args) {
    "use strict";
    var globs = require('lib/globals');
    var FooList = require('ui/FooList');

    var instance = Ti.UI.createWindow(args);

    // add text boxes etc to search window here...
    var btnSubmit = Ti.UI.createButton({
        title : 'Search',
        top : 5
    });

    instance.add(btnSubmit);


    btnSubmit.addEventListener('click', function(e) {
        // PseudoGuid generates the GUID. Listed below.
        var PseudoGuid = require('lib/PseudoGuid');
        var pg = new PseudoGuid();
        globs.GV.tabs.currentTab.open(new FooList({
                title : 'Foo Found',
                searchVal : , //Whatever your search value is!
                winId: pg.generate() // Call the generate ID function (see below)
                
            }));
    });
    return instance;
};

As you can see, the new window in FooList will have a property called winId which contains the result of a GUID generation routine. I guess the routine itself is not that relevant here but I include it for reference:

// The GUID generation mechnanism comes from: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
// Such long GUIDs are probably a bit overkill in my case but I liked it!
function generate(){
    "use strict";
    
    var S4 = function() {
       return ((Math.floor((1+Math.random())*0x10000))).toString(16).substring(1);
    };
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}

function PseudoGuid(){
    "use strict";
    this.generate=generate;
}

module.exports=PseudoGuid;

Now finally below is ‘FooList.js’, which shows how my app’s windows handled the custom event. All other windows that were called from here are handled in exactly the same way.

function PeopleList(args) {"use strict";
    globs = require('lib/globals');
    var GlobalEvents = require('lib/GlobalEvents');
    var ge = new GlobalEvents();

    var instance = Ti.UI.createWindow(args);
    this.win = instance;
    var self = this;
    
    // The callback function used by the closeAll event handler (below). Remember the
    // context is that of Titanium.App, not FooList, so we need to use a self reference (lookup
    // 'closures' if you are unsure what this is).
    this.closeAllCallback = function(e) {
        if (e.winId === self.win.winId) {
            self.win.fromCloseAll = true;
            self.win.close();
        }
    }; 

    // The window has been opened so add to the list
    instance.addEventListener('open', function(e) {
        ge.addToWindowList(this.winId);
    });
    
    // Look for the window close event - remember this can also be called using the back button
    instance.addEventListener('close', function(e) {
        // Only remove from the window list if called via back button - as the GlobalEvent handler
        // does it when closeAll is called.
        if (e.source.fromCloseAll === null) {
            ge.removeFromWindowList(e.source.winId);
        }
        
        // remove the custom event for this window (apparently Titanium can't do this itself
        Titanium.App.removeEventListener('closeAllWindows:' + instance.winId, self.closeAllCallback);

        // remember to clear up after yourself, as Titanium doesn't!
        this.remove(this.children[0]);
        this.children[0] = null;
        globs = null;
    }); 

    // Only have the emulator and want to test this? Use doubletap - emulator doesn't do 'shake'!
    var globEvents = new GlobalEvents();
    instance.addEventListener('doubletap', ge.closeCurrentWindows);

    // Add the custom even for this window
    Titanium.App.addEventListener('closeAllWindows:' + instance.winId, self.closeAllCallback);
    

    // At this point do any displaying of data, links to new windows etc...

   
    return instance;
}

module.exports = FooList;

I mainly created this post for my own and my team’s reference, but hopefully others will find it useful too. I may update it once I get a better handle on memory management etc, I’m not sure how well this does that at the moment.

Titanium HTTPClient returns immediately

If you want to consume a web service using Titanium then you need to use the Titanium.Network.createHTTPClient() method. By default however this is asynchronous. There is a ‘sync’ parameter to the open() method on the HTTPClient object however this does not work on Android (and apparently is deprecated anyway). This means that if you want to first call one service, then use the results to call another service, then you need to know that you can’t do this:


var showView = function(view){
    var json_data = getAndShowFirstService(view);
            
    getAndShowSecondService(view, json_data);
}

The above code will execute both calls to the service at roughly the same time. I assume that behind the scenes a new thread is being kicked off to make the call to the service, and then return to the main code immediately. You may get lucky (as obviously the first call is kicked off slightly before the second), however there is no guarantee that the first call will finish before the second starts.

One method to get around this problem is to use a callback function. The code starts to looks more complicated very quickly! First here is the above function rewritten correctly, or rather less incorrectly (I’m new to Titanium :-)):


var showView = function(view){

    getFirstServiceData(view, function(view, json_from_first_service){
           // Run when called from getFirstServiceData()
           showFirstServiceData(view, json_from_first_service);  

           getSecondServiceData(view, json_from_first_service, function(view, json_from_first_service, json_from_second_service){
                // Run when called from getSecondServiceData() 
                showSecondServiceData(view, json_from_second_service);
           });
    });
            
}

The last parameter of getFirstServiceData and getSecondServiceData are themselves anonymous functions – in other words all the code within the anonymous function will get executed when it is called from within the function it is being passed to. You obviously don’t need to pass view and json_from_first_service as parameters, you could hold them as globals, however that is not recommended best practice.

The outline of the four functions mentioned above is shown below. The parameter ‘view’ is either a Titanium Window or a View:


var getFirstServiceData = function(view, callback_function){
     
        var xhr = Titanium.Network.createHTTPClient();
        var url = "http://myservice.com/MyService.svc/Things";
        xhr.open("GET", url);
        xhr.setRequestHeader('accept', "application/json");
       
        xhr.onload = function() {
            var json_data = JSON.parse(xhr.responseText).d;
            
            // The anonymous function is called here  
            callback_function(view, json_data);
        }; 

        xhr.send({}); 
}

var getSecondServiceData = function(view, json_from_first, callback_function){
     
        var xhr = Titanium.Network.createHTTPClient();

        var searchId = json_from_first.Id; 

        var url = "http://myservice.com/MyService.svc/MoreThings(" + searchId +")";
        xhr.open("GET", url);
        xhr.setRequestHeader('accept', "application/json");
       
        xhr.onload = function() {
            var json_data = JSON.parse(xhr.responseText).d;
            
            // The anonymous function is called here  
            callback_function(view, json_from_first, json_data);
        }; 

        xhr.send({}); 
}

var showFirstServiceData = function(view, json_data){
    // Use Titanium UI methods to display on 'view'
}

var showSecondServiceData = function(view, json_data){
    // Use Titanium UI methods to display on 'view'
}

You can use this method to chain together lots of queries, but as you can imagine it gets messy quite quickly!

This post applies to Titanium SDK 2.0.1.