Touch 2.0.2 Sencha Docs

Using DataViews in Sencha Touch 2

DataView makes it easy to create lots of components dynamically, usually based off a Store. It's great for rendering lots of data from your server backend or any other data source and is what powers components like Ext.List.

Use DataView whenever you want to show sets of the same component many times, for examples in apps like these:

  • List of messages in an email app
  • Showing latest news/tweets
  • Tiled set of albums in an HTML5 music player

Creating a Simple DataView

At its simplest, a DataView is just a Store full of data and a simple template that we use to render each item:

var touchTeam = Ext.create('Ext.DataView', {
    fullscreen: true,

    store: {
        fields: ['name', 'age'],
        data: [
            {name: 'Jamie Avins',  age: 100},
            {name: 'Rob Dougan',   age: 21},
            {name: 'Tommy Maintz', age: 24},
            {name: 'Jacky Nguyen', age: 24},
            {name: 'Ed Spencer',   age: 26}
        ]
    },

    itemTpl: '{name} is {age} years old'
});

Here we just defined everything inline so it's all local with nothing being loaded from a server. For each of the 5 data items defined in our Store, DataView will render a Component and pass in the name and age data. The component will use the tpl we provided above, rendering the data in the curly bracket placeholders we provided.

Because DataView is integrated with Store, any changes to the Store are immediately reflected on the screen. For example, if we add a new record to the Store it will be rendered into our DataView:

touchTeam.getStore().add({
    name: 'Abe Elias',
    age: 33
});

We didn't have to manually update the DataView, it's just automatically updated. The same happens if we modify one of the existing records in the Store:

touchTeam.getStore().getAt(0).set('age', 42);

This will get the first record in the Store (Jamie), change the age to 42 and automatically update what's on the screen.

Loading data from a server

We often want to load data from our server or some other web service so that we don't have to hard code it all locally. Let's say we want to load all of the latest tweets about Sencha Touch into a DataView, and for each one render the user's profile picture, user name and tweet message. To do this all we have to do is modify the store and itemTpl a little:

Ext.create('Ext.DataView', {
    fullscreen: true,

    store: {
        autoLoad: true,
        fields: ['from_user', 'text', 'profile_image_url'],

        proxy: {
            type: 'jsonp',
            url: 'http://search.twitter.com/search.json?q=Sencha Touch',

            reader: {
                type: 'json',
                root: 'results'
            }
        }
    },

    itemTpl: '<img src="{profile_image_url}" /><h2>{from_user}</h2><p>{text}</p>'
});

The DataView no longer has hard coded data, instead we've provided a Proxy, which fetches the data for us. In this case we used a JSON-P proxy so that we can load from Twitter's JSON-P search API. We also specified the fields present for each tweet, and used Store's autoLoad configuration to load automatically. Finally, we configured a Reader to decode the response from Twitter, telling it to expect JSON and that the tweets can be found in the 'results' part of the JSON response.

The last thing we did is update our template to render the image, twitter username and message. All we need to do now is add a little CSS to style the list the way we want it...

Styling a DataView

You may have realized by now that although our DataView is displaying data from our Store, it doesn't have any default styling. This is by design, but adding custom CSS is very simple. DataView has two configurations so you can target your custom CSS to your view: baseCls and itemCls. baseCls is used to add a className around the outer element of the DataView. The itemCls you provide is added onto each item that is rendered into our DataView.

If you do not specify a itemCls, it will automatically take the baseCls configuration (which defaults to x-dataview) and prepend -item. So each item would have a className of x-dataview-item.

But we before we add that configuration, we need to create our custom CSS. Here is a simple example below:

.my-dataview-item {
    background: #ddd;
    padding: 1em;
    border-bottom: 1px solid #ccc;
}
.my-dataview-item img {
    float: left;
    margin-right: 1em;
}
.my-dataview-item h2 {
    font-weight: bold;
}

Once we have that complete, we can go back to our previous Twitter example and add the basecls configuration:

Ext.create('Ext.DataView', {
    fullscreen: true,

    store: {
        autoLoad: true,
        fields: ['from_user', 'text', 'profile_image_url'],

        proxy: {
            type: 'jsonp',
            url: 'http://search.twitter.com/search.json?q=Sencha Touch',

            reader: {
                type: 'json',
                root: 'results'
            }
        }
    },

    itemTpl: '<img src="{profile_image_url}" /><h2>{from_user}</h2><p>{text}</p>',

    baseCls: 'my-dataview'

    //As described above, we don't need to set itemCls in most cases as it will already add a className
    //generated from the baseCls to each item.
    //itemCls: 'my-dataview-item'
});

Component DataView

Above we created our DataView with an itemTpl, which means each item is rendered from a template. However, sometimes you need each item to be a component so you can provide a rich UI for your users. In Sencha Touch 2, we introduced the useComponents configuration which allows you to do just that.

Creating a component DataView is very similiar to creating a normal template based DataView like above, however you must define the item view used when rendering each item in your list.

Ext.define('MyListItem', {
    extend: 'Ext.dataview.component.DataItem',
    requires: ['Ext.Button'],
    xtype: 'mylistitem',

    config: {
        nameButton: true,

        dataMap: {
            getNameButton: {
                setText: 'name'
            }
        }
    },

    applyNameButton: function(config) {
        return Ext.factory(config, Ext.Button, this.getNameButton());
    },

    updateNameButton: function(newNameButton, oldNameButton) {
        if (oldNameButton) {
            this.remove(oldNameButton);
        }

        if (newNameButton) {
            this.add(newNameButton);
        }
    }
});

