Twitter LinkedIn E-mail RSS
Home Development Titanium: Accordion Table View (IOS)
formats

Titanium: Accordion Table View (IOS)

This post (I was going to say “little”  but I don’t think it will be) is about how to code an Accordion (expand and collapse) using a table view which has sections using Appcelerator Titanium.

 

Studio 3.0.2, SDK 3.0.0.GA on a Mac with xCode 4.6 for iPhone and iPad. (Hopefully Android coming later)

 

I thought it was going to be a fairly straight forward task, but no…..

Firstly it’s worth noting that these DO NOT WORK, within table views.

rowAtIndex

show()

hide()

indentionLevel

I don’t know if the docs are wrong or not, but either that is the case or the SDK has a bug or 2.

iPad screen shot 1

The solution provided enables each section of a table to be expanded or collapsed by pressing the section title, as well as the rows within sections. It works by formatting the data to have separate levels. In this example we start at level 1 and go down to level 4. If indentionLevel worked, then this would also indent the data accordingly, although for this example I have just added spaces at the front.

 

 

 

Now before we go through how this is achieved, you will need the code and get it running. Here it is TiAccordion. Within studio create a new project, replace the app.js file with the one included in the zip file and copy the images into the resource directory. That should be it … run it and play.

 

The remainder of this post will go into details on how this works.

 

Data

How you format your data for processing is really down to your individual requirements. Within the  app.js file, I have simply created a data structure. The key here is to have each record defining it’s level and if it has children. I would expect your data to have more fields, but for this example I have kept it to a minimum.

