O'Reilly logo

Sencha Touch 2 Up and Running by Adrian Kosmaczewski

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Data

Sencha Touch provides an incredible support for data-bound applications. Whether the information is stored locally or on a remote server, the framework allows developers to quickly prototype and develop applications consuming complex data, in XML or JSON formats.

This chapter will provide an introduction to the basic concepts of data management in Sencha Touch apps. You’ll learn in detail about stores and proxies, as well as about different data-bound controls such as lists and data views.

Model Classes

The basic component of data bound applications is, without doubt, the Ext.data.Model class. Every business component of a Sencha Touch application, like for example “customers,” “orders,” or “invoices,” is represented in Sencha Touch as a subclass of the Ext.data.Model class.

Sample application

To explain the concepts of this chapter, we are going to use a generic MVC application that consumes data in JSON or XML format. The data is produced by a set of PHP files, reading a rather large MySQL database containing random contact information. Users can choose whether to load data in JSON or XML format. Figure 4-1 shows a screenshot of the application.

This PHP application is located in the Chapter_04_Data/Server folder of the source code files provided with book, available in Github.

Data sample application
Figure 4-1. Data sample application

The following code provides the definition of a Person class:

Ext.define('Chapter4Data.model.Person', {
    extend: 'Ext.data.Model',
    config: {

        fields: [{
            name: 'entryId', type: 'int'
        }, {
            name: 'firstName', type: 'string'
        }, {
            name: 'lastName', type: 'string'
        }, {
//
// ... snip ...
//
        }, {
            name: 'enabled', type: 'boolean', defaultValue: true
        }, {
            name: 'createdOn',  type: 'date'
        }],

        idProperty: 'entryId',

        belongsTo: 'Company',

        hasMany: {
            model: 'Orders', name: 'orders'
        },

        validations: [{
            type: 'presence', field: 'firstName'
        }, {
            type: 'presence', field: 'lastName'
        }, {
            type: 'length', field: 'description', min: 15
        }, {
            type: 'inclusion', field: 'role', list: ['User', 'Admin']
        }]
    }
});

As previously shown, a model class definition consists of two major configuration options:

  1. The list of the data fields that each instance of this class must store; they are represented as literal objects with name and type entries.
  2. An idProperty entry, indicating the field that is used to uniquely identify each entry in a store. This field is extremely important, since as we are going to see in a minute, Sencha Touch stores use this information pervasively.

You can also specify other parameters together with the name and the type of a field; for example, developers can add information about default values using the defaultValue property; other useful properties are allowNull, sortDir (which can take the values ASC or DESC), or dateFormat (for date fields).

Finally, you can also specify model relationships, as well as validation logic in your models, which we’ll learn more about in the following sections.

Model Field Types

Sencha Touch models can have fields with the following types:

  • int
  • string
  • float
  • boolean
  • date

Every field in a model class is an instance of the Ext.data.Field class.

Associations

This is the part of the book where Ruby on Rails developers will feel at home. Sencha Touch allows us to define relationships among model classes, using two very common configuration properties: belongsTo and hasMany, which are self-explanatory.

Thanks to associations, developers can chain objects using these predefined semantics, creating code that is easy to read and which, thanks to stores and proxies, can be mapped to server-side entities as well:

var person = Ext.create('Chapter4Data.model.Person')
person.set('firstName', 'Aaron');
person.set('lastName', 'Schwartz');

var company = person.getCompany(function (company) {
    console.log('Here is the company information: ' + company.get('name'));
});

person.orders().add({
    item: 234,
    value: 699.00,
    note: 'To be delivered next month'
});

person.orders().sync();

The preceding script shows that the belongsTo relationship automatically generates a getCompany() function, which works asynchronously; you have to provide a callback to it, to be executed as soon as the data is retrieved. This is required to avoid blocking the JavaScript engine of the browser while the data is fetched, potentially from a remote location.

Validations

Finally, models can carry their own validation logic. Using the validations key in the configuration of a model, we can ensure that new instances are valid or not at any given time, providing a unified, simple API for developers.