Above is an example of how to define your component based DataView item component. There are a few important notes about this:

  • We must extend Ext.dataview.component.DataItem for each item. This is an abstract class which handles the record handling for each item.
  • Below the extend we require Ext.Button. This is simply because we are going to insert a button inside our item component.
  • We then specify the xtype for this item component.
  • Inside our config block we define nameButton. This is a custom configuration we add to this component which will be transformed into a button by the class system. We set it to true by default, but this could also be a configuration block. This configuration will automatically generate getters and setters for our nameButton.
  • Next we define the dataMap. The dataMap is a map between the data of a record and this view. The getNameButton is the getter for the instance you want to update; so in this case we want to get the nameButton configuration of this component. Then inside that block we give it the setter for that instance; in this case being setText and give it the field of the record we are passing. So, once this item component gets a record it will get the nameButton and then call setText with the name value of the record.
  • Then we define the apply method for our nameButton. The apply method uses Ext.factory to transform the configuration passed into an instance of Ext.Button. That instance is then returned, which will then cause updateNameButton to be called. The updateNameButton method simply removes the old nameButton instance if it exists, and adds the new nameButton instance if it exists.

Now we have created the item component, we can create our component DataView, similar to how we done it before.

Ext.create('Ext.DataView', {
    fullscreen: true,

    store: {
        fields: ['name', 'age'],
        data: [
            {name: 'Jamie Avins',  age: 100},
            {name: 'Rob Dougan',   age: 21},
            {name: 'Tommy Maintz', age: 24},
            {name: 'Jacky Nguyen', age: 24},
            {name: 'Ed Spencer',   age: 26}
        ]
    },

    useComponents: true,
    defaultType: 'mylistitem'
});

There are two key additions. Firstly, we add the useComponents configuration and set it to true. Secondly, we set the defaultType configuration to our item component mylistitem. This tells the DataView to use our defined item component as the view for each item.

Now if we run this code together, we can see the component DataView in action.

Ext.define('MyListItem', {
    extend: 'Ext.dataview.component.DataItem',
    requires: ['Ext.Button'],
    xtype: 'mylistitem',

    config: {
        nameButton: true,

        dataMap: {
            getNameButton: {
                setText: 'name'
            }
        }
    },

    applyNameButton: function(config) {
        return Ext.factory(config, Ext.Button, this.getNameButton());
    },

    updateNameButton: function(newNameButton, oldNameButton) {
        if (oldNameButton) {
            this.remove(oldNameButton);
        }

        if (newNameButton) {
            this.add(newNameButton);
        }
    }
});

Ext.create('Ext.DataView', {
    fullscreen: true,

    store: {
        fields: ['name', 'age'],
        data: [
            {name: 'Jamie Avins',  age: 100},
            {name: 'Rob Dougan',   age: 21},
            {name: 'Tommy Maintz', age: 24},
            {name: 'Jacky Nguyen', age: 24},
            {name: 'Ed Spencer',   age: 26}
        ]
    },

    useComponents: true,
    defaultType: 'mylistitem'
});

The great thing about this is the flexibilty it can add to your dataviews. Each item component has access to its own record, so you can do just about anything with it.

Below we add a event listener to the tap event on our nameButton, which will then alert the user with the age of the selected person.

Ext.define('MyListItem', {
    //...

    updateNameButton: function(newNameButton, oldNameButton) {
        if (oldNameButton) {
            this.remove(oldNameButton);
        }

        if (newNameButton) {
            // add an event listeners for the `tap` event onto the new button, and tell it to call the onNameButtonTap method
            // when it happens
            newNameButton.on('tap', this.onNameButtonTap, this);

            this.add(newNameButton);
        }
    },

    onNameButtonTap: function(button, e) {
        var record = this.getRecord();

        Ext.Msg.alert(
            record.get('name'), // the title of the alert
            "The age of this person is: " + record.get('age') // the message of the alert
        );
    }
});

And when we add this code to our above example, we get the finished result:

Ext.define('MyListItem', {
    extend: 'Ext.dataview.component.DataItem',
    requires: ['Ext.Button'],
    xtype: 'mylistitem',

    config: {
        nameButton: true,

        dataMap: {
            getNameButton: {
                setText: 'name'
            }
        }
    },

    applyNameButton: function(config) {
        return Ext.factory(config, Ext.Button, this.getNameButton());
    },

    updateNameButton: function(newNameButton, oldNameButton) {
        if (oldNameButton) {
            this.remove(oldNameButton);
        }

        if (newNameButton) {
            // add an event listeners for the `tap` event onto the new button, and tell it to call the onNameButtonTap method
            // when it happens
            newNameButton.on('tap', this.onNameButtonTap, this);

            this.add(newNameButton);
        }
    },

    onNameButtonTap: function(button, e) {
        var record = this.getRecord();

        Ext.Msg.alert(
            record.get('name'), // the title of the alert
            "The age of this person is: " + record.get('age') // the message of the alert
        );
    }
});

Ext.create('Ext.DataView', {
    fullscreen: true,

    store: {
        fields: ['name', 'age'],
        data: [
            {name: 'Jamie Avins',  age: 100},
            {name: 'Rob Dougan',   age: 21},
            {name: 'Tommy Maintz', age: 24},
            {name: 'Jacky Nguyen', age: 24},
            {name: 'Ed Spencer',   age: 26}
        ]
    },

    useComponents: true,
    defaultType: 'mylistitem'
});