现在的位置: 首页 > 综合 > 正文

ExtJS 4 Grids 详解

2013年06月12日 ⁄ 综合 ⁄ 共 9644字 ⁄ 字号 评论关闭

1.ExtJS 4 数据(包)详解

这里的包和java的包同意同义,不是指“数据包”,这个数据包是指Ext.data

data包是负责加载和保存应用中的数据的包,有41个类,其中三个是最重要的:Model,Store,Ext.data.proxy.Proxy,几乎每个应用都要用到它们,有若干个支持类辅助它们:

data-package

Models and Stores 模型和存储器

data包的中心是Ext.data.Model,模型代表了应用中的一些数据类型,例如电子商务应用中可能会有Users,Products,Orders等模型,最简单的模型就是一组字段和它们的数据,看一下模型的四个主要部分,Fields,Proxies,Associations,Validations

model

让我们看一下,如何创建一个模型:

Ext.define('User', {
    extend: 'Ext.data.Model',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'name', type: 'string' }
    ]
});

Model的一个典型用处就是用在Store中,Store就是Model实例的集合,构建一个Store并加载数据是很简单的:

Ext.create('Ext.data.Store', {
    model: 'User',
    proxy: {
        type: 'ajax',
        url : 'users.json',
        reader: 'json'
    },
    autoLoad: true
});

我们配置了Store使用Ajax
Proxy
,告诉它加载数据的url和解析数据的Reader,这个例子中服务器端需要输出json数据,因此使用了Json
Reader
,这个store会从users.json中自动加载一组User模型的实例,users.json需要输出的内容应该类似这样:

{
    success: true,
    users: [
        { id: 1, name: 'Ed' },
        { id: 2, name: 'Tommy' }
    ]
}

Inline data 内联数据

Store还可以读取内联的数据,这种情况Store内部会转换我们内联的每个对象作为Model实例的数据:

Ext.create('Ext.data.Store', {
    model: 'User',
    data: [
        { firstName: 'Ed',    lastName: 'Spencer' },
        { firstName: 'Tommy', lastName: 'Maintz' },
        { firstName: 'Aaron', lastName: 'Conran' },
        { firstName: 'Jamie', lastName: 'Avins' }
    ]
});

Sorting and Grouping 排序和分组

Store同时支持本地和远程的排序、过滤、分组:

Ext.create('Ext.data.Store', {
    model: 'User',

    sorters: ['name', 'id'],
    filters: {
        property: 'name',
        value   : 'Ed'
    },
    groupField: 'age',
    groupDir: 'DESC'
});

这段代码创建的store,数据会先根据name后根据id来排序,name不是Ed的Users会被过滤掉,而且数据还会根据年龄分组并且倒序。通过Store的api改变排序、过滤、分组都很容易

Proxies 代理

代理是store用来加载和保存模型数据的工具,有两种代理,客户端和服务器端。客户端代理例如Memory内存代理是完全在浏览器内存中的,还有HTML5的本地存储代理。服务器端代理处理和远程服务器间的数据通信例如AJAX,JSONP,REST等方式。

代理可以直接由模型定义:

Ext.define('User', {
    extend: 'Ext.data.Model',
    fields: ['id', 'name', 'age', 'gender'],
    proxy: {
        type: 'rest',
        url : 'data/users',
        reader: {
            type: 'json',
            root: 'users'
        }
    }
});
// Uses the User Model's Proxy
Ext.create('Ext.data.Store', {
    model: 'User'
});

这么做有两点好处。第一是假如不同的store都使用User模型,而且假如在每个store中User模型加载数据的方式都相同,这时候在User模型中指定代理,就不需要在多个store中重复指定代理。第二是我们可以让模型直接加载和保存数据而不需要store。

// 获取 User 模型的引用
var User = Ext.ModelMgr.getModel('User');

var ed = Ext.create('User', {
    name: 'Ed Spencer',
    age : 25
});

// 我们可以直接保存Ed的数据而不用通过Store
// 因为我们在User模型中配置了一个RestProxy代理,这可以自动发起POST请求到url /users
ed.save({
    success: function(ed) {
        console.log("Saved Ed! His ID is "+ ed.getId());
    }
});

// 加载User 1 然后处理 (通过 GET 请求到 url /users/1)
User.load(1, {
    success: function(user) {
        console.log("Loaded user 1: " + user.get('name'));
    }
});

