Titanium Community Questions & Answer Archive

We felt that 6+ years of knowledge should not die so this is the Titanium Community Questions & Answer Archive

Two column picker: a suggested method

What follows is one possible workaround for pickers that require two columns with the contents of the second column being dependent on the value of first column. The method addresses (what I believe to be) a bug in the Titanium 1.4x SDK (Titanium.UI.Picker.reloadColumn does not work).

The app I'm working on at the moment requires a means of picking your timezone from a list. The total list of world timezones is pretty large (400+ zones) so, in order to keep the list manageable for the user, I am parsing a text file (from geonames.org) so that it breaks the data up into continents and regions. The resulting hash contains items like this:

nameArray["America"][94] = { region: "New York", gmt: "-5.00", dst: "-4.00" };
nameArray["Europe"][17] = { region: "Jersey", gmt: "0.00", dst: "1.00" };

When coding in Javascript, I much prefer to use the "Constructor" pattern to create classes and this seems to work out pretty well with Titanium code as well. This is the class to create a fairly flexible two-column picker:

function MultiPicker(arg) {
    //*** Create a reference to this instance to be used within methods
    var me = this;
    this.arg = arg;
    //*** Set arg values if defined or default if not
    this.arg_set = function (argv, defaultValue) {
        var dVal = (typeof(defaultValue) == "undefined") ? null : defaultValue;
        return (typeof(argv) == "undefined") ? dVal : argv;
    };
    this.win = this.arg_set(arg.win);
    this.dataArray = this.arg_set(arg.dataArray);
    this.picker = null;
    this.column1 = null;
    this.column2 = null;
    this.colOneArray = [];
    this.colTwoArray = [];
    this.colOneOptions = this.arg_set(arg.colOneOptions,{
        opacity: 0
    });
    this.colTwoOptions = this.arg_set(arg.colTwoOptions,{
        opacity: 0
    });
    this.colTwoLabel = this.arg_set(arg.colTwoLabel);
    this.currentValue = "";
    this.pickerValues = null;
    this.initialized = false;

    MultiPicker.prototype.load = function (colOneName, colTwoSelected) {
        if (me.initialized) {
            me.win.remove(me.picker);
        }
        me.picker = Ti.UI.createPicker({
            type: Ti.UI.PICKER_TYPE_PLAIN
        });
        me.picker.selectionIndicator = true;

          var colOneIndex = me.load_col_one(colOneName);
        me.picker.add(me.column1);

         var colTwoIndex = me.load_col_two(colOneName,colTwoSelected);
        me.picker.add(me.column2);

        me.picker.setSelectedRow(0,colOneIndex,false);
        //*** Use timers to prevent unwanted behaviors
        setTimeout(function() { me.picker.setSelectedRow(1,colTwoIndex,false); },100);
        setTimeout(function() { me.set_event(); },500);

        me.pickerValues = { col1: colOneName, col2: colTwoIndex };
        me.win.add(me.picker);
        me.initialized = true;
    };

    MultiPicker.prototype.get_values = function () {
        return me.pickerValues;
    };

    //*** Privileged methods (members)

    this.load_col_one = function (selectName) {
        var i = 0;
        var selectedIndex;

        me.column1 = Ti.UI.createPickerColumn(me.colOneOptions);        
        me.column1.colIndex = 1;
        for (var key in me.dataArray) {
            if (me.dataArray.hasOwnProperty(key)) {
                me.colOneArray[i] = key;
                var row = Ti.UI.createPickerRow({ title: key });
                row.rowIndex = i;
                me.column1.addRow(row);    
                if (selectName == key) {
                    selectedIndex = i;
                }
                i++;
            }
        }
        return selectedIndex;
    };

    this.load_col_two = function (colOneName, colTwoSelected) {
        var selectedIndex;

        me.column2 = Ti.UI.createPickerColumn(me.colTwoOptions);
        me.column2.colIndex = 2;
        for (var i=0; i<me.dataArray[colOneName].length; i++) {
            me.colTwoArray[i] = me.dataArray[colOneName][i];
            var row = Ti.UI.createPickerRow({});
            var value = me.colTwoArray[i].region;

            if (me.colTwoLabel != null) {
                var label = Ti.UI.createLabel(me.colTwoLabel);
                label.text = value;
                row.add(label);
            } else {
                row.title = value;
            }
            if (colTwoSelected == value) {
                selectedIndex = i;
            }
            row.rowIndex = i;
            me.column2.addRow(row);    
        }
        return selectedIndex;
    };

    this.set_event = function () {
        me.picker.addEventListener('change',function(e) {
            var selectedValue = me.colOneArray[e.row.rowIndex];
            if (e.column.colIndex == 1 && selectedValue != me.currentValue) {
                //*** Reload picker with the selected value
                me.load(selectedValue, 0);
                me.currentValue = selectedValue;
            }
            if (e.column.colIndex == 2) {
                me.pickerValues = { col1: me.currentValue, col2: e.row.rowIndex };
            }
        });
    };
}

To use this in practice, you'll need to insert your data in to a hash similar to the one shown above and then instantiate the MultiPicker like this:

var win = Titanium.UI.currentWindow;

var tzPick = new MultiPicker({
    win: win,
    dataArray: nameArray
});

If you want to get fancy, you can define the appearance of the columns and include a custom view in the second column like this:

var tzPick = new MultiPicker({
    win: win,
    dataArray: nameArray,
    colOneOptions: {
        opacity: 0,
        width: 120
    },
    colTwoOptions: {
        opacity: 0,
        width: 180
    },
    colTwoLabel: {
        font: { fontSize: 14, fontWeight: 'bold'},
        textAlign: 'left',
        left: 'auto',
        width: 'auto',
        height: 'auto'
    }
});

Next, call the "load" method and the picker will display with the selected values:

tzPick.load("America","New York");

The 'change' event of the picker returns an object that contains the properties 'col1' and 'col2'. You can access it by calling the 'get_values()' method. In my implementation, I'm grabbing it in the listener of a 'Save' button:

var saveBtn = Ti.UI.createButton({
    title: 'Save'
});
saveBtn.addEventListener('click',function() {
    var tzValues = tzPick.get_values();
    //*** Do whatever you need to with the returned values here
    win.close();
});
win.setRightNavButton(saveBtn);

That's about it! There are a couple of kludges in this system due to the present operation of the picker in Titanium (e.g, the picker is removed then re-added to the current window since we can't reload the column data and there some 'setTimeout' calls to avoid timing snafus when binding the listener and positioning the selection of the second column). Hope you find it to be of some use!

Mark

— asked December 5th 2010 by Mark Pemburn
  • iphone
  • picker
  • workaround
0 Comments

0 Answers

The ownership of individual contributions to this community generated content is retained by the authors of their contributions.
All trademarks remain the property of the respective owner.