Taking Advantage of ComponentView in Sencha Touch 2
19/12/2011
This blog post is redundant and no longer applies to latest version of Sencha Touch 2. Please read the official guide on the Sencha Touch 2 Documentation.
The GitHub project has been updated to the latest version of Sencha Touch 2. It is available here.
Sencha Touch 2 is still in preview (at the time of writing this), and although the release has been focused on performance improvements, there are a few new components that can be incredibly useful. One of the new components I love is Ext.dataview.ComponentView.
What is it?
Ext.dataview.ComponentView is basically a Ext.DataView. You bind a store to it and it shows it in a list-like fashion. However with ComponentView, each list item can be a component. As a result, the possibilities are incredible.
Please be aware that the following API may change before the Sencha Touch 2.0 final release.
Example
I've put together a simple MVC application to show you how Ext.dataview.ComponentView works. The source can be found here.
Oh, and our example application is going to have some kittens in it. NICE. So let's get started!
Structure
The MVC structure in Sencha Touch 2 is similar to Ext JS 4. You have an few important parts:
app/controller/model/store/view/
resources/css/sass/config.rbstyle.scss
lib/touch2/
index.htmlindex-debug.html
I'm not going to explain what this means here. There is a great post on the subject here and I'm sure there will be several guides in the future when Sencha Touch 2 is released.
Your Application
app.js
We need to define an app.js file so Sencha Touch knows we are creating an application. It is very simple to put together.
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext', 'lib/touch2/src');
Ext.application({
name: 'Example',
controllers: ['Application'],
views: ['Main'],
launch: function() {
Ext.Viewport.add({
xclass: 'Example.view.Main'
});
}
});
- Enable
Ext.Loader. - Set the path location of the
Ext.*class namespace. - Define our application.
- Give it a
name. - Give it the name of our only controller,
Application(we don't have to use the full namespace). - Give it the name of our only view,
Main(we don't need the full namespace here either); - Define a
launchmethod which addedExample.view.Mainas an item toExt.Viewport.
Model
app/model/Kitten.js
Our example is going to have lots of kittens in it which will come from a store, so we obviously need a Kitten model.
Ext.define('Example.model.Kitten', {
extend: 'Ext.data.Model',
fields: [
"name",
"image",
{ name: "cuteness", type: 'int' }
]
});
- Define our model and extend
Ext.data.Model. Notice how it uses theExamplenamespace. - Give the model some
fields(one of which is an integer).
Store
app/store/Kittens.js
Now we have a Kitten model, we need to setup a store to use that model and give it some data. Simple.
Ext.define('Example.store.Kittens', {
extend: 'Ext.data.Store',
model: 'Example.model.Kitten',
data: [
{
name: 'Running kitten',
image: 'data/images/kitten1.jpg',
cuteness: 70
},
{
name: 'Spreading legs kitten',
image: 'data/images/kitten2.jpg',
cuteness: 90
},
{
name: 'Tongue kitten',
image: 'data/images/kitten3.jpg',
cuteness: 70
},
{
name: 'Ginger kitten',
image: 'data/images/kitten4.jpg',
cuteness: 80
},
{
name: 'Kitten friends',
image: 'data/images/kitten5.jpg',
cuteness: 20
},
{
name: 'Milk kitten',
image: 'data/images/kitten6.jpg',
cuteness: 50
}
]
});
- Define our store and make it extend
Ext.data.Store. - Give it a model of
Example.model.Kitten(which we defined above). - Give it some fake
data.
Controller
app/controller/Application.js
We only have one controller in our application, and it isn't going to do much. For now, we just need to include our Kittens store and the KittensList view (which we'll create later).
Ext.define('Example.controller.Application', {
extend: 'Ext.app.Controller',
stores: ['Kittens'],
views: ['KittensList']
});
- Define our controller and make it extend
Ext.app.Controller. - Give it our store (notice how we don't have to add
Examples.store., it's done automatically). - Give it our view (no namespace just like above).
Views
app/view/Main.js
We need to add a global view for application, which basically acts as a Viewport. We shall call this Main.js. There isn't much to it.
Ext.define('Example.view.Main', {
extend: 'Ext.Container',
requires: ['Example.view.KittensList'],
config: {
layout: 'fit',
items: [
{ xclass: 'Example.view.KittensList' }
]
}
});
- Define our view and make it extend
Ext.Container. - Give it some
requiresfor ourExample.view.KittensListcomponent (defined next). - Give it a fit
layoutso the list stretches to the size of this container. - Add the
Example.view.KittensListas a child item of the container.
app/view/KittensList.js
The first view we will create is Example.view.KittensList. It is going to extend Ext.dataview.ComponentView and it's not gonna do much.
So here's the code:
Ext.define('Example.view.KittensList', {
extend: 'Ext.dataview.ComponentView',
xtype: 'kittenlist',
requires: ['Example.view.KittensListItem'],
config: {
cls: 'kitten-list',
store: 'Kittens',
defaultType: 'kittenslistitem'
}
});
- We define the class and extend
Ext.dataview.ComponentView. - Add a custom
clsso we can make it pretty. - Give it a
store(Kittensto be exact). - Give it a
defaultTypewhich is the default xtype of any components being added to the list.
app/view/KittensListItem.js
This view is going to be used as the defaultType of the above Example.view.KittensList. There is a little bit of magic that goes on in this class, so I will go through it a little slower. Sorry about it's length (that's what she said).
So first up, the base of the class:
Ext.define('Example.view.KittensListItem', {
extend: 'Ext.dataview.DataItem',
xtype : 'kittenslistitem',
config: {
cls: 'kitten-list-item'
}
});
- We extend
Ext.dataview.DataItem. - Give it an
xtype(which we are using above). - Add the
clsconfig so we can style it.
So we have the basics of the application put together now. If you open it up in a browser, it will show a list with a bunch of items. Unfortunately, because the ListItem has no content, it will be blank.
So what we gotta do to make it display?
New Configurations
First up, we need to add new configurations for each of the different views we are going to have in each list item. To do this, we need to know what it is going to look like.

So you can see we are going to show an image of the kitten, the name of the kitten and a slider which will show the cuteness of the kitten; all inside the Examples.view.KittensListItem.
So let's go ahead and add a configuration for each one of these components:
config: {
...
image: true,
name: {
cls: 'x-name',
flex: 1
},
slider: {
flex: 2
}
}
- The
imageconfiguration will be transformed into aExt.Imgcomponent. - The
nameconfiguration will be transformed into aExt.Componentcomponent. - The
sliderconfiguration will be transformed into aExt.slider.Slidercomponent.
So how do these configurations get transformed?
What we need to do is add apply and update methods for each of this configurations. Let me show you the code.
applyImage: function(config) {
return Ext.factory(config, Ext.Img, this.getImage());
},
updateImage: function(newImage, oldImage) {
if (newImage) {
this.add(newImage);
}
if (oldImage) {
this.remove(oldImage);
}
}
The apply method (which is called when setImage is called) will use Ext.factory to take the passed configuration, and any existing instance (using getImage) and return a new instance of the type passed (in this case, Ext.Img).
Then in our updateImage method, we simply add the newImage into the list item, and remove oldImage item if one exists.
Make sense? If not, take a look at the Class System guide.
We need to now do the same for the other two configurations:
applyName: function(config) {
return Ext.factory(config, Ext.Component, this.getName());
},
updateName: function(newName, oldName) {
if (newName) {
this.add(newName);
}
if (oldName) {
this.remove(oldName);
}
},
applySlider: function(config) {
return Ext.factory(config, Ext.slider.Slider, this.getSlider());
},
updateSlider: function(newSlider, oldSlider) {
if (newSlider) {
this.add(newSlider);
}
if (oldSlider) {
this.remove(oldSlider);
}
}
The same idea again, only using Ext.Component and Ext.slider.Slider as the component of choice.
Requires
We are now using the Ext.Img and Ext.slider.Slider components in our list item, so we must require those classes so when this list item class is loaded, the required class are also loaded.
...
requires: [
'Ext.Img',
'Ext.slider.Slider'
]
...
Note: this is not inside the config block.
Layout
Now we have items inside each list item, we must have them displayed properly. For this, we use the layout configuration and set it to hbox.
config: {
...
layout: {
type: 'hbox',
align: 'center'
}
}
dataMap
This is were the magic begins.
The
dataMapconfiguration allows you to call functions with a value of a field, on functions of your list item.
Stumped? Let's look at the code:
config: {
...
dataMap: {
getImage: {
setSrc: 'image'
}
}
}
So you can see we defined the dataMap configuration. It is just an object with a few keys and values. The key of each item is the method name you want to call. This method name must return something, and then with that returned value, it will call the key of the inner object with the value (which is a field name).
So with the above code, this is what happens when the record of the list item is updated:
- We get the new
record. - We then call:
listItem.getImage().setSrc(record.get('image'));
Understand now? Good job!
So now we have figured this out, we need to add the correct dataMap for each of the fields so the whole item has the correct look.
config: {
...
dataMap: {
getImage: {
setSrc: 'image'
},
getName: {
setHtml: 'name'
},
getSlider: {
setValue: 'cuteness'
}
}
}
Custom CSS
For this example I have used SCSS to make it look a little better.
As always, there isn't much too it:
//include sencha touch
@import 'sencha-touch/default/all';
//include layout and form parts of sencha touch
@include sencha-layout;
@include sencha-form-sliders;
//define a variable so we can have consistant padding throughout the app
$list-padding: .7em;
//body background
body {
background: #eee;
}
//kitten list styling
.kitten-list {
.x-dataview-inner {
padding: $list-padding;
}
//kitten list item styling
.kitten-list-item {
padding: $list-padding/2;
background: #fff;
border: 1px solid #ccc;
border-bottom-width: 0;
//add rounded corners to the last item
&:last-child {
border-bottom-width: 1px;
@include border-bottom-radius($list-padding/2);
}
}
//add rounded corners to the first item
.x-mask + .kitten-list-item {
@include border-top-radius($list-padding/2);
}
.kitten-list-item .x-img {
background: #eee;
background-size: cover;
background-position: center center;
@include border-radius($list-padding/2);
width: 80px;
height: 80px;
}
.kitten-list-item .x-name {
padding: 0 $list-padding/2;
}
}
I've added inline comments to explain everything.
Before

After

What if we want to know when the slider changed?
Each list item now has a slider, of which the value can change. So what if you want to know when that happens? Doing this is pretty simple.
Let's go back to our controller.
app/controller/Application.js
What we want to do is override our init method in our controller, so we can call control with any listeners we have on our views.
init: function() {
this.control({
'kittenslistitem slider': {
change: this.onCutenessChange
}
});
},
onCutenessChange: function(slider, value) {
//we should do something here
console.log('onCutenessChange', value);
}
- We define the
initmethod - We call the
controlmethod, which you can pass a ComponentQuery and then which events you want to listen to (changein this case), and then we give it a method to call. - Define the
onCutenessChangemethod so we know something happens.
Now if you reload and move one of the sliders, you will get a billion logs of value being changed. NICE.
Incredible, but be careful
As you can see, the flexibility and power available when using the new Ext.dataview.ComponentView is incredible. This can be so very useful in many, many occasions.
But it's not all good..
So let's say we created this same example with a Ext.DataView (but without the slider). In total, your application would have 2 component; Main.js and KittensList.js. With Ext.dataview.ComponentView though, that number goes up to 20, and that is with only 6 rows in the store. This can drastically increase memory usage and the amount of DOM generated for all your list items. So you must watch out when using this.
Conclusion
If you have any issues, feel free to comment below; but only after you checkout the source over on GitHub (I've included loads of documentation in the actual source code).
That will be all.