There are many kinds of possible validations, all defined as functions in the Ext.data.Validations singleton object:

  • email, used to check the proper formatting of email addresses
  • inclusion and exclusion, which ensure that the value provided is included or not included in a predefined list
  • format, making sure that a particular string conforms to some regular expression
  • length, which as the name implies verifies the length of a particular string field
  • presence, used to check the existence of a particular value in a model instance

During runtime, applications can use the validate() method on model instances to trigger all the required validations and to return an object containing all the information about any errors found during the procedure:

var person = Ext.create('Chapter4Data.model.Person')
person.set('firstName', 'Aaron');
person.set('lastName', 'Schwartz');

// Perform validation
var validationResults = person.validate();

console.log('Is valid? ' + validationResults.isValid());
console.log('All problems: ' + validationResults.items);
console.log('Specific problem: ' + validationResults.getByField('email'));

Stores and Proxies

Models, by themselves, are completely oblivious to any kind of storage mechanism; they just define in-memory individual data structures, with types, validation rules, and mutual interrelationships.

This is where stores and proxies come in; they provide ways to abstract different concepts that are pervasive in most data-bound apps, providing a plug-and-play API that isolates applications from the underlying data storage.

  • Stores can be thought of as managed sets of model instances; for example, you can have a store containing all the instances of the Person class in your application. There are two basic types of stores: linear and hierarchical.
  • Proxies, on the other hand, encapsulate the logic required to connect to a local or remote data source, such as the browser’s localStorage object, or a REST web service elsewhere on the network.

There are two kinds of proxies available in Sencha Touch: local and remote. We are going to learn more about these two groups in the following sections.

Local Proxies

Local proxies are used by stores to keep sets of model instances in the local browser. There are three kinds of local proxies:

  • Memory
  • LocalStorage
  • SessionStorage

Let’s study each of these in detail.

Memory

The memory proxy, represented by the Ext.data.proxy.Memory class, is used only as a simple in-memory storage option that is not persisted across page refreshes.

The following code shows how to create a very simple memory proxy:

Ext.define('Chapter4Data.store.MemoryPeopleStore', {
    extend: 'Ext.data.Store',
    xtype: 'peoplestore',
    config: {
        model: 'Chapter4Data.model.Person',
        storeId: 'peopleStore',
        autoLoad: true,
        sorters: [{
            property: 'firstName',
            direction: 'ASC'
        }],
        proxy: {
            type: 'memory',
            reader: {
                type: 'json',
                root: 'people'
            }
        }
    }
});

You can use the Memory proxy to keep small amounts of in-memory temporary data. However, the SessionStorage proxy (described later in this section) is a better choice for larger quantities of temporary data.

LocalStorage

One of the most common proxies available in Sencha Touch is represented by the Ext.data.proxy.LocalStorage class, also identified by its localstorage xtype. This proxy uses the HTML5 localStorage functionality available in modern browsers, a simple key-value storage facility that accepts only strings for both keys and values. This proxy is able to serialize and deserialize complex objects into strings, transparently and seamlessly.

The code that follows shows a very simple example of a localstorage proxy:

Ext.define('Chapter4LocalStorage.store.PeopleStore', {
    extend: 'Ext.data.Store',
    xtype: 'peoplestore',
    config: {
        model: 'Chapter4LocalStorage.model.Person',
        storeId: 'peopleStore',
        autoLoad: true,
        autoSync: true,
        sorters: [{
            property: 'firstName',
            direction: 'ASC'
        }],
        proxy: {
            type: 'localstorage',
            id: 'peopleproxy'
        }
    }
});

The contents of the localStorage store are preserved across browser restarts, usually accepting a maximum of 5 MB of data. This might be the most useful local proxy available.

SessionStorage

Finally, the third local proxy available is the Ext.data.proxy.SessionStorage class, also identified by its xtype sessionstorage. This proxy uses the HTML5 sessionStorage functionality, which is very similar to the localStorage described in this chapter in terms of API. However, remember that the contents of the sessionStorage proxy are erased as soon as the browser window is closed; so in a certain way it is closer in behavior to the Memory proxy we have seen before.

The following code shows a very simple example of a sessionstorage proxy:

Ext.define('Chapter4LocalStorage.store.SessionPeopleStore', {
    extend: 'Ext.data.Store',
    xtype: 'peoplestore',
    config: {
        model: 'Chapter4LocalStorage.model.Person',
        storeId: 'peopleStore',
        autoLoad: true,
        autoSync: true,
        sorters: [{
            property: 'firstName',
            direction: 'ASC'
        }],
        proxy: {
            type: 'sessionstorage',
            id: 'peopleproxy'
        }
    }
});

The SessionStorage proxy stores its contents in disk, which makes it a better option than the Memory proxy for storing large amounts of temporary data.

Remote Proxies

Remote proxies are used to connect stores to remote data sources, located elsewhere on the network. They encapsulate all the logic required to perform typical “CRUD” (create, read, update, delete) operations on stores, and to map those operations to their corresponding HTML requests.

The remote proxies available in Sencha Touch are the following:

  • Ajax
  • JsonP
  • Rest

The following sections will provide some insight about these proxies.

Ajax

The ajax proxy, represented by the Ext.data.proxy.Ajax class, is used to access data sources located in the same domain as the Sencha Touch application. This proxy encapsulates requests performed using the XMLHTTPRequest component, available in all modern browsers, and allowing developers to perform asynchronous HTTP requests from a web page.

Same domain policy

Remember that browsers are bound to the “same domain policy”; this limits the use of the Ext.data.proxy.Ajax proxy to web services located in the same domain as your Sencha Touch application.

The following code shows how to create a store using a very simple ajax proxy:

Ext.define('Chapter4Data.store.PeopleStore', {
    extend: 'Ext.data.Store',
    xtype: 'peoplestore',
    config: {
        model: 'Chapter4Data.model.Person',
        storeId: 'peopleStore',
        autoLoad: true,
        sorters: [{
            property: 'firstName',
            direction: 'ASC'
        }],
        proxy: {
            type: 'ajax',
            url : 'Server/index.php?format=json'
        }
    }
});

By default, ajax proxies can read data in JSON format; however, they can very easily read XML data as well, and for that, developers only need to specify a reader of type Ext.data.reader.Xml, whose xtype is simply xml:

{
    type: 'ajax',
    url : 'Server/index.php?format=xml',
    reader: {
        type: 'xml',
        rootProperty: 'data',
        record: 'person'
    }
}

As previously shown, the xml reader requires you to specify the rootProperty and the record properties; in this case, the values are data and person respectively, because the XML feed being read has the following structure:

<data>
    <person>
        <entryId>1857</entryId>
        <firstName>Jennifer</firstName>
        <lastName>Hudson</lastName>
        <phone>1 66 871 1728-5906</phone>
        <email>est.arcu.ac@sem.edu</email>
        <address>7509 Eleifend. Rd.</address>
        <city>Hope</city>
        <zip>A5C 9Z5</zip>
        <state>Gld.</state>
        <country>Mexico</country>
        <description>
            dui, semper et, lacinia vitae, sodales at, velit.
        </description>
        <password>FAB39FTL9MV</password>
        <createdOn>2010-07-25</createdOn>
        <modifiedOn>2009-12-19</modifiedOn>
    </person>
    <person>
        <!-- ... -->
    </person>
</data>

JsonP

To overcome the same-domain policy of the ajax proxy, the Ext.data.proxy.JsonP proxy class (usually referred to as jsonp) can be used. When this proxy is activated, it injects a <script> tag to the current DOM, with its src attribute pointing to the remote URL of the data service.

Note

The name JsonP comes from the expression “JSON with Padding,” because the data served by the remote URL is “padded” with a function call, which enables it to be “activated” and consumed by the current JavaScript application.

The biggest limitation of this proxy is that it can execute only GET requests; however, they can be executed on any remote domain, not only on the current domain of the web application. The following code shows a simple example of how to create a jsonp proxy connecting to Twitter, in order to get the latest tweets about “argentina,” for example:

{
    type: 'jsonp',
    url : 'http://search.twitter.com/search.json?q=argentina',
    reader: {
        type: 'json',
        rootProperty: 'results'
    }
}

Rest

The final remote proxy we are going to learn about is the rest proxy type, which is represented by the Ext.data.proxy.Rest class. This is a subclass of the Ext.data.proxy.Ajax class (which means that it can work only under the umbrella of the same-domain policy) and provides a means to easily connect to REST web services.

REST web services

