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.
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.
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:
-
The list of the data fields that each instance of this class must store; they
are represented as literal objects with
name
andtype
entries. -
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.
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.
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.
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
andexclusion
, 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'
));
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 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.
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.
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.
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 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.
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>
<
>
est
.
arcu
.
ac
@
sem
.
edu
<
/email>
<
address
>
7509
Eleifend
.
Rd
.
<
/address>
<
city
>
Hope
<
/city>
<
zip
>
A5C
9
Z5
<
/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>
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'
}
}
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.
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.
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.
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, usingExt.data.StoreManager.lookup()
or its aliasExt.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 calledautoSync
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 anajax
kind of proxy, whoseurl
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!
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
:
[{
//
name
:
'text'
,
type
:
'string'
}],
defaultRootProperty
:
'team'
,
//
sorters
:
'text'
,
//
root
:
{
text
:
'Teams'
,
team
:
[{
text
:
'Finance'
,
team
:
[{
text
:
'Alma Boyle'
,
leaf
:
true
}]
//
},
{
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
}]
}]
}
}
Instead of defining a
model
property that points to some subclass ofExt.data.Model
, we can simply specify the names (eventually also the types) of the fields of each data item.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 valuechildren
.Specifying the
sorters
property here will automatically provide the store values in an ordered fashion.The
root
property contains the raw hierarchical data of each node. Pay attention to the fact that each node has both atext
property (used for sorting and displaying) and ateams
property, previously defined as thedefaultRootProperty
.
We are going to see a detailed example of use of a tree
store when learning
about the Ext.dataview.NestedList
class.
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.
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>'
,
//
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'
//
}],
proxy
:
{
type
:
'jsonp'
,
url
:
'http://search.twitter.com/search.json?q=argentina'
,
//
reader
:
{
type
:
'json'
,
rootProperty
:
'results'
}
}
}
}
});
The
itemTpl
property specifies a string or anExt.XTemplate
object, used to render each item in the data store. Pay attention to the fact that the data is formatted using theExt.Date.format()
function, inside the template code. Even mathematical expressions can be used in templates.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 JavaScriptDate
object for us.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.
As expected, the Ext.dataview.DataView
class (and its subclasses) are able to
react to various events; the most important are the following:
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 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>'
}
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.
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
:
{
//
ui
:
'round'
,
itemTpl
:
'{text}'
},
detailCard
:
{
//
xtype
:
'panel'
,
html
:
'This is the leaf node detail card'
},
listeners
:
{
//
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
:
{
//
// ...
}
}
});
The configuration of each list used by the
nestedlist
can be modified using thelistConfig
property. Ensure that the same configuration will be used for all the lists used by thenestedlist
.The
detailCard
property (which can be accessed by event handlers using thegetDetailCard()
getter function) contains apanel
definition, which is automatically displayed every time the user selects an item whoseleaf
flag is set to true.The
listeners
key, as usual, contains the definition of event handlers to be executed at some point in the future.Please refer to the previous section about stores, where we have shown how to create a hierarchical
Ext.data.TreeStore
object.
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.
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.
Get Sencha Touch 2 Up and Running now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.