Ext JS MVC Architecture

我们之前在之前的 Ext JS Architecture介绍的Ext JS 6的应用程序的结构,简单介绍了MVC, 和MVVM两种体系,这里包括Controller, Model, View, ViewModel, ViewController等相关的知识。因为Ext JS 6中,同样支持MVC结构,而在之前的文章中,并没有详细的讲到MVC, 更多的是新特性MVVM结构,本指南将详细的讲解MVC, Ext JS 4只有此结构,所以在Ext JS 4的文档中找到此文章.

当前Ext JS 6中的Ext.app.Application中,关于application architecture的链接是错误的,它应该是此链接,在此纠正一下 http://docs.sencha.com/extjs/4.1.3/#!/guide/application_architecture

MVC Architecture

大型的客户端程序都是很难写,难以组织和维护。随着功能和开发人员的增加,更加难以控制。Ext JS 4提供了一个新的应用程序架构,不仅能够更好的组织你的代码,还能减少代码量。

我们的应用程序架构遵循 MVC-like模式,首次引入了Controller和Models. 当前有许多的MVC结构,其中大部分的定义都有略微的不同,以下是我们对MVC的定义:

  • Model是多个字段(field)和他们数据的集合(e.g. 一个User model包含username 和 password field). Models知道如何通过data package进行执久化, 也能够通过association(关系)跟其它的model进行相连。Models的功能跟Ext JS 3中的Record class相同。并且跟Stores一起,用于grids和其它组件数据显示
  • View 任何组件类型 - grids, trees,panel都称之为view.
  • Controllers 存放你的应用代码逻辑的地方,包括是否渲染视图,实例化Models, 或者其它的逻辑

在本指南中,我们将创建一个非常简单的应用,来管理用户数据。

应用程序架构主要是为实际的类和框架代码提供结构,并且保持代码的一致性,它有以下作用:

  • 每个application的原理都是一致的,所以你只需要了解一遍,就能知道它的运行原理
  • 更容易在不同的应用之前共享代码,因为它们的工作方式是一样的
  • 你可以使用我们的编译工具,创建一个优化后的production版本

File Structure

Ext JS 4应用遵循统一的目录结构,在MVC结构中,所有的类都存放在app/目录下. 如下图所示

Ext JS MVC Architecture_第1张图片

在这个例子中,我们将整个应程程序封装在’account_manager’目录中。 所需要的Ext JS 4 SDK文件,则保存在ext-4/目录下。因此index.html文件的内容如下

<html>
<head>
    <title>Account Managertitle>

    <link rel="stylesheet" type="text/css" href="ext-4/resources/css/ext-all.css">

    <script type="text/javascript" src="ext-4/ext-debug.js">script>

    <script type="text/javascript" src="app.js">script>
head>
<body>body>
html>

Creating the application in app.js

Ext JS 应用通常是使用Ext.container.Viewport创建的单页面应用。

一个应用由一个或者多个Views组件,一个view的行为由相应的Ext.app.ViewController和Ext.app.ViewModel进行管理。

每一个 Ext JS 4 application都是从 Applcation类的实例开始, Application类包含你应用的全局设置(比如app的name, 创建一个全局的命名空间), 还包含app中要使用到的其它类的引用,比如models, views, controllers. 一个Application还包含一个launch 函数, 它在整个页面都加载完成时,自动运行

让我们创建一个简单的Account Manager app,用来帮助我们管理用户帐号。首先我们需要为application选择一个namespace. 所有的Ext JS 4应用都应该使用单个的全局变量,这样,application中的所有类都在这个全局变量之下。
通常我们需要一个简短的全局变量,我们在这里使用 “AM”

Ext.application({
    requires: ['Ext.container.Viewport'],
    name: 'AM',

    appFolder: 'app',

    launch: function() {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: [
                {
                    xtype: 'panel',
                    title: 'Users',
                    html : 'List of users will go here'
                }
            ]
        });
    }
});

上面的代码做了以下几件事, 首先调用Ext.application创建一个新的Application实例,给这个实例传递的name ‘AM’. 它会为我们自动创建一个全局变量AM, 并且在Ext.Loader中注册这个namespace. 并且通过appFolder设置与namespace相应的文件夹路径。我们也简单的提供了一个launch函数,它仅创建一个 Viewport, Viewport创建好后,可以自动的渲染到doucment body。 一个页面,只能创建一个Viewport.

Ext JS MVC Architecture_第2张图片

Defining a Controller

Controller相当于应用程序中的胶水,将应用程序中的元素(views, models)绑定到一起. 它真正做的是,监听事件(监听views中的事件),并做出相应的处理。接下来,我们创建一个app/controller/Users.js的控制器.

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',

    init: function() {
        console.log('Initialized Users! This happens before the Application launch function is called');
    }
});

接着我们将新创建的Users 控制器添加到application配置中, 修改 app.js的以下代码:

Ext.application({
    ...

    controllers: [
        'Users'
    ],

    ...
});

