Touch 2.0.2 Sencha Docs

Using Nested List in Sencha Touch 2

Nested List is a component in Sencha Touch which provides a miller column interface to navigate between nested sets of data with a clean and easy to use interface.

Ext.define('ListItem', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['text']
    }
});

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items',
    root: {
        items: [
            {
                text: 'Drinks',
                items: [
                    {
                        text: 'Water',
                        items: [
                            { text: 'Still', leaf: true },
                            { text: 'Sparkling', leaf: true }
                        ]
                    },
                    { text: 'Soda', leaf: true }
                ]
            },
            {
                text: 'Snacks',
                items: [
                    { text: 'Nuts', leaf: true },
                    { text: 'Pretzels', leaf: true },
                    { text: 'Wasabi Peas', leaf: true  }
                ]
            }
        ]
    }
});

Ext.create('Ext.NestedList', {
    fullscreen: true,
    store: treeStore
});

Creating a simple Nested List

Creating a simple Nested List is fairly simple. There are only a few important configurations:

Lets look at the at the code needed to create a basic nested list:

Ext.define('ListItem', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['text']
    }
});

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items',
    root: {
        items: [
            {
                text: 'Drinks',
                items: [
                    {
                        text: 'Water',
                        items: [
                            { text: 'Still', leaf: true },
                            { text: 'Sparkling', leaf: true }
                        ]
                    },
                    { text: 'Soda', leaf: true }
                ]
            },
            {
                text: 'Snacks',
                items: [
                    { text: 'Nuts', leaf: true },
                    { text: 'Pretzels', leaf: true },
                    { text: 'Wasabi Peas', leaf: true  }
                ]
            }
        ]
    }
});

Ext.create('Ext.NestedList', {
    fullscreen: true,
    store: treeStore
});