有些代理可以受益于HTML5的本地存储LocalStorageSessionStorage,尽管老旧浏览器不支持这些特性,但是很多应用还是可以受益

Associations 关联

模型之间可以通过关联API关联到一起,大多数应用都处理许多不同的模型,并且模型总是相关的。一个博客应用可能有User,Post,Comment模型,每个User创建Post,每个Post接收评论Comment,我们可以这样表达它们之间的关系:

Ext.define('User', {
    extend: 'Ext.data.Model',
    fields: ['id', 'name'],
    proxy: {
        type: 'rest',
        url : 'data/users',
        reader: {
            type: 'json',
            root: 'users'
        }
    },

    hasMany: 'Post' // shorthand for { model: 'Post', name: 'posts' }
});

Ext.define('Post', {
    extend: 'Ext.data.Model',
    fields: ['id', 'user_id', 'title', 'body'],

    proxy: {
        type: 'rest',
        url : 'data/posts',
        reader: {
            type: 'json',
            root: 'posts'
        }
    },
    belongsTo: 'User',
    hasMany: { model: 'Comment', name: 'comments' }
});

Ext.define('Comment', {
    extend: 'Ext.data.Model',
    fields: ['id', 'post_id', 'name', 'message'],

    belongsTo: 'Post'
});

模型之间表示关联非常容易,每个模型可以有随意多的和其他模型的关联,而且定义模型的先后顺序不受限制。当我们有一个模型的实例,我们就可以很容易的遍历出和它相关的数据,例如我们想打印出一个User所有文章的所有评论:

// Loads User with ID 1 and related posts and comments using User's Proxy
User.load(1, {
    success: function(user) {
        console.log("User: " + user.get('name'));

        user.posts().each(function(post) {
            console.log("Comments for post: " + post.get('title'));

            post.comments().each(function(comment) {
                console.log(comment.get('message'));
            });
        });
    }
});

上面例子中每一个hasMany关联都会给Model创建一个新function,例如我们声明了User
hasMany Posts
,这就会给User模型创建一个user.posts()方法,调用这个方法会返回一个装有Post模型的StorePost也同样获得了一个comments方法

关联不仅帮助我们加载数据,创建新record时也很方便:

user.posts().add({
    title: 'Ext JS 4.0 MVC Architecture',
    body: 'It\'s a great Idea to structure your Ext JS Applications using the built in MVC Architecture...'
});

user.posts().sync();

这里我们实例化了一个新Post模型,它会的user_id字段会自动设置成user的id,调用sync()方法可以通过配置的Proxy保存新建的Post实例,sync是个移步调用,如果想在sync完成之后获得通知,可以传一个callback函数。

belongsTo关联也会为模型创建一个新方法:

// get the user reference from the post's belongsTo association
post.getUser(function(user) {
    console.log('Just got the user reference from the post: ' + user.get('name'))
});

// try to change the post's user
post.setUser(100, {
    callback: function(product, operation) {
        if (operation.wasSuccessful()) {
            console.log('Post\'s user was updated');
        } else {
            console.log('Post\'s user could not be updated');
        }
    }
});

这里的getUser也是异步调用,需要传递回调函数来获取user实例。setUser方法更新了外键(user_id),并且保存Post模型,也可以传递回调函数用来得知操作是否成功。

Loading Nested Data 加载嵌套数据

你可能会奇怪为什么只传了一个success函数给User.load,但是获取User的posts和comments时却不用传递回调函数。这是因为上面的例子我们假设了服务器端会在user数据一起返回它相关的post和comment数据。设置了关联关系,框架会自动从user的请求数据中解析post和comment的数据,而不是需要post数据时发新请求,需要comment的数据时又发新请求(作者注:如果每个post和comment都单独请求数据,会给服务器造成很大压力),我们可以像这样返回数据:

{
    success: true,
    users: [
        {
            id: 1,
            name: 'Ed',
            age: 25,
            gender: 'male',
            posts: [
                {
                    id   : 12,
                    title: 'All about data in Ext JS 4',
                    body : 'One areas that has seen the most improvement...',
                    comments: [
                        {
                            id: 123,
                            name: 'S Jobs',
                            message: 'One more thing'
                        }
                    ]
                }
            ]
        }
    ]
}

