Appcelerator: Making an expandable (accordian) menu

Mark OlsenSr. Developer
Published:
Preface: This article is part of a series focused on cross platform mobile app development (specifically Android and iOS) using the Alloy framework and Titanium Studio made by Appcelerator. This article presumes a working knowledge of Titanium Studio and the Alloy framework.

The Alloy framework has great built in elements for making a simple menu, mainly using the TableView (and TableViewRow) system. Unfortunately this does not include any built in functionality for expanding and collapsing rows. At first glance, using the "insertRowAfter" and "deleteRow" methods of the TableView would work for this. Unfortunately there seems to be a bug with the iOS implementation which leads to a "no row found for index" error when deleting rows. To avoid this error, the menu can be implemented as verticaly aligned View elements.

In the menu View elements represent each row. The rows for the submenus start out "hidden" by a wrapper View with a height of 0. Each row of them menu that does not expand will have a "menuAction" property which will be harvested by the "menuClick" onClick listener. The parent row of each submenu will have extra properties which define how big the submenu is and the name of the submenu's wrapper View.

Example Alloy markup would look like this:

file: /views/index.xml
<Alloy>
                        <Window class="container">
                          <ScrollView id="menu" layout="vertical">
                            <View class="menuDivider"/>
                            <View class="menuRow" onClick="menuClick" menuAction="home">
                              <Label class="rowName">Home</Label>
                            </View>			
                            <View class="menuDivider"/>
                            <View class="menuRow" onClick="menuClick" menuAction="away">
                              <Label class="rowName">Away</Label>
                            </View>			
                            <View class="menuDivider"/>		
                            <View class="menuRow" onClick="menuExpand" menuItems="3" submenuId="submenuBrowsers" expanded="false">
                              <Label class="rowName">Browsers</Label>
                              <Label class="expandArrow" id="expandIndicator" text=">"/>
                            </View>
                            <View class="submenuWrap" id="submenuBrowsers" layout="vertical">
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="chrome">
                                <Label class="rowName">Chrome</Label>
                              </View>
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="firefox">
                                <Label class="rowName">FireFox</Label>
                              </View>	
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="ie">
                                <Label class="rowName">Internet Explorer</Label>
                              </View>				
                            </View>			
                            <View class="menuDivider"/>
                            <View class="menuRow"  onClick="menuExpand" menuItems="4" submenuId="submenuPhones" id="network" expanded="false">
                              <Label class="rowName">Smart Phones</Label>
                              <Label class="expandArrow" id="expandIndicator" text=">"/>
                            </View>
                            <View class="submenuWrap" id="submenuPhones" layout="vertical">
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="android">
                                <Label class="rowName">Android</Label>
                              </View>			
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="blackberry">
                                <Label class="rowName">Blackberry</Label>	
                              </View>			
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="iphone">
                                <Label class="rowName">iPhone</Label>
                              </View>			
                              <View class="menuDivider"/>
                              <View class="submenuRow" onClick="menuClick" menuAction="windows">
                                <Label class="rowName">Windows</Label>
                              </View>
                            </View>			
                            <View class="menuDivider"/>
                            <View class="menuRow" onClick="menuClick" menuAction="settings" id="bottom">
                              <Label class="rowName">Settings</Label>
                            </View>			
                          </ScrollView>
                        </Window>
                      </Alloy>

Open in new window


The "tss" (Titanium Style Sheet) file specifies the initial styles of the menu. Note the "submenuWrap" has a height of "0" and visible set to "false". Each submenu row is configured to have a height of "36".
 
file: /styles/index.tss
".container" : {
                         backgroundColor: "#63666a"
                      }
                      "#menu" : {
                         backgroundColor: "#63666a",
                      }
                      "Label" : {
                         color: "#fff",
                         verticalAlign: Titanium.UI.TEXT_VERTICAL_ALIGNMENT_CENTER,
                         height: Ti.UI.FILL,
                         touchEnabled: "false"
                      }
                      ".menuDivider" : {
                         width: Ti.UI.FILL,
                         height: '1dp',
                         backgroundColor: '#999'
                      }
                      ".menuRow" : {
                         height: '40dp',
                         backgroundColor: '#777',
                         width: Ti.UI.FILL
                      }
                      ".rowName" : {
                         left: '20dp'
                      }
                      ".expandArrow" : {
                         right: '20dp'
                      }
                      ".submenuWrap" : {
                         height: '0dp',
                         visible: 'false'
                      }
                      ".submenuRow" : {
                         height: '36dp',
                         backgroundColor: '#888'
                      }

Open in new window

 
The controller is where the expanding and collapsing is processed. When a submenu's title row is clicked, the "expandMenu" function will calculate how tall to make the submenuWrap View so that the submenu will be visible. To collapse the menu the submenuWrap is set back to a height of "0". Note: Each row in the menu includes a "divider" which is a height:1 View, hence why the value used to calculate the new height is 37 for each row instead of 36.
 
file: /controllers/index.js
function menuClick(e) {
                         //  Perform processing based on the menuAction of the clicked menu item
                         alert(e.source.menuAction);
                      }
                      
                      var submenuRowHeight = 37;
                      
                      function menuExpand(e) {
                         var menuRow = e.source;
                         var submenuWrap = $[menuRow.submenuId];
                         
                         if (menuRow.expanded == 'false') {
                            var newHeight = (submenuRowHeight * menuRow.menuItems);
                            submenuWrap.setHeight(newHeight);
                            submenuWrap.setVisible(true);
                            menuRow.expanded = 'true';
                         } else if (menuRow.expanded == 'true') {
                            menuRow.expanded = 'false';
                            submenuWrap.setHeight(0);
                            submenuWrap.setVisible(false);
                         }
                      }
                      
                      $.index.open();

Open in new window


This is a basic example of a menu that includes optional expanded submenus created in the Alloy framework that will work for both Android and iPhone devices. The nested submenu items could also be have their own submenus allowing for deeper navigation. By incorporating this menu into an app it can be utilzied as a "side swipe" drawer, options list, or any number of uses.
1
3,234 Views

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.