doc21 - KoreaSenchaUserGroup/Lab1 GitHub Wiki

DOC 21. [Components] Using DataViews in Sencha Touch 2

원문 : http://docs.sencha.com/touch/2-0/#!/guide/forms

번역 : 정건우 [Github:NangChun]

센차터치2 에서 데이터뷰 사용하기

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

Use {@link Ext.dataview.DataView DataView} whenever you want to show sets of the same component many times, for examples in apps like these:

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

Creating a Simple DataView

At its simplest, a {@link Ext.dataview.DataView DataView} is just a {@link Ext.data.Store Store} full of data and a simple template that we use to render each item:

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

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

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

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

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

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

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

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

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

Loading data from a server

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

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

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

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

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

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

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

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

Styling a DataView

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

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

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

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

Once we have that complete, we can go back to our previous Twitter example and add the {@link Ext.dataview.DataView#baseCls basecls} configuration:

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

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

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

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

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

    baseCls: 'my-dataview'

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

Component DataView

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

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

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

    config: {
        nameButton: true,

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

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

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

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

Above is an example of how to define your component based {@link Ext.dataview.DataView DataView} item component. There are a few important notes about this:

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

Now we have created the item component, we can create our component {@link Ext.dataview.DataView DataView}, similar to how we done it before.

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

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

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

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

Now if we run this code together, we can see the component {@link Ext.dataview.DataView DataView} in action.

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

    config: {
        nameButton: true,

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

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

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

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

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

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

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

The great thing about this is the flexibilty it can add to your dataviews. Each item component has access to its own {@link Ext.dataview.component.DataItem#record record}, so you can do just about anything with it.

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

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

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

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

            this.add(newNameButton);
        }
    },

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

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

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

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

    config: {
        nameButton: true,

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

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

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

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

            this.add(newNameButton);
        }
    },

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

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

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

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

    useComponents: true,
    defaultType: 'mylistitem'
});
⚠️ **GitHub.com Fallback** ⚠️