REST stands for “REpresentational State Transfer” and is currently one of the strongest alternatives to Simple Object Access Protocol (SOAP) web services. Many server-side technologies allow developers to create REST web services, returning different kinds of data in formats such as XML and JSON; to name a few, Ruby on Rails, ASP.NET MVC, or even some PHP frameworks allow developers to create such services very easily.

The rest proxy can be configured to perform requests using different HTTP verbs for every CRUD operation:

  • Create: POST
  • Read: GET
  • Update: PUT
  • Delete: DELETE

REST proxies can also be configured, through the api property, to use different URLs for each of these operations, if required by the server-side technology being used.

The following code snippet shows the creation of a very simple rest proxy attached directly to a model.

Ext.define('Chapter4Data.model.Person', {
    extend: 'Ext.data.Model',
    config: {
        fields: [{
            name: 'id',
            type: int
        }, {
            // ... snip ...
        }],

        proxy: {
            type: 'rest',
            url: '/users'
        }
    }
});

Once a model has been defined this way, you can manipulate its instances directly in your controllers and just use the save() method, which will automatically push any changes to the remote web server automatically:

var person = Ext.create('Chapter4Data.model.Person', {
    firstName: 'John',
    lastName: 'Smith'
});

// This will trigger a POST request to "/users"
person.save({
    success: function (person) {
        console.log('new person has been sent to the server!');
    }
});

The dictionary passed to the save() function includes a set of optional callbacks to be executed asynchronously as soon as the network operation completes.

What about SOAP web services?

Many companies have invested heavily in web services using the SOAP technology, which received a strong push by Microsoft and other vendors in the early 2000s. To help these companies leverage the power of the platform, the Sencha team has announced the availability of a SOAP proxy, but (at least at the moment of this writing) it is only available to companies and teams who have purchased a “Sencha Complete: Team” package. Also, it is only available for desktop applications developed with Ext JS 4.1.2.

Store Types

While proxies encapsulate the logic required to read and write information, stores are designed to hold data and to make it available to other components, most notably data-bound components such as lists.

The two most common kinds of stores are linear ones, usually just referred to as stores, and hierarchical stores, usually represented by tree stores. We are going to learn more about these two kinds of stores in the following sections.

The StoreManager Singleton

Sencha Touch MVC applications usually contain one or more stores, and they are usually referred to from different locations: event handlers, controllers, or even from the Ext.application() function call.

To make it easier for developers to work with several stores in the same application, and to be able to refer uniquely to each one of them, the Ext.data.StoreManager singleton comes in handy. It provides a simple lookup() method which takes an ID as parameter; this ID must be a string, and it is the same one provided in the storeId configuration property.

For convenience, the Ext.data.StoreManager.lookup() function is aliased to Ext.getStore(), which is a commonly used alternative, particularly in the code samples for this book.

Linear stores

The most common kind of store is the linear one, usually represented by an instance of the Ext.data.Store class. In the grand scheme of a Sencha Touch MVC application, stores are associated to a model, and stores contain a proxy object. Proxies are usually associated with a Reader and a Writer object, which contain the logic required to serialize back and forth model objects from the transport mechanism used.

Let’s take a look at a simple example of a store and a proxy.

Ext.define('Chapter4Data.store.PeopleStore', {
    extend: 'Ext.data.Store',
    xtype: 'peoplestore',
    config: {
        model: 'Chapter4Data.model.Person',
        storeId: 'peopleStore',
        autoLoad: true,
        sorters: [{
            property: 'firstName',
            direction: 'ASC'
        }],
        proxy: {
            type: 'ajax',
            url : 'Server/index.php?format=json'
        }
    }
});

In the preceding code, we define a class named Chapter4Data.store.PeopleStore which is associated to the Person model we have defined previously. We also define some key properties of our store:

  • storeId provides a unique string that can be used to retrieve this store instance from anywhere in the application, using Ext.data.StoreManager.lookup() or its alias Ext.getStore().
  • autoLoad specifies that as soon as this object is created and initialized, it will attempt to connect to the remote (or local) data source automatically. There is a similar property called autoSync that can be used to push back to the proxy object any changes that have been made in the store at runtime.
  • sorters provides the information required to sort the information on the store automatically.
  • Finally, proxy contains the definition of the proxy object associated to the store. In this case, it’s an ajax kind of proxy, whose url is specified immediately after.