当我们在浏览器中查看index.html, Users controller会自动的被加载(因为我们在application中有指定它), 并且它的init函数在application的launch之前被调用.

init函数是设置你的控制器与view交互的一个好地方,在init函数中,经常使用this.control函数,用来监听view类的事件,并作出相应的行为。接下来,我们更新Users controller的代码,告诉我们,panel什么时候被渲染

Ext.define("AM.controller.Users", {
    extend: "Ext.app.Controller",
    init: function(){
        console.log("'Initialized Users! This happens before the Application launch function is called ")
        this.control({
            'viewport > panel': {  //use Ext.ComponentQuery class
                render: this.onPanelRendered
            }
        })
    },
    onPanelRendered: function(){
        console.log('The panel was render');
    }
})

我们使用this.control来设置application中视图的监听器。control函数使用新的ComponentQuery 引擎来快速获取一个页面中组件的引用(xtype)。如果你还不熟悉ComponentQuery, 可以查看Component Query文档, 简单来说,它允许我们使用类似于CSS选择器,来匹配一个页面的组件(不是html节点, 而是Ext JS的Component, 可以是xtype, ID)

在我们的init函数中,我们使用了’viewport > panel’,它表示”viewport 下的直接Panel组件, 它返回一个数组,包含每个组件的引用”. 然后我们指定一个对像,对像中的键映射为匹配到组件的事件名称,它的值映射为每个事件的处理函数。即,为所有匹配到的组件,注册事件监听函数。

##Defining a View

接下来,我们想添加一个grid,用来显示我们系统中的users.

其实View就是一个组件,它是Ext JS Component的一个子类。我们创建一个app/view/user/List.js文件,使用一个grid来显示用户列表

Ext.define('AM.view.user.List' ,{
    extend: 'Ext.grid.Panel',
    alias: 'widget.userlist',

    title: 'All Users',

    initComponent: function() {
        this.store = {
            fields: ['name', 'email'],
            data  : [
                {name: 'Ed',    email: '[email protected]'},
                {name: 'Tommy', email: '[email protected]'}
            ]
        };

        this.columns = [
            {header: 'Name',  dataIndex: 'name',  flex: 1},
            {header: 'Email', dataIndex: 'email', flex: 1}
        ];

        this.callParent(arguments);
    }
});

我们的View Class就是一个普通的类。在这里,我们让它继承于Grid 组件,并且设置它的别名,这样我们就可以用xtype的方式来使用它。我们也配置了一个store和columns, 这两个配置是grid渲染时,需要使用到的数据

接下来,我们将view添加到User controller中。 因为我们设置别名时,使用了特殊的’widget.’格式,所以可以使用’userlist’作为xtype. 类似于之前使用的’panel’

接下来,我们需要在Users controller中添加这个view, 否则没有办法在app.js中使用 xtype对它渲染。

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',

    views: [
        'user.List'
    ],

    init: ...

    onPanelRendered: ...
});

然后,我们可以在main viewport中,对它进行浸染

Ext.application({
    ...

    launch: function() {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: {
                xtype: 'userlist'
            }
        });
    }
});

需要注意的一个事件是,我们在views数组中,指定了’user.List’, 它是告诉应用程序自动加载这些文件,这样我们才能在application launch中使用这些组件。application使用了Ext JS 4中新的动态加载系统,可以自动从服务器上加载。
Ext JS MVC Architecture_第3张图片

Controlling the grid

当前onPanelRendered函数依然可以执行,这是因为grid类 继承于 Panel类,符合’viewport > panel’选择器。

所以我们需要使用一个新的xtype,用来缩窄选择的范围。我们可以添加以下代码,用来监听grid中每一行的双击事件

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',

    views: [
        'user.List'
    ],

    init: function() {
        this.control({
            'userlist': {
                itemdblclick: this.editUser
            }
        });
    },

    editUser: function(grid, record) {
        console.log('Double clicked on ' + record.get('name'));
    }
});

效果如下图所示

Ext JS MVC Architecture_第4张图片

接下来,我们创建一个app/view/user/Edit.js,用来真真的编辑用户信息,而不是简单的console log

Ext.define('AM.view.user.Edit', {
    extend: 'Ext.window.Window',
    alias: 'widget.useredit',

    title: 'Edit User',
    layout: 'fit',
    autoShow: true,

    initComponent: function() {
        this.items = [
            {
                xtype: 'form',
                items: [
                    {
                        xtype: 'textfield',
                        name : 'name',
                        fieldLabel: 'Name'
                    },
                    {
                        xtype: 'textfield',
                        name : 'email',
                        fieldLabel: 'Email'
                    }
                ]
            }
        ];

        this.buttons = [
            {
                text: 'Save',
                action: 'save'
            },
            {
                text: 'Cancel',
                scope: this,
                handler: this.close
            }
        ];

        this.callParent(arguments);
    }
});

