Enhancing Titanium: How to set up Collections with DataBinding

So in the past I’ve written about Collections, how to speed them up and stuff, but I’ve never actually talking about how to set them up.

Consider this post a short Tutorial on how to set up some basics. I would advice to take a close look at the Appcelerator documentation on DataBinding, it will tell what is needed, but a guiding hand is probably what is needed, which I will give right here.

The Data Source

First you will need a data source. Inspect the JSON result and look for patterns, look at the “ID” element. As an example, we’ll use my own API for train times in the Netherlands. Take a look at the Departures from Amsterdam Central Station

When you look at the data, you’ll see the array is contained within the “times” object. You’ll also see the “ID” element in this data source is, actually, ‘id’. This makes it easier, but having a different ID doesn’t make it much harder. Simply adding the idAttribute does the trick. (Yes, you can use actual Backbone documentation on it, just be aware of a version mismatch, as in, the latest version isn’t in Alloy)

Now that is concluded, lets make a Model. Making a model with Appcelerator Studio is easy, just right click on your project in the project explorer (or on any folder/file of your choice within the project), select “New” and click “Alloy Model”.

This will create a basic Backbone model for you. Besides the earlier mentioned idAttribute, you shouldn’t have to change anything in it.

Setup in Alloy

Now, you will need to add the collection to the controller you want to use it in. In this case, we’re going to add a collection as a local instance. That means, it can and will be used within this controller, but will not be accessible in other controllers. This is done by simply adding instance=true to the alloy section. So how does this look?

<Alloy>
<Collection src="Departure" instance="true" id="Departures" />
<Window>
</Window>
</Alloy>

As you can see, the collection is added to the root of the Alloy tag, not within the window (important!).

The src property is the name of your model you just created. In this case I named my model Departure but choose whatever you want. Unsure what the name is? Look into the models folder in your application, and look at the filename. The name of the model is the filename without the extension.

The id property is useful for talking to the collection in the controller.

Now you have set up the collection within your controller, and it is ready to be used

Filling a collection with Data

The simplest way to fill a collection with data, without having to worry about REST adapters for collections (we’ll not dive into this) is to use good old XHR http client, which you should be accustomed to use just now, so I will not dive into how to use it, I’ll show you how I’ll handle the data.

Within the controller you can access your collection through the id given to the collection in the XML. In my case Departures, so I can access it as $.Departures. In my code I have a function set up for fetching the data from the API I mentioned above. It looks like this:

function getTimes(cb){
var url = “http://spoorbaan.com/API/station/asd";
var xhr = Ti.Network.createHTTPClient({
onload: function(e) {
cb(JSON.parse(this.responseText));
},
onerror: function(e) {
Ti.API.debug(e.error);
alert(‘error’);
},
timeout:5000
});
xhr.open(“GET”, url);
xhr.send();
}

As you can see this is pretty straightforward to get the JSON out of the API. I call the function getTimes with a callback function in the parameters. Within the callback I will loop through departure times (within the times object which we already discussed earlier). And while looping through the data, I will create a model for each entry and in the end I will add this to the collection, like so

getTimes(function(data){
var deps = data.times;
var depModels = [];

_.each(deps, function(dep){
depModels.push(Alloy.createModel(‘Departure’,dep));
});

$.Departures.reset(depModels);
});

As you can see in this code, I do not add the models to the collection when creating them, but instead I add them to a new array, and insert them into the collection by using reset. So why do I do this?

First of all, let me explain reset. What does it do. Well, it is a method from backbonejs and their documentation has this to say about it.

Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you’d rather just update the collection in bulk. Use reset to replace a collection with a new list of models

So… this means, if you already HAD some of the departure times in your collection, the new models will be used as a guideline to merge. (So for example only the platform changes, then that is the only thing that changes). If it already exists in the collection without any changes, nothing will change and the UI doesn’t have to update. If a newly added model doesn’t exist, it will be added to the collection, and if a model exists already in the collection, but is absent in the new list of models, it will be removed. All at once. This is very convenient for this use case, because departure times change sometimes, old ones need to go in the collection, and new ones need to be added.

It will also keep the order in which the new list is provided, so no need for sorting. Custom sorting is possible though, but thats for later.