I expect this solution to work for maybe a couple of hundred rows of simple data or up to about 100 rows for complex data. This will not work if you have thousands of rows. (Nor should it)…
var tableDataLoad = [{
    sectionNum:  1,
    sectionTitle:  'Section 1',
    data:  [{
        level:  1,
        title:  'Section 1 Level 1 Item 1',
        child:  true
    }, {
        level:  2,
        title:  '  Section 1 Level 2 Item 1',
        child:  true
    }, etc

 

The Opening Function

There isn’t a lot to say about this function. Set the seperatorStyle to 0, so blank lines are not displayed. Add the tableview event listener and then process the data.

function displayWindow() {'use strict';
    win = Ti.UI.createWindow({
        backgroundColor:  '#3333FF'
    });
    var screenTitle = Ti.UI.createLabel({
        top:  10,
        left:  10,
        right:  10,
        height:  50,
        text:  'Table View Accordion',
        textAlign:  'center',
        color:  '#ffffff',
        font: {
            fontSize:  18,
            fontWeight:  'bold'
        }
    });
    viewTable = Ti.UI.createTableView({
        top:  65,
        left:  20,
        right:  20,
        bottom:  50,
        rowHeight:  0,
        backgroundColor:  'transparent',
        separatorColor:  '#000000',
        separatorStyle:  0
    });
    viewTable.addEventListener('touchend', expandCollapseView);

    win.add(screenTitle);
    win.add(viewTable);

    processData();

    win.open();

    return;
}

Processing the Data

This function takes the data object, and builds the data for the tableview, utilising a separate function to build the table rows.

function buildTableRow(inParam) {'use strict';

    var row = Ti.UI.createTableViewRow({
        title: inParam.title,
        height: viewheight,
        indentionLevel: inParam.level,
        backgroundGradient: {
            type: 'linear',
            colors: [{
                color: '#414141',
                offset: 0.0
            }, {
                color: '#B2B2B2',
                offset: 0.12
            }, {
                color: '#A1A1F1',
                offset: 0.85
            }]
        },
        color: '#ffffff',
        font: {
            fontSize: 14,
            fontWeight: 'normal'
        },
        LEVEL: inParam.level,
        EXPANDED: (inParam.level < 2) ? true : false,
        CHILDEXPANDED: (inParam.level < 1) ? true : false,
        CHILD: inParam.child
    });
    var rowImg = Ti.UI.createImageView({
        right: 30,
        height: 18,
        width: 18,
        top: 7,
        image: (inParam.child) ? plusImg : ''
    });
    var clickView = Ti.UI.createView({
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'transparent'
    });
    row.add(rowImg);
    row.add(clickView);

    row.height = (inParam.level < 2) ? viewheight : 0;
    rowImg.height = (inParam.level < 2) ? imageheight : 0;

    return row;
}

 

function processData() {'use strict';
    var dCnt = 0;

    for ( dCnt = 0; dCnt < tableDataLoad.length; dCnt += 1) {
        var sectionView = Ti.UI.createView({
            top:  0,
            left:  0,
            right:  0,
            height:  33,
            backgroundGradient: {
                type:  'linear',
                colors:  [{
                    color:  '#818181',
                    offset:  0.0
                }, {
                    color:  '#B2B2B2',
                    offset:  0.12
                }, {
                    color:  '#C3C3C3',
                    offset:  0.85
                }]
            }
        });
        var sectionViewLabel = Ti.UI.createLabel({
            top:  0,
            left:  0,
            width:  '100%',
            height:  '100%',
            text:  '  ' + tableDataLoad[dCnt].sectionTitle,
            textAlign:  'left',
            color:  "#ffffff",
            font: {
                fontSize:  14,
                fontWeight:  'bold'
            },
            secValue:  tableDataLoad[dCnt].sectionNum,
            EXPANDED:  true
        });
        sectionView.add(sectionViewLabel);
        sectionView.addEventListener('touchend', sectionProcess);

        var section = Ti.UI.createTableViewSection({
            headerView:  sectionView
        });
        var rCnt = 0;

        while (rCnt < tableDataLoad[dCnt].data.length) {
            section.add(buildTableRow(tableDataLoad[dCnt].data[rCnt]));
            rCnt += 1;
        }
        sectionsData.push(section);
    }
    viewTable.setData(sectionsData);

    return;
}

Expanding & Collapsing

The code below expands and collapses the view. This works by deciding on the rows, which need to be expanded or collapsed and then apply a new height to that row. This process does not work when using the hide or show option, which is why the height is adjusted.

As you will see for sections we have to process each section separately and the rows contained within it.

function sectionProcess(inParam) {'use strict';

    var iCnt = 0;
    var tCnt = 0;
    var baseLevel = 0;
    var rowStart = 0;

    var secTableData = viewTable.getData();
    var sectionData = secTableData[inParam.source.secValue - 1];
    var rowData = sectionData.rows;
    var setViewHeight = (inParam.source.EXPANDED)  ?  0  :  viewheight;
    var setImgHeight = (inParam.source.EXPANDED)  ?  0  :  imageheight;
    var childLevelChk = {};
    var keyChk = 'AA0';

    inParam.source.EXPANDED = (inParam.source.EXPANDED)  ?  false  :  true;

    while (iCnt < rowData.length) {
        var setRowHeight = setViewHeight;
        var setImgRowHeight = setImgHeight;

        if (setRowHeight > 0) {
            if (rowData[iCnt].LEVEL === 1) {
                baseLevel += 1;
                keyChk = 'AA' + baseLevel;
                childLevelChk[keyChk] = [];
                childLevelChk[keyChk][1] = rowData[iCnt].CHILDEXPANDED;
            } else {
                var currLevel = rowData[iCnt].LEVEL;
                var prevLevel = currLevel - 1;

                childLevelChk[keyChk][currLevel] = (childLevelChk[keyChk][prevLevel])  ?  rowData[iCnt].CHILDEXPANDED  :  false;

                setRowHeight = (childLevelChk[keyChk][prevLevel])  ?  setViewHeight  :  0;
                setImgRowHeight = (childLevelChk[keyChk][prevLevel])  ?  setImgHeight  :  0;
            }
        }
        if (rowData[iCnt].EXPANDED) {
            rowData[iCnt].height = setRowHeight;
            rowData[iCnt].children[0].height = setImgRowHeight;
        }
        iCnt += 1;
    }
    return;
}

function expandCollapseView(inParam) {'use strict';

    var inLevel = inParam.rowData.LEVEL;
    var inExpanded = inParam.rowData.CHILDEXPANDED;
    var rowTableData = viewTable.data;

    // need to find the section of the row ...
    var secLevel = 0;
    var totalRowCnt = 0;
    var sectionRows = null;
    var sectionStartRow = 0;
    var currentSectionRow = 0;
    var childLevelChk = [];

    for ( secLevel = 0; secLevel < rowTableData.length; secLevel += 1) {
        totalRowCnt = totalRowCnt + rowTableData[secLevel].rows.length;

        if (totalRowCnt >= (inParam.index + 1)) {
            sectionRows = rowTableData[secLevel].rows;
            sectionStartRow = totalRowCnt - sectionRows.length;
            currentSectionRow = inParam.index - sectionStartRow;
            break;
        }
    }
    var setViewHeight = (inExpanded)  ?  0  :  viewheight;
    var setImgHeight = (inExpanded)  ?  0  :  imageheight;
    var tCnt = 0;
    var tmpLevel = 0;
    var bCnt = 0;

    tCnt = currentSectionRow + 1;

    childLevelChk[inLevel] = (inExpanded)  ?  false  :  true;

    while (tCnt < sectionRows.length) {
        tmpLevel = sectionRows[tCnt].LEVEL;

        if (tmpLevel <= inLevel) {
            break;
        }
        childLevelChk[tmpLevel] = (childLevelChk[tmpLevel - 1])  ?  sectionRows[tCnt].CHILDEXPANDED  :  false;

        sectionRows[tCnt].height = setViewHeight;
        sectionRows[tCnt].children[0].height = setImgHeight;
        sectionRows[tCnt].EXPANDED = (inExpanded)  ?  false  :  true;
        bCnt = tCnt + 1;

        if (bCnt < sectionRows.length) {
            while (sectionRows[bCnt].LEVEL > tmpLevel) {
                // here is the complex one.... A child level can be expanded but a parent level can be collapsed so need to validate which it is ...
                childLevelChk[sectionRows[bCnt].LEVEL] = (childLevelChk[sectionRows[bCnt].LEVEL - 1])  ?   sectionRows[bCnt].CHILDEXPANDED  :  false;
                var setRowHeight = (childLevelChk[sectionRows[bCnt].LEVEL - 1])  ?  setViewHeight  :  0;
                var setImgRowHeight = (childLevelChk[sectionRows[bCnt].LEVEL - 1])  ?  setImgHeight  :  0;

                if (sectionRows[bCnt].EXPANDED) {
                    sectionRows[bCnt].height = setRowHeight;
                    sectionRows[bCnt].children[0].height = setImgRowHeight;
                }
                bCnt += 1;

                if (bCnt >= sectionRows.length) {
                    break;
                }
            }
        }
        tCnt = bCnt;
    }
    inParam.rowData.CHILDEXPANDED = (inExpanded)  ?  false  :  true;
    inParam.rowData.children[0].image = (inExpanded)  ?  plusImg  :  minusImg;

    return;
}

Well a shortish text content post, but heavy on the code. Hope it helps.

 

 
 Share on Facebook Share on Twitter Share on Reddit Share on LinkedIn
Comments Off on Titanium: Accordion Table View (IOS)  comments