API Development

Updating User Interface Elements Using Alloy & Backbone — Part 2

In Part 1, we showed how it was possible to use Titanium and Alloy and the built-in Backbone to manipulate user-interface elements, turn on properties, or change the value of switches.

After publishing the post, I had a chat with a developer who was trying to create a dynamic form — they wanted to be able to create a series of TextField controls at runtime and be able to access the values when the form saved.

The challenge they had was how to get the values of the dynamically-created fields. There was a LOT of JavaScript involved as they were creating all the elements manually looping through the data using Ti.UI.createTextField to create each one and add it to a parent view. The code was all “Classic” Titanium code, so they were missing out on all the benefits of Alloy: styling, platform-specific content, themes, XML views and more.

When it comes to Classic Titanium vs Alloy, the key for me is that with Alloy you write far less JavaScript code, and less code means less chance of mistakes. You also gain all the event management and memory clean up of Alloy.

Looking back at Part 1, we already showed that we could dynamically alter User Interface elements using Alloy and data-binding, but what about creating new elements on-the-fly?

To get started, let’s look at a simple example that is going to create a few fields at runtime.

Firstly, we’re going to use a library I wrote called reste.js — you can also use mocx.js — they both will do what we need in this case — which is to create an on-the-fly collection of models.

I’m going to assume we’re using reste in this case. So, with that installed in the lib folder, we can add the following to alloy.js:

var reste = require("reste")();

reste.createCollection("formFields", [{
    id: "address1",
    hintText: "address1",
    value: ""
}, {
    id: "address2",
    hintText: "address2",
    value: ""
}]);

I’ve left the values empty there to show you could pre-populate the newly created fields.

With this code we’re creating a new instance of reste, and creating an on-the-fly collection called formFields which is accessible using Alloy.Collections.dynamicFields. This is the collection that will store the models that define the field elements we want to create.

Next, in index.xml we replace its contents with this XML:

<Alloy>
<NavigationWindow>
<Window title="Your Details" class="container">
<RightNavButton>
<Button top="20" onClick="submit">Save</Button>
</RightNavButton>
<View layout="vertical" top="20" id="form">
<TextField hintText="name" id="name"/>
<TextField hintText="age" id="age"/>
<View dataCollection="formFields" layout="vertical">
<TextField hintText="{hintText}" value="{value}"/>
</View>
</View>
</Window>
</NavigationWindow>
</Alloy>

In this view we’re creating a simple container that will hold some fields. There are two fixed fields for name and age and under those we want to seamlessly create some new form fields.

To do that, we create a container view and use the dataCollection attribute to associate with the formFields collection. Inside that view, we’re creating a template TextField, that will have some hintText and an optional default value.

Let’s finish up by replacing the contents of index.js with this:

Alloy.Collections.formFields.fetch();

$.getView().open();

When this app launches, it creates a collection called formFields and when index.js opens it binds the collection to the parent view, and adds two TextFields based on the data within the collection.

This is great, but we need to get the data from the fields when the form saves — fixed fields are easy as we can use $.name.value or $.age.value, but how do we get fields that are dynamically created at runtime?

It turns out it’s pretty easy — all we have to do is create some pointers to the newly created fields when they are created.

Let’s update the index.xml to look like this:

<Alloy>
<NavigationWindow>
<Window title="Your Details" class="container">
<RightNavButton>
<Button top="20" onClick="submit">Save</Button>
</RightNavButton>
<View layout="vertical" top="20" id="form">
<TextField hintText="name" id="name"/>
<TextField hintText="age" id="age"/>
<View dataCollection="formFields" layout="vertical">
<TextField onPostlayout="saveFieldReference" name="{id}" hintText="{hintText}" value="{value}"/>
</View>
</View>
</Window>
</NavigationWindow>
</Alloy>

and index.js to this:

Alloy.Collections.formFields.fetch();

function submit(){
    console.log($.name.value);
    console.log($.age.value);
    console.log($.address1.value);
    console.log($.address2.value);
}

function saveFieldReference(e){
    $[e.source.name] = e.source;
}

$.getView().open();

Now, run the app and let’s look at what’s happening here.

Firstly we need a way to access the dynamic fields we’re creating — so we need a reference for them that we can access easily. In this example, I’m using the same $.name method we’re used to using with Alloy when accessing items by an id.

To do this, I’m using the postlayout event of the TextField to tell me when the field is created and add a pointer in the $ object, so in this case $.address1 and $.address2.

Note: I’m using a name attribute to do this — you can’t use id because that’s managed by Alloy.

Because we’re adding the dynamic fields to the $ object, we’re able to mix the fixed and dynamic fields easily, and reference them just like we would normally in Alloy.

You may need to dynamically iterate through the fields instead of referencing them directly in code like this example — in this case, I’d recommend adapting the saveFieldReference function:

function saveFieldReference(e){
  $.formFields =  $.formFields || {};
    $.formFields[e.source.name] = e.source;
}

By doing this, you now have a $.formFields object that you can iterate though. You could also make that an Array if you prefer. Personally I like the object as it’s easier to access an object directly by name.

With this kind of approach, it’s easy to create a single “form” widget or module and have it render simple or even complex forms, with more interesting field definitions by writing less JavaScript code.

This could lend itself to having other forms that could be created from a simple array, or even defined from a remote server!

Alloy data-binding is really powerful, requires little JavaScript and leverages all the power of Alloy — it’s theming, styling and platform-specific exceptions — all of this means simpler, easier-to-read, more maintainable code and less bugs!