If you just want to add new models, you should do it like this

$.Departures.add(Alloy.createModel(‘Departure’,dep));

Binding it to a ListView

So, now that we’ve set up the collection and added data to it, we need to display it. We’ll do this with a ListView in this example, but a scrollview, tableview, regular view or others are also possible. The complete list (and documentation) can be found at the Appcelerator Documentation about Alloy Data Binding.

So, a simple setup can be done in Alloy like this:

<ListView onItemclick=”itemClick”>
<ListSection
dataCollection=”$.Departures”
dataTransform=”transformDeparture”
>

<ListItem
title=”{title}”
itemId=”{departureId}”
/>
</ListSection>
</ListView>

As you can see, I’m hooking up the Departures collection to a ListSection within the ListView. And I create a ListItem which passes some data around.

I use a dataTransform function so I can manually assign and manipulate data from a model to what I want to display. Usually you will need to use this, so I think it is good practice to always do it even with the simplest data possible. Like in this case we don’t even have a template for a ListItem.

Using dataTransform

Diving into dataTransform, this is a simple function that receives a model as first parameter, and you should return an object. A simple example using the above ListView example.

function transformDeparture(model){
var time = require(‘Date’).formatTime(model.get(‘time’));

return {
title: time + ‘ — ‘ + model.get(‘destination’).name,
departureId: model.get(‘id’)
};
}

As you can see, I return an object containing title and departureId. Title is a mapped to the title property in a ListViewItem as displayed earlier, and departureId is mapped to itemId.

This simple use case already displays the benefit of having a transform function, because in this case we add the departure time to the destination name to function as the title.

Any property you set in the return object in the transform function can be mapped with the same name.

So then the itemId, why is that one needed? Well, that is a property that is very very convenient.

The itemId property

Yes, this property isn’t one I invented. It is very convenient for mapping a click to a model. How? Easy!

We have the ItemClick method:

<ListView onItemclick=”itemClick”>

This maps to a function on click. But you want to know which item was clicked right? And preferably, you want to have the model that was represented by that item. Well, that is why you get the itemId property. It is this simple with collection:

function itemClick(e){
var model = $.Departures.get(e.itemId);
}

And there you have it, you’ve got the model. Do whatever you want with it what is needed. Create the proper content view, open a window, etc.

Updating the ListView

So now you can easily get the model on a click, you can also manipulate the view easily. If you update the model, the view will automatically refresh and update based on the new content. The transform function will also be called upon again and the view is properly rendered.

This means, if you manipulate the date object in the model, and set it again (check backbone docs on set), the view is updated straight away with the newly set date. It is that easy.

Also, if you open up a window based on a model (to show more details for example) I recommend passing down the model. If you want to manipulate some model data in another window, the ListView is also updated, even when you don’t have it open.

Using Templates in ListViews

So, you can now set up a basic ListView, you probably also want to know how to set up a template. I’ll give a simple example using the same title property again, but now with a Label.

<ListView onItemclick=”itemClick” id=”OpenJobs”>
<Templates>
<ItemTemplate name="Departure">
<Label id="title" bindId="title"></Label>
</ItemTemplate>
</Templates>
</ListView>

Now this is a very simple template, with the name Departure. You need to map this in the DataBinding, let me copy-paste this from above with that added

 <ListSection 
dataCollection=”$.Departures”
dataTransform=”transformDeparture”
>

<ListItem
title:text=”{title}”
itemId=”{departureId}”
template="Departure"
/>
</ListSection>

So, I’ve set the template property in the View, and I’ve also updated the title binding. Because title is now bound to a label, you need to define which property needs to be mapped. I mapped the title to, of course, the text property of the label. But you can map to anything you like, you can also set colors, font, width, height or visibility. Basically anything can be mapped.

Further Reading

Some of what has been discussed, and some other new stuff, can be found in a Github Gist I set up. You can go there to dig some further.

This is just the start. You’ve only set up one collection on one ListSection. There has been no filtering, no sorting and no multiple collections in one view. Also, this was just a local instance, you can also have global instances.

Some further reading can be done on the subject. And I recommend a lot of experimenting.