框架会自动解析数据。很容易配置模型的代理从任意数据源获取数据,各种数据格式基本都有相应的Reader可以hold住。ExtJS
3中Store被各种组件使用,例如Grid, Tree, Form

当然也可以用非嵌套的方式加载数据,在’lazy load’的时候这种做法是有用的,只加载需要的数据。让我们像之前一样加载User的数据,这次假设服务器端只返回了User的数据,不包括相关联的其他模型的数据,然后调用user.posts().load() 并传递回调函数接收加载的Post数据:

// Loads User with ID 1 User's Proxy
User.load(1, {
    success: function(user) {
        console.log("User: " + user.get('name'));

        // Loads posts for user 1 using Post's Proxy
        user.posts().load({
            callback: function(posts, operation) {
                Ext.each(posts, function(post) {
                    console.log("Comments for post: " + post.get('title'));

                    post.comments().each(function(comment) {
                        console.log(comment.get('message'));
                    });
                });
            }
        });
    }
});

Validations 校验

ExtJS4的模型Model功能开始变的丰富起来,并且支持数据校验,我们继续上面的例子来展现如何使用数据校验,首先添加对User的数据校验:

Ext.define('User', {
    extend: 'Ext.data.Model',
    fields: ...,

    validations: [
        {type: 'presence', name: 'name'},
        {type: 'length',   name: 'name', min: 5},
        {type: 'format',   name: 'age', matcher: /\d+/},
        {type: 'inclusion', name: 'gender', list: ['male', 'female']},
        {type: 'exclusion', name: 'name', list: ['admin']}
    ],

    proxy: ...
});

校验的形式和定义模型种字段的形式一致,每次指定一个字段的一个校验类型,例子种的校验,定义了name字段必须设置值,必须最少5个字符,age字段必须是个数字,gender字段只能取male或者female,username可以是除了’admin’的任何值,有些校验可以接收附加的选项,例如length长度校验可以接收min最小值和max最大值,format校验可以接收一个matcher匹配,ExtJS内置了5种校验器,先来看一下内置的5种校验器:

  • presence 确保这个字段被设置了一个值,空数组是个有效的值,但是空字符串不是
  • length 确保这个字段的长度在最大值最小值之间,最大值和最小值都是可选参数
  • format 确保这个字段符合一个正则表达式
  • inclusion 确保字段在给定的值的集合内,在枚举集合种
  • exclusion 确保字段不在给定的集合种

现在我们已经知道了不同的校验器做了什么事情,通过User使用一下,创建一个User然后执行校验:

// now lets try to create a new user with as many validation errors as we can
var newUser = Ext.create('User', {
    name: 'admin',
    age: 'twenty-nine',
    gender: 'not a valid gender'
});

// run some validation on the new user we just created
var errors = newUser.validate();

console.log('Is User valid?', errors.isValid()); //returns 'false' as there were validation errors
console.log('All Errors:', errors.items); //returns the array of all errors found on this model instance

console.log('Age Errors:', errors.getByField('age')); //returns the errors for the age field

关键的方法是validate,它执行所有配置的校验,并返回一个Errors对象,这个对象含有所有错误信息,还有一些常用方法例如isValid,如果没有任何错误isValid的返回值是true,getByField方法可以用来获取某一个诊断错误。

2.ExtJS 4 Grids 详解

欢迎加入我的ExtJS交流群 – 透视ExtJS 群号 256700289 入群暗号 Zen

Grid Panel是ExtJS最常用的组件之一,它的功能非常丰富,提供了非常便捷的方法执行排序,分组,编辑数据。

Basic Grid Panel 基本表格面板

simple grid

让我们创建一个简单的表格,这有创建和运行表格的全部知识。

Model and Store 模型和存储器

Grid Panel展现Store中的数据,Store可以被认为是records的集合,或者模型(Model)实例的集合。更多关于StoreModel的内容请查看《ExtJS
4 数据(包)详解
》,讲这些是为了明确一下概念,Grid Panel本身只关注如何展现数据,Store负责通过Proxy获取和保存数据。

首先我们需要定义一个ModelModel就是一组数据字段,先定义一个User
Model

Ext.define('User', {
    extend: 'Ext.data.Model',
    fields: [ 'name', 'email', 'phone' ]
});

然后创建Store以容纳若干User的实例