Lets walk through the code:

  • First we define our ListItem model. This is a simple model with 1 field defined which is our text field. This is the only information we need at this point, and it will be displayed in the list for each item.
  • Next we create our treeStore.
  • The first property we pass is the model instance was just defined.
  • Then we define the defaultRootPropertyof the data that will be passed into our treeStore. In our case, this will beitems`.
  • Then we define the root property. This is the data that will be passed into the treeStore.

Tree Store Data

The most confusing part of the above example is definitely the TreeStore and its root/data. Lets look at the code again:

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items',
    root: {
        items: [
            {
                text: 'Drinks',
                items: [
                    {
                        text: 'Water',
                        items: [
                            { text: 'Still', leaf: true },
                            { text: 'Sparkling', leaf: true }
                        ]
                    },
                    { text: 'Soda', leaf: true }
                ]
            },
            {
                text: 'Snacks',
                items: [
                    { text: 'Nuts', leaf: true },
                    { text: 'Pretzels', leaf: true },
                    { text: 'Wasabi Peas', leaf: true  }
                ]
            }
        ]
    }
});

So the first two lines are easy to understand. First we create an instance of a TreeStore and then we give it a model which we defined above. Simple.

defaultRootProperty is where it starts to get a little bit trickier. It is the root field of your data which tells the nested list where the data starts. This is the same whether it is inline JavaScript (like above) or remote data (which we will come to later). This properly also applies to each item in your data, even if it is nested.

The root property is this case is the data for this store. As you can see it is an object, and it only has one property which is items - which is also our defaultRootProperty. Now within each item, we specify a text property which we also defined in our ListItem model above. As you can see the Drinks, Water and Snacks items also have children (using the defaultRootProperty items).

Some of the items also have a leaf property. This means that when a user taps on this item, a detail card will appear (if configured) - but we will leave that until later.

Loading Remote Data

In the above example we simply add some inline data, but when creating real world examples this is very rarely the case. What if we want to load data from a remove JSON file?

It is actually very similar. Lets look at the JSON file we want to load:

{
    "items": [
        {
            "text": "Drinks",
            "items": [
                {
                    "text": "Water",
                    "items": [
                        { "text": "Still", "leaf": true },
                        { "text": "Sparkling", "leaf": true }
                    ]
                },
                { "text": "Soda", "leaf": true }
            ]
        },
        {
            "text": "Snacks",
            "items": [
                { "text": "Nuts", "leaf": true },
                { "text": "Pretzels", "leaf": true },
                { "text": "Wasabi Peas", "leaf": true  }
            ]
        }
    ]
}

As you can see, it is identical to the inline root properly we defined above. So lets look at what is different when we create the TreeStore:

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items'
    proxy: {
        type: 'ajax',
        url: 'data.json'
    }
});

We create the store using Ext.create and then we set the model and defaultRootProperty properties just like before, but then, this time, we set a proxy. This property tells the TreeStore to load its data using a specify proxy. In this case we give it a type of ajax (because it is loading a remote file) and then url of where the JSON file is. Simple.

Detail Cards

A detail card is what shows (if configured) if a user taps on an item that is a leaf (defined in the data the TreeStore loads - example above). It can be any type of Component, which means it is very customizable. You can set the detail card using the detailCard config in the nested list configuration. Lets look a simple example:

Ext.define('ListItem', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['text']
    }
});

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items',
    root: {
        items: [
            {
                text: 'Drinks',
                items: [
                    {
                        text: 'Water',
                        items: [
                            { text: 'Still', leaf: true },
                            { text: 'Sparkling', leaf: true }
                        ]
                    },
                    { text: 'Soda', leaf: true }
                ]
            },
            {
                text: 'Snacks',
                items: [
                    { text: 'Nuts', leaf: true },
                    { text: 'Pretzels', leaf: true },
                    { text: 'Wasabi Peas', leaf: true  }
                ]
            }
        ]
    }
});

Ext.create('Ext.NestedList', {
    fullscreen: true,
    store: treeStore,
    detailCard: {
        html: 'You are viewing the detail card!'
    }
});

As you can see, when you tap on one of the leaf items, the detail card is visible. So lets look at the code:

Ext.create('Ext.NestedList', {
    fullscreen: true,
    store: treeStore,
    detailCard: {
        html: 'You are viewing the detail card!'
    }
});

We simply set the detailCard to be an object, with a config of html set. This object will automatically be transformed into a Component (just like when you call add on a Container).

What if we want to set the data in the detailCard depending on what item we tap? Lets look at how we do that:

Ext.create('Ext.NestedList', {
    fullscreen: true,
    store: treeStore,
    detailCard: {
        html: 'You are viewing the detail card!'
    },
    listeners: {
        leafitemtap: function(nestedList, list, index, target, record) {
            var detailCard = nestedList.getDetailCard();
            detailCard.setHtml('You selected: ' + record.get('text'));
        }
    }
});

Look how we added the listeners config. We do this because we want to listen to the leafitemtap event, which is called when any leaf item is... tapped. :)

When that event is fired, it is passed a number of arguments:

  • nestedList - this is a reference to the nested list instance.
  • list - this is a reference to the last list, where the item with the leaf config was tapped.
  • index - the index of the record in the list store.
  • target - the element which was tapped.
  • record - the record of the item which was tapped.

Now that we know when someone taps a leaf item, and we know which record was tapped, we can update the detail card with some data.

To get the detailCard, we can use the Ext.NestedList.getDetailCard method, and then with that we can update the html config of our detailCard.

Lets look at an example of this working:

Ext.define('ListItem', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['text']
    }
});

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items',
    root: {
        items: [
            {
                text: 'Drinks',
                items: [
                    {
                        text: 'Water',
                        items: [
                            { text: 'Still', leaf: true },
                            { text: 'Sparkling', leaf: true }
                        ]
                    },
                    { text: 'Soda', leaf: true }
                ]
            },
            {
                text: 'Snacks',
                items: [
                    { text: 'Nuts', leaf: true },
                    { text: 'Pretzels', leaf: true },
                    { text: 'Wasabi Peas', leaf: true  }
                ]
            }
        ]
    }
});

Ext.create('Ext.NestedList', {
    fullscreen: true,
    store: treeStore,
    detailCard: {
        html: 'You are viewing the detail card!'
    },
    listeners: {
        leafitemtap: function(nestedList, list, index, target, record) {
            var detailCard = nestedList.getDetailCard();
            detailCard.setHtml('You selected: ' + record.get('text'));
        }
    }
});

Detail Container

Nested List has a detailContainer configuration which is used to specify the container of the detailCard. This is used when you want the detailCard to be in another container, which is often the case when the screen size is bigger than a normal phone.

Lets look at an example of this in action:

Ext.define('ListItem', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['text']
    }
});

var treeStore = Ext.create('Ext.data.TreeStore', {
    model: 'ListItem',
    defaultRootProperty: 'items',
    root: {
        items: [
            {
                text: 'Drinks',
                items: [
                    {
                        text: 'Water',
                        items: [
                            { text: 'Still', leaf: true },
                            { text: 'Sparkling', leaf: true }
                        ]
                    },
                    { text: 'Soda', leaf: true }
                ]
            },
            {
                text: 'Snacks',
                items: [
                    { text: 'Nuts', leaf: true },
                    { text: 'Pretzels', leaf: true },
                    { text: 'Wasabi Peas', leaf: true  }
                ]
            }
        ]
    }
});

var detailContainer = Ext.create('Ext.Container', {
    layout: 'card',
    flex: 1
});

var nestedList = Ext.create('Ext.NestedList', {
    store: treeStore,
    detailContainer: detailContainer,
    detailCard: true,
    listeners: {
        leafitemtap: function(nestedList, list, index, target, record) {
            var detailCard = nestedList.getDetailCard();
            detailCard.setHtml('You selected: ' + record.get('text'));
        }
    },
    flex: 1
});

Ext.Viewport.add({
    layout: 'hbox',
    items: [
        nestedList,
        detailContainer
    ]
});

As you can see, we have 2 items in the Viewport now.

Setting the configuration is very simple. You just give it a reference to the container you want to use as the detail container:

var detailContainer = Ext.create('Ext.Container', {
    layout: 'card'
});

var nestedList = Ext.create('Ext.NestedList', {
    store: treeStore,
    detailCard: true,
    detailContainer: detailContainer
});

If you do not specify a detailContainer, the detailCard will be added into the nested list.