One of the most interesting characteristics of the association between stores and proxies is that they can be composed at runtime; a store can change its proxy dynamically, and thus “talk” to a different server or storage mechanism as required; in the following code we see such a behavior taking place.

switchFormat: function (segmentedButton, button, isPressed, eOpts) {
    // Get a reference to the store
    var store = Ext.getStore('peopleStore');

    // This is required because the "Ext.SegmentedButton" class
    // calls its event handler once for each button; we are only
    // interested in the event pertaining to the pressed button:
    if (isPressed) {
        var format = button.getText();
        var newProxy = null;

        // If the user selects "JSON," use an AJAX proxy
        // with a JSON reader (the default option)
        if (format === 'JSON') {
            newProxy = {
                type: 'ajax',
                url : 'Server/index.php?format=json'
            };
        }

        // Otherwise, use an XML reader
        else if (format === 'XML') {
            newProxy = {
                type: 'ajax',
                url : 'Server/index.php?format=xml',
                reader: {
                    type: 'xml',
                    rootProperty: 'data',
                    record: 'person'
                }
            };
        }

        // Set the proxy in the store and reload!
        store.setProxy(newProxy);
        store.load();
    }
}

Object composition at work!

Hierarchical stores

The other kind of store is the hierarchical store, usually represented by an instance of the Ext.data.TreeStore class, whose xtype is tree.

Tree stores have their origin in the ExtJS framework, where they are used to represent visual tree nodes in UI widgets; however, given that Sencha Touch is a touchscreen framework, and that common tree-like UIs are hard to represent and manipulate with fingers, tree stores are usually associated with the Ext.dataview.NestedList class, which will be described in detail.

For the moment, suffice to show the following tree store definition:

store: {
    type: 'tree',
    fields: [{                        // 1
        name: 'text',
        type: 'string'
    }],
    defaultRootProperty: 'team',      // 2
    sorters: 'text',                  // 3
    root: {
        text: 'Teams',
        team: [{
            text: 'Finance',
            team: [{
                text: 'Alma Boyle',
                leaf: true
            }]                        // 4
        }, {
            text: 'Accounting',
            team: [{
                text: 'Fletcher Herbert',
                leaf: true
            }, {
                text: 'Elmo Irwin',
                leaf: true
            }]
        }, {
            text: 'Human Resources',
            team: [{
                text: 'Felicia Gray',
                leaf: true
            }, {
                text: 'Petra Ferguson',
                leaf: true
            }]
        }]
    }
}
1

Instead of defining a model property that points to some subclass of Ext.data.Model, we can simply specify the names (eventually also the types) of the fields of each data item.

2

The defaultRootProperty specifies the name of the key, in each data node, that contains child items. If not specified, the tree store will use the value children.

3

Specifying the sorters property here will automatically provide the store values in an ordered fashion.

4

The root property contains the raw hierarchical data of each node. Pay attention to the fact that each node has both a text property (used for sorting and displaying) and a teams property, previously defined as the defaultRootProperty.

We are going to see a detailed example of use of a tree store when learning about the Ext.dataview.NestedList class.

Data-Bound Controls

One of the most important uses of stores is to be paired to data-bound view components, such as a list. Whenever a data-bound component is shown on the screen, it associates in such a way to its store that any change in the underlying data will be reflected automatically in the visual component. Developers do not need to call any refresh() method to see their changes; as soon as a model instance is changed in the store, the associated view component is updated automatically.

The three data-bound controls available in Sencha Touch are the following:

  • DataView
  • List
  • NestedList

We are going to learn more about these in the following sections.

DataView

The Ext.dataview.DataView class (also known by its xtype dataview) is the simplest of all the data-bound controls. It can be thought of as a very simple template engine, which takes data from a store as a parameter and renders that data according to a string template.

The following example shows a very simple dataview loading some tweets about Argentina:

Ext.define('Chapter4DataViews.view.DataViewDemo', {
    extend: 'Ext.dataview.DataView',
    xtype: 'dataviewdemo',
    config: {
        xtype: 'dataview',
        title: 'DataView Demo',
        itemTpl: '<div class="tweetdiv"><div class="avatar">
        <img src="{profile_image_url}" /></div><div class="text">
        <p class="username">{from_user_name}</p><p class="tweet">{text}
        </p><p class="date">{[Ext.Date.format(values.created_at, "d.m.Y, H:i")]}
        </p></div></div>', // 1
        store: {
            autoLoad: true,
            fields: [{
                name: 'id', type: 'int'
            }, {
                name: 'profile_image_url', type: 'string'
            }, {
                name: 'from_user', type: 'string'
            }, {
                name: 'from_user_name', type: 'string'
            }, {
                name: 'text', type: 'string'
            }, {
                name: 'created_at', type: 'date',
                dateFormat: 'D, j M Y H:i:s O' // 2
            }],
            proxy: {
                type: 'jsonp',
                url: 'http://search.twitter.com/search.json?q=argentina',
                // 3
                reader: {
                    type: 'json',
                    rootProperty: 'results'
                }
            }
        }
    }
});
1

The itemTpl property specifies a string or an Ext.XTemplate object, used to render each item in the data store. Pay attention to the fact that the data is formatted using the Ext.Date.format() function, inside the template code. Even mathematical expressions can be used in templates.

2

Here we specify the format of each created_at field, as returned by Twitter. Sencha Touch will take care of transforming each one of these values into a proper JavaScript Date object for us.

3

This store is attached to a very simple jsonp proxy, downloading the latest tweets about Argentina.

The itemTpl property provides a rather complex template, which can be styled as follows to get the result shown in Figure 4-2:

div.tweetdiv {
    float: left; width: 300px; padding: 10px 10px 0px 10px; margin: 10px;
    border-color: lightgray; border-width: 1px;
    border-style: solid; min-height: 150px;
}

div.avatar {
    float: left; width: 50px; margin-left: 10px;
    margin-right: 10px; margin-top: 5px;
}

div.text {
    width: 200px; float: left;
}

p.username {
    font-weight: bold; font-size: 0.8em;
}

p.tweet {
    margin-top: 5px; font-size: 0.8em;
}

p.date {
    color: gray; font-size: 0.7em; margin-top: 5px; margin-bottom: 10px;
}

This CSS code is stored in the Chapter_04_DataViews/css/styles.css file included in the source code of the book.

DataView on an iPad
Figure 4-2. DataView on an iPad

DataView Events

As expected, the Ext.dataview.DataView class (and its subclasses) are able to react to various events; the most important are the following:

  • itemtap and itemsingletap: They are both triggered when the user taps only once on any item of the dataview.
  • itemdoubletap: Triggered when the user double-taps on an item.
  • itemswipe: Executed when the user swipes his finger on top of a particular item.

itemtap versus itemsingletap

The main difference between itemtap and itemsingletap is that the former is triggered immediately, while the second one is triggered with a slight delay of a couple of milliseconds; thus, the itemsingletap event is specially adapted to be used together with the itemdoubletap event, because in case the user taps twice on an object, the itemtap event would not yield to the execution of the itemdoubletap event.

Using the listeners configuration key, we can add some event handlers for our dataview:

listeners:  {
    itemsingletap: function (dataview, index, target, record, e, eOpts) {
        var id = record.get('id');
        var author = record.get('from_user');
        var message = Ext.String.format('Tweet {0}<br>from {1}', id, author);
        Ext.Msg.alert('Information', message);
    },
    itemswipe: function (dataview, index, target, record, e, eOpts) {
        var id = record.get('id');
        var message = Ext.String.format('Swiping! {0}', id);
        Ext.Msg.alert(message);
    },
    itemdoubletap: function (dataview, index, target, record, e, eOpts) {
        var name = record.get('from_user_name');
        var message = Ext.String.format('Double tap! {0}', name);
        Ext.Msg.alert(message);
    }
}

Lists

Lists are the quintessential data-bound control. They are represented by the Ext.dataview.List class, itself a subclass of Ext.dataview.DataView. Similarly as their superclass, they require a store definition (in this case, the peoplestore defined earlier in the chapter) and an itemTpl which defines the template used to render each cell of the list.

The code that follows describes a very simple list, attached to the peoplestore mentioned previously:

Ext.define('Chapter4DataViews.view.IndexView', {
    extend: 'Ext.navigation.View',
    xtype: 'indexview',
    config: {
        id: 'navigationView',
        items: [{
            xtype: 'list',
            title: 'People',
            store: {
                xtype: 'peoplestore'
            },
            itemTpl: '<div class="contact">{firstName} <strong>{lastName}
            </strong></div>'
        }]
    }
});

Lists can also be grouped, and they could also show a nice indexBar on the right side to simplify navigation and scrolling, and the result would be the same as shown in Figure 4-3:

{
    xtype: 'list',
    title: 'People',
    grouped: true,
    indexBar: true,
    store: {
        xtype: 'peoplestore'
    },
    itemTpl: '<div class="contact">{firstName} <strong>{lastName}
    </strong></div>'
}
List component
Figure 4-3. List component

Finally, just like their superclass, list components can react to the itemtap, itemswipe, and itemdoubletap events. They can also react to the disclose event, which is fired when the user taps on the disclosure button.

Nested Lists

Finally, nested lists (represented by the Ext.dataview.NestedList component, whose xtype is nestedlist) are used to represent hierarchical stores, implemented using the Ext.data.TreeStore class seen previously in this chapter.

A nestedlist component does not inherit either from Ext.dataview.DataView or from Ext.dataview.List; rather, it is a class that uses as many list components as required to help the user navigate the hierarchy of data being presented.

Nested lists or navigation views?

The question has come from many different developers: When is it better to use a nested list versus a navigation view with a series of embedded list components?

The answer, as always in these cases, is the famous “It depends.” Experience shows that nested lists are quite rigid and inflexible components, which usually work best when the underlying hierarchical data set is uniform and coherent. In those cases, nested lists are wonderfully simple and straightforward.

On the other hand, when your application has to deal with heterogeneous sets of data, where parents and children have radically different structures, or when each list has to have a really different visual layout from the rest, then it is better to use a navigation view, encapsulating individual consecutive lists.

The following code shows the basic configuration required to get a nested list in your application, which looks like the one shown in Figure 4-4:

Ext.define('Chapter4DataViews.view.NestedListDemo', {
    extend: 'Ext.dataview.NestedList',
    xtype: 'nestedlistdemo',
    config: {
        title: 'Teams',
        margin: 20,
        listConfig: {           // 1
            ui: 'round',
            itemTpl: '{text}'
        },
        detailCard: {           // 2
            xtype: 'panel',
            html: 'This is the leaf node detail card'
        },
        listeners: {            // 3
            itemtap: function (nestedList, list, index,
            target, record, e, eOpts) {
                var name = record.get('text');
                var html = Ext.String.format('This is the leaf
                node card of {0}', name);
                this.getDetailCard().setHtml(html);
            }
        },
        store: {                // 4
            // ...
        }
    }
});
1

The configuration of each list used by the nestedlist can be modified using the listConfig property. Ensure that the same configuration will be used for all the lists used by the nestedlist.

2

The detailCard property (which can be accessed by event handlers using the getDetailCard() getter function) contains a panel definition, which is automatically displayed every time the user selects an item whose leaf flag is set to true.

3

The listeners key, as usual, contains the definition of event handlers to be executed at some point in the future.

4

Please refer to the previous section about stores, where we have shown how to create a hierarchical Ext.data.TreeStore object.

Nested list component
Figure 4-4. Nested list component

Different itemtap events handler signatures!

Pay attention to the fact that the itemtap event handler signature is different between the Ext.dataview.NestedList and the Ext.dataview.List classes; for the nestedlist class, this is the signature:

itemtap(nestedList, list, index, target, record, e, eOpts)

while for the list and dataview classes, it’s the following:

itemtap(list, index, target, record, e, eOpts)

This is due to the fact that NestedList “contains” an instance of the List class, and as such the event handler receives a pointer to both objects. This difference in signatures is often a source of confusion.

Conclusion

One of the most important aspects for enterprise applications is the management of data, and this is precisely one of the strongest points of Sencha Touch. It offers a complete set of tools to allow developers to describe not only data structures, including their validation rules and mutual interrelationships, but also the storage mechanisms that apply in each case, and it even proposes a set of visual components that automatically bind themselves to stores for displaying data.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required