var userStore = Ext.create('Ext.data.Store', {
    model: 'User',
    data: [
        { name: 'Lisa', email: 'lisa@simpsons.com', phone: '555-111-1224' },
        { name: 'Bart', email: 'bart@simpsons.com', phone: '555-222-1234' },
        { name: 'Homer', email: 'home@simpsons.com', phone: '555-222-1244' },
        { name: 'Marge', email: 'marge@simpsons.com', phone: '555-222-1254' }
    ]
});

简单起见,这里使用了内联的数据,真实应用中,通常都是使用代理加载服务器端的数据,有关代理可以查看《ExtJS
4 数据(包)详解

Grid Panel 表格面板

现在我们已经有了模型定义数据结构,也加载了若干个模型实例到Store,已经为表格面板展示数据做好了准备

Ext.create('Ext.grid.Panel', {
    renderTo: Ext.getBody(),
    store: userStore,
    width: 400,
    height: 200,
    title: 'Application Users',
    columns: [
        {
            text: 'Name',
            width: 100,
            sortable: false,
            hideable: false,
            dataIndex: 'name'
        },
        {
            text: 'Email Address',
            width: 150,
            dataIndex: 'email',
            hidden: true
        },
        {
            text: 'Phone Number',
            flex: 1,
            dataIndex: 'phone'
        }
    ]
});

这就是全部要做的。我们创建了一个表格面板渲染到了body中,并且告诉它从之前创建的userStore中取得数据,最后我们定义了表格面板有哪些列,用dataIndex属性配置表格中的列和User
Model中的字段的对应关系,Name列宽度是100,不能排序也不能隐藏,Email
Address
列默认是隐藏的(可以通过其他列的菜单控制显示),Phone Number列自适应表格剩余的宽度

Renderers 渲染器

你可以使用renderer属性改变数据的展示方式,renderer是个function,它接收原始的数据并且需要返回一个处理过的数据,返回的数据将会被展示在表格上,一些最常用的渲染器在Ext.util.Format中可以找到,也可以自定义:

columns: [
    {
        text: 'Birth Date',
        dataIndex: 'birthDate',
        // format the date using a renderer from the Ext.util.Format class
        renderer: Ext.util.Format.dateRenderer('m/d/Y')
    },
    {
        text: 'Email Address',
        dataIndex: 'email',
        // format the email address using a custom renderer
        renderer: function(value) {
            return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
        }
    }
]

Grouping 分组

grouping

让表格中的行分组排列很容易,首先要在Store中指定一个groupField属性

Ext.create('Ext.data.Store', {
    model: 'Employee',
    data: ...,
    groupField: 'department'
});

关于分组的更多内容,请参见《ExtJS
4 数据(包)详解
》,接下来配置表格有分组属性:

Ext.create('Ext.grid.Panel', {
    ...
    features: [{ ftype: 'grouping' }]
});

Selection Models 选中模式

有些时候,表格是用来在屏幕上展示数据的,但是经常会需要更新数据或者和表格中的数据做交互,所有表格都有个选中模式,用来控制表格中的数据是如何选中的,主要两种选中模式是行模式(Row
Selection Model
)和单元格模式(Cell Selection Model)

表格默认使用行模式,但是切换到单元格模式也很容易:

Ext.create('Ext.grid.Panel', {
    selType: 'cellmodel',
    store: ...
});

使用单元格模式改变了几个事情,第一,点击表格中的单元格只会选中当前单元格,而在行模式的时候是选中整行的,第二,用键盘导航的时候会从一个单元格走到另一个单元格,而不是从一行走到另一行,单元格模式通常配合可编辑的表格一起使用。

Editing 编辑表格

表格内置了编辑能力,我们见识一下,行编辑和单元格编辑

Cell Editing 单元格编辑

单元格编辑允许你一次编辑一个单元格内容,实现单元格编辑,首先要为每一列配置编辑控件,只要设置editor属性即可,最简单的办法就是将editor属性设置为需要使用的控件的xtype

Ext.create('Ext.grid.Panel', {
    ...
    columns: [
        {
            text: 'Email Address',
            dataIndex: 'email',
            editor: 'textfield'
       }
    ]
});

如果你需要控制编辑控件的行为,可以把editor属性设置为编辑控件可接收的配置对象,例如我们设置一个不能为空的输入框:

抱歉!评论已关闭.