我们再次定义了已存在组件(这次为Ext.window.Window)的一个子类. 布局使用 ‘fit’,它适用于一个组件的容器,它的子组件,随着容器大小的改变而改变。

接下来,我们在controller中添加新创建的view. 并且渲染它

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',

    views: [
        'user.List',
        'user.Edit'
    ],

    init: ...

    editUser: function(grid, record) {
        var view = Ext.widget('useredit');

        view.down('form').loadRecord(record);
    }
});

首先,我们通过便捷的方法Ext.widget创建了一个视图。它等于 Ext.create(‘widget.useredit’); 然后,我们利用了ComponentQuery,来查找form 组件。每一个Component都有一个down函数,它接收一个ComponentQuery选择器,可以快迅的查找一个子组件。

Ext JS MVC Architecture_第5张图片

Creating a Model and a Store

当前我们的是以内联的方法创建的store. 虽然这样也可以,但我们想要在应用程序的任何地方引用这个store, 并更新它的数据,那就需要将它提取出来,存放到app/store/Users.js中

Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    fields: ['name', 'email'],
    data: [
        {name: 'Ed',    email: '[email protected]'},
        {name: 'Tommy', email: '[email protected]'}
    ]
});

然后修改controller,

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
    stores: [
        'Users'
    ],
    ...
});

然后,我们就可以在app/view/user/List.js中,通过id来引用这个store.

Ext.define('AM.view.user.List' ,{
    extend: 'Ext.grid.Panel',
    alias: 'widget.userlist',
    title: 'All Users',

    //我们不需要initComponent中定义User store.
    store: 'Users',

    initComponent: function() {

        this.columns = [
        ...
});

我们在Users controller中包含stores,是用来告诉应用程序自动加载这个store文件,并且根据store类的定义,给定一个storeid, 这样就可以在view中,通过id引用相应的store.

当前我们只在store中定义了’name’和’email’字段。虽然这也可以满足我们的需求,但在Ext JS 4中,我们有一个更强大的Ext.data.Model类

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

接下来,我们替换store中的内联field

Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    model: 'AM.model.User',

    data: [
        {name: 'Ed',    email: '[email protected]'},
        {name: 'Tommy', email: '[email protected]'}
    ]
});

然后,我们也需要告诉User controller获得User model的引用

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
    stores: ['Users'],
    models: ['User'],
    ...
});

Saving data with the Model

首先让我们监听save button

Ext.define('AM.controller.Users', {
    ...
    init: function() {
        this.control({
            'viewport > userlist': {
                itemdblclick: this.editUser
            },
            'useredit button[action=save]': {
                click: this.updateUser
            }
        });
    },
    ...
    updateUser: function(button) {
        console.log('clicked the Save button');
    }
    ...
});

现在我们可以看到,点击save后,将日志出输到控制台,接下来,我们更新User

updateUser: function(button) {
    var win    = button.up('window'),
        form   = win.down('form'),
        record = form.getRecord(),
        values = form.getValues();

    record.set(values);
    win.close();
}

Client 事件给了我们一个button的引用,但我们实际上是需要访问 form 组件(它包含数据) 和window 组件。所以我们再次使用了ComponentQuery,先通过button.up(‘window’)获得Edit User window, 然后通过win.down(‘form’)获得 form.

Saving to the server

Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    model: 'AM.model.User',
    autoLoad: true,

    proxy: {
        type: 'ajax',
        url: 'data/users.json',
        reader: {
            type: 'json',
            root: 'users',
            successProperty: 'success'
        }
    }
});

在上面的代码中,我们删除了’data’属性,而用 一个Proxy代替。 Proixs可以用于store和model中,用来加载和保存数据,类型有AJAX, JSON-P 以及HTML5 localStorage. 在这里,我们使用 AJAX代理,加载服务器的’data/users.json’.

我们在代理中定义了一个Reader, 它用于解析服务器响应到Store可以理解的格式。这里,我们使用了JSON Reader, 并且指定了JSON中数据保存的位置root, 以及successProperty. 以下是
data/user.json文件

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

在store中,我们还有一个autoLoad=true, 它的意思是Store将要求Proxy立即加载数据。如果我们刷新网页,将看到跟之前的效果是一样的。但现在我们却可以不用将数据硬编码在application。

接下来,我们需要将改变的数据返回到服务器。

proxy: {
    type: 'ajax',
    api: {
        read: 'data/users.json',
        update: 'data/updateUsers.json'
    },
    reader: {
        type: 'json',
        root: 'users',
        successProperty: 'success'
    }
}

我们依然从user.json中读取数据,但是任何的更新,都将发送到updateUser.json.

接下来,我们需要告诉Store 跟服务器同步

updateUser: function(button) {
    var win    = button.up('window'),
        form   = win.down('form'),
        record = form.getRecord(),
        values = form.getValues();

    record.set(values);
    win.close();
    // synchronize the store after editing the record
    this.getUsersStore().sync();
}

你可能感兴趣的:(ext-js)