Published on 23 Feb 2015 - 5 min read by Richard Lustemberg
Working with ListViews - Part 2
(Demonstration code for this tutorial can be found at : https://github.com/rlustemberg/listviewtutorial)
In the first part of this tutorial, I introduced the basic concepts of how to instantiate listviews. On this sequel I'll cover how to use them as to obtain the same functionality we get from tableviews
Handling events
When we click on a row in a listView, an ** itemClick** event is generated in the listView itself (not on the row).
We are only concerned with the itemIndex property from the event's payload.
With listViews we do it as follows:
- We setup an eventListener for the onClickItem event on the list view
- We get the index of the clicked item (event ** itemIdex** property).
- We get the item and it’s data (using the listSection getItemAt() method plus the index).
- We do something with the data
- We might also modify the data
- We update the listItem with the new data
We've defined the itemClick callback in the view file:
<ListView onItemclick="listItemHandler" defaultItemTemplate="mainTemplate">
on the controller we declare the callback:
function listItemHandler (e) {
var item = $.listSection.getItemAt(e.itemIndex);
// we do something (in this case we swap an image)
item.bookMark.backgroundImage = '/images/star_selected.png';
// we update the item
$.listSection.updateItemAt(e.itemIndex, item);
}
Creating ListItems programatically
On a real world scenario, a typical data driven app consumes data from a web service. This data’s format is usually JSON or XML. The data is parsed into a javascript array of data objects which is processed and fed to the list view. In this tutorial we use randomised dummy data provided by the function createDummyDataArray().
The next example shows how a single listDataItem is added to the array.
//Adding one listItem:
var listItemsArryay = [
{ title: {text:’Great landscape!’},{bodyText:{text:’Some text'}}}
];
//The listSection takes care of adding the rows
$.listSection.setItems (listItemsArray);
Notice the 'title' and 'bodyText' properties of the ListDataItem. They correspond to the bindId properties of a view in the itemTemplate:
<Label bindId="title" id="title"></Label>
It's important to notice that :
- bindId: is used to map values to view properties on each row. It uses values provided from the controller.
- id: is used to map values to view properties on all rows. It uses values defined on the tss file.
With ListViews, direct access to this properties is not possible. They can be only accessed by as explained before, getting the item using indexes.
Creating the array of ListDataItem using a loop
/**
* Creates a formated ListDataItem object
* @param {object} obj Raw data
* @return {ListDataItem} A listDataItem object
*/
function makeItem (obj) {
return {
data: obj,//adding the full data object to the item, so that we can keep the item’s state
title : {text : obj.title},
bodyText : {text : obj.body},
avatar : {image : obj.avatar},
pic : {image : obj.image},
bookMark : {backgroundImage : obj.isFavorite ? '/images/star_selected.png':'/images/star_unselected.png'}
};
}
/**
* Setting up the ListView data
*/
function setListItems () {
// We create an array of objects using
var dataArray = createDummyDataArray (50);
var arrayCount = dataArray.length;
var listItems = [];
//we iterate over the data and add it to an array of ListDataItem
for (var i = 0; i < arrayCount; i++) {
//we make a listDataItem and push it to the array
listItems.push (makeItem(dataArray[i]));
}
//We set the data on the listView throught the listSection
$.listSection.setItems (listItems);
}
We use two functions:
- makeItem: for creating a listDataItem formatted object
- setListItems: to loop over the unformatted array, create the items, add them to an array of formatted objects and then populate the listView with the latter array.
Keeping List items state
The function makeItem, invoked on every iteration of the loop over the data, receives an object and creates the ListDataItem. You may notice that besides setting the corresponding binId properties, I'm also adding the raw object itself to the ListDataItem. In this way, I can use the raw object as 'state' for the item. I can the use this state for conditional modifications to the ListItem views, or to pass to other objects along the way.
In iOS, the ListItemData is serialized to an NSDictionary instance. This means that you cannot pass objects with methods ( they will be trimmed down. Also beware of passing very complex javascript objects because the conversion process will slow things down.
In the code example, we have the isFavorite boolean which determines the image background for the bookMark view. If we want to toggle the image (selected and unselected states), it’s a bad practice and cumbersome to do a conditional on the imageBackground property. It is much better to do the conditional on the property itself, which is what we already do in the makeItem function.
Event handler example
/**
* Handling itemClick event on ListView
*/
function listItemHandler (e) {
var item = $.listSection.getItemAt(e.itemIndex);
//Ti.API.info ('ListItem event data '+ JSON.stringify(e));
//handling toggling of bookMark view
if (e.bindId === 'bookMark') {
Ti.API.info ('ListItem data '+ JSON.stringify(item));
//modifyng the data
item.data.isFavorite = !item.data.isFavorite;
//recreating the item's data and updating the ListItem with the new item
$.listSection.updateItemAt(e.itemIndex, makeItem (item.data));
}
}
Buttons, a special case!
Buttons are always special, but even more so when they are added to a ListItemTemplate.
Button events on ListViewItems don’t propagate to the ListView. Event listeners must be added then to on the template declaration (in Alloy, on the itemTemplate xml node).
The click event generated by the button will carry the same data as any other view on the row. So it is handle in the same way:
- We setup an eventListener for the onClick event on the list view.
- We get the index of the clicked item.
- We get the item’s data by using the getItemAt method on the list section
This is fine when the template declaration is on the same view as the listView.
But itemListTemplates can also be added to the listItem using an Alloy require statement. You can (and certainly will) create templates on their own xml view files. The one place where can put the event callback declaration is , of course, in the view's controller.
These Alloy views are not really 'views', they are a template. When compiling, Alloy doesn't asign an alloy_id to the templates. If you declare an 'id' property on the view xml file, will be ignored.
<Templates>
<Require id="rowTemplate" src="rowTemplate"/>
</Templates>
the id property in the template will be ignored. Therefore, the template's controller becomes isolated
$.rowTemplate //undefined in the parent controller
This means that the event callbacks on the template controller have no direct way of passing any data to the list section. This is necessary , for example, if you want to refresh the list upon clicking a button.
The button's click event doesn't propagate to the listSection, and the event callback cannot access the listSection.
Workarounds for buttons?
- Use Ti.UI.Views instead of Ti.UI.Button
- Use only on templates declared on the same view as it's ListView parent
- Make the listView and necessary data global (very bad)
I hope you find this article useful. Please comment if in doubt, or found any error in the article.