Extjs6的Application Architecture(应用架构)介绍

来源:互联网 发布:java静态变量默认值 编辑:程序博客网 时间:2024/04/30 00:34

原文地址:http://docs.sencha.com/extjs/6.0/application_architecture/application_architecture.html

本文大部分是翻译的,最后一些内容是由本人增加的;一方面是为了方便自己以后参阅复习,另一方面希望对需要的朋友提供帮助,共同进步。对于文中有翻译不准确的地方,请见谅(本人水平有限)。如有任何问题请指出,欢迎大家交流学习,谢谢!

Ext JS提供了对MVC和MVVM应用程序架构的支持。这两种架构方式有一些思想是相同的,都是关注于将应用程序代码和业务逻辑分离。在一个应用程序中你的代码如何组织,根据使用者的偏好来确定使用MVC还是MVVM(每种模式都有自己的优点)。

这篇指导的目的就是提供有关组成这些框架的组件的基础知识。

What is MVC?

在一个MVC框架中,大多数的类或者是模型(Models),或者是视图(Views),或者是控制器(Controllers)。用户(user)与视图(Views)交互,视图(Views)显示模型(Models)中的数据(data)。这些交互都被控制器(Controllers)监控,为了响应交互当需要时,控制器(Controllers)会更新模型(Models)和视图(Views)。

通常情况下,模型(Model)和视图(View)都不知道彼此的存在,因为控制器(Controller)对直接更新负主要责任。一般来讲,在一个应用程序中控制器(controllers)包含了大多数的应用逻辑。在理想情况下视图(views)会包含一点点业务逻辑。模型(models)主要是一个用户数据接口,包含着管理变化数据的业务逻辑。

MVC的目标是清晰的划分应用程序中各个类的职责。因为每一个类都有明确的职责,在大型的应用环境中,它们就暗暗解耦了。这就让应用程序容易测试和维护,代码复用性也加强了。

What is MVVM?

MVC和MVVM的最大的区别就是MVVM有一种叫做视图模型(viewmodel)的抽象视图。视图模型利用一种叫作数据绑定(data binding)的技术将模型的数据和视图的展示之间的变化进行调和。

结果是,模型和框架完成尽可能多的工作,最大限度地减少或消除应用程序逻辑直接操作视图。

Returning Users

Extjs5引入了支持MVVM的架构支持,同时对mvc中的C进行了改进。我们鼓励你去探索和研究这些改进,需要特别注意的是,我们做了很大的努力对extjs4的mvc的继续支持。

MVC and MVVM

为了理解如何在你的应用程序中选择合适的框架,首先我们应该进一步定义各种缩写代表什么:

(M:Model)模型:这是您应用程序的数据。一组类(称为“模型”)定义了它们数据的字段(如一个User模型有两个字段:用户名和密码)。通过数据包模型知道如何展示自己,模型也能通过关联于其它模型联系。

模型通常和Stores联合使用,给表格和其他组件提供数据。对于你可能需要的数据逻辑(比如验证、转换等)在模型(Models)中定义是比较理想的。

(V:View)视图:一个任何类型的组件是一个视图,视图即是视觉表示。例如:网格(grids),树(trees)和面板(panels)都是视图(views)。

(C:Controller)控制器:控制器是维护你的视图逻辑的(view’s logic),并使你的应用程序正常工作。控制器的工作可能包括渲染视图、路由、实例化模型,和任何其他类型的应用程序逻辑。

(VM: ViewModel)视图模型:视图模型是一个类,它管理视图的数据。它允许感兴趣的组件绑定和数据更改通知。

对于你的框架代码,这些应用程序架构提供结构化和一致性。按照约定,我们的建议将对你们有重要的好处:

  • 每个应用程序在相同的方式工作,所以你只需要学一次。

  • 很容易在应用程序之间共享代码。

  • 您可以使用Sencha Cmd创建优化的生产版本的应用程序。

构建一个示例应用程序(Building a Sample App)

让我们使用Sencha Cmd构建一个示例应用程序。首先,下载并解压缩ExtJS SDK。接下来,安装Sencha Cmd。之后我们打开命令行,输入以下命令:

sencha -sdk local/path/to/ExtJS generate app MyApp MyAppcd appsencha app watch

注意:如果你对上面的命令不熟悉,请参考 Getting Started guide(入门指南)

应用概述(Application Overview)

在我们讲解了关于MVC、MVVM 、MVC+MV的知识之后,让我们看一下sencha cmd生成的应用程序的结构:

文件目录结构

Ext JS应用程序遵循统一的目录结构,每个应用程序都是相同的。我们介绍的layout、all Store、Model、ViewModel和ViewController类都放在app文件夹下(model文件夹存放Models,store文件夹存放Stores,view文件夹存放ViewModels/Controllers),最佳实践是使用类似命名结构的规则将逻辑组ViewControllers和ViewModels一起存放在app/view的子文件夹下。对于视图,看下面文件夹“app/view/main/”和 “classic/src/view/main/”。

命名空间(Namespace)

每一个类的第一行代码是各种地址,地址(adress)叫做命名空间,命名空间的格式如下:

<AppName>.<foldername>.<ClassAndFileName>

在示例应用中,AppName为“MyApp”,foldername为“view”,子文件夹的名字是“main”,ClassAndFileName为“Main”。基于这些信息,框架在以下的位置查找一个文件名为Main.js的文件:

// Classicclassic/src/view/main/Main.js// Modernmodern/src/view/main/Main.js// Core// "MyApp.view.main.MainController" 这个共享的文件位于:app/view/main/MainController.js

如果这个文件找不到,Extjs会给我们抛出一个异常信息,直到你解决这个问题。

应用程序(application)

让我们从index.html开始看。

<!DOCTYPE HTML><html manifest=""><head><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta charset="UTF-8"><title>MyApp</title><script type="text/javascript">var Ext = Ext || {}; // Ext namespace won't be defined yet...// This function is called by the Microloader after it has performed basic// device detection. The results are provided in the "tags" object. You can// use these tags here or even add custom tags. These can be used by platform// filters in your manifest or by platformConfig expressions in your app.//Ext.beforeLoad = function (tags) {var s = location.search,  // the query string (ex "?foo=1&bar")profile;// For testing look for "?classic" or "?modern" in the URL to override// device detection default.//if (s.match(/\bclassic\b/)) {profile = 'classic';}else if (s.match(/\bmodern\b/)) {profile = 'modern';}else {profile = tags.desktop ? 'classic' : 'modern';//profile = tags.phone ? 'modern' : 'classic';}Ext.manifest = profile; // this name must match a build profile name// This function is called once the manifest is available but before// any data is pulled from it.////return function (manifest) {// peek at / modify the manifest object//};};</script><!-- The line below must be kept intact for Sencha Cmd to build your application --><script id="microloader" type="text/javascript" src="bootstrap.js"></script></head><body></body></html>

Ext js使用Microloader去加载应用程序资源,这些资源基于app.json文件描述。这就省去了我们在index.html中引入其他信息的麻烦事。有了app.json,应用程序需要的meta-data都会在一个单独的文件位置存储。Sencha Cmd可以通过一个简单而有效的方式编译你的应用程序。

app.json通过配置它所认可的参数提供了一个极好的收集信息资源的方式。

对于更多beforeLoad部分的信息和平台构建请参考:Developing for Multiple Environments and Screens guide

app.js

当我们生成的应用程序之前,我们创建了一个类(Application.js)和推出了它的一个实例(在app.js)。你可以看到app.js的内容如下:

/* * This file is generated and updated by Sencha Cmd. You can edit this file as * needed for your application, but these edits will have to be merged by * Sencha Cmd when upgrading. */Ext.application({name: 'MyApp',extend: 'MyApp.Application',requires: ['MyApp.view.main.Main'],// The name of the initial view to create. With the classic toolkit this class// will gain a "viewport" plugin if it does not extend Ext.Viewport. With the// modern toolkit, the main view will be added to the Viewport.//mainView: 'MyApp.view.main.Main'//-------------------------------------------------------------------------// Most customizations should be made to MyApp.Application. If you need to// customize this file, doing so below this section reduces the likelihood// of merge conflicts when upgrading to new versions of Sencha Cmd.//-------------------------------------------------------------------------});

通过指定MainView容器类,您可以使用任何类作为你的视窗。在上面的例子中,我们已经确定MyApp.view.main.Main(一个TabPanel类)是我们的视窗(Viewport)。

mainView配置指示应用程序来创建指定的视图和附加窗口插件。将视图渲染到document body上。

Application.js

每一个Ext JS应用程序启动Application类的一个实例。这个Application类的目的是通过app.js成为launch-able,以及instantiable(可实例化)进行测试。

当你使用Sencha Cmd生成你的应用程序时,下面的Application.js内容是自动创建的。

Ext.define('MyApp.Application', {extend: 'Ext.app.Application',name: 'MyApp',stores: [// TODO: add global / shared stores here],launch: function () {// TODO - Launch the application},onAppUpdate: function () {    Ext.Msg.confirm('Application Update', 'This application has an update, reload?',        function (choice) {            if (choice === 'yes') {            window.location.reload();            }        }    );}});

对于你的应用程序,Application类包含全局设置,如应用程序的命名空间,共享stores等。当在浏览器中Application Cache或者Local Storage Cache被检测有更新时,onAppUpdate方法被自动地调用。则提示用户为了当前构建操作请重新加载应用程序。

视图(Views)

视图只不过是一个组件,它是Ext.Component的一个子类。一个视图包含所有应用程序的视觉方面。
如果你打开应用程序根目录下classic/src/view/main/Main.js这个文件,您应该看到下面的代码。

Ext.define('MyApp.view.main.Main', {extend: 'Ext.tab.Panel',xtype: 'app-main',requires: [    'Ext.plugin.Viewport',    'Ext.window.MessageBox',    'MyApp.view.main.MainController',    'MyApp.view.main.MainModel',    'MyApp.view.main.List'],controller: 'main',viewModel: 'main',ui: 'navigation',tabBarHeaderPosition: 1,titleRotation: 0,tabRotation: 0,header: {    layout: {        align: 'stretchmax'    },    title: {        bind: {        text: '{name}'        },        flex: 0    },    iconCls: 'fa-th-list'},tabBar: {    flex: 1,    layout: {        align: 'stretch',        overflowHandler: 'none'    }},responsiveConfig: {    tall: {        headerPosition: 'top'    },    wide: {        headerPosition: 'left'    }},defaults: {    bodyPadding: 20,    tabConfig: {        plugins: 'responsive',        responsiveConfig: {            wide: {                iconAlign: 'left',                textAlign: 'left'            },            tall: {                iconAlign: 'top',                textAlign: 'center',                width: 120        }        }}},items: [{        title: 'Home',        iconCls: 'fa-home',        // The following grid shares a store with the classic version's grid as well!        items: [{            xtype: 'mainlist'        }]    }, {        title: 'Users',        iconCls: 'fa-user',        bind: {            html: '{loremIpsum}'        }    }, {        title: 'Groups',        iconCls: 'fa-users',        bind: {            html: '{loremIpsum}'        }    }, {        title: 'Settings',        iconCls: 'fa-cog',        bind: {            html: '{loremIpsum}'    }}]});

请注意,视图不包含任何应用程序的逻辑。你所有视图的逻辑部分应该包含在ViewController中,在下一节中,我们将讨论ViewController。

在这个视图中有两个值得注意的配置项:controller和viewModel。

接下来“List”视图也是值得关注的,它定义在classic/src/main/view/List.js中,代码如下:

/** * This view is an example list of people. */Ext.define('MyApp.view.main.List', {    extend: 'Ext.grid.Panel',    xtype: 'mainlist',    requires: [        'MyApp.store.Personnel'    ],    title: 'Personnel',    store: {        type: 'personnel'    },    columns: [        { text: 'Name',  dataIndex: 'name' },        { text: 'Email', dataIndex: 'email', flex: 1 },        { text: 'Phone', dataIndex: 'phone', flex: 1 }    ],    listeners: {        select: 'onItemSelected'    }});

控制器配置(Controller Config)

Controller配置允许您为视图指定ViewController。当以这种方式将一个ViewController指定给一个视图后,它就变成了一个事件处理程序和引用(references)的容器。这给了视图组件和事件触发一个一对一的关系。在下一节中我们将进一步讨论控制器。

视图模型配置(ViewModel Config)

viewModel配置允许您给一个视图指定一个视图模型。ViewModel是该组件和它的子视图的数据提供者。视图通常使用视图模型中的数据,通过绑定配置的方式将想要展示或编辑的数据添加到组件中。

在“Main”视图中,你能看到“Main”视图(tabpanel)的title是绑定到了视图模型中的name。这意味着title将被“name”的值所填充,name的值被ViewModel所管理。如果ViewModel的数据发生了改变,这个title的值将自动的被更新。我们将在本文的后面讨论ViewModels。

控制器(Controllers)

接下来,让我们看看控制器。应用程序生成的ViewController在MainController.js中,代码如下:

Ext.define('MyApp.view.main.MainController', {    extend: 'Ext.app.ViewController',    alias: 'controller.main',    onItemSelected: function (sender, record) {        Ext.Msg.confirm('Confirm', 'Are you sure?', 'onConfirm', this);    },    onConfirm: function (choice) {        if (choice === 'yes') {        //        }    }});

如果你回顾你的list视图(List.js),您会注意到一个grid的选择事件指定了一个函数。在list视图的parent视图(Main.js)中,处理程序映射到控制器(MainController)的一个叫做onItemSelected的函数。正如您可以看到的,不需要任何其它特殊的配置,该控制器已准备好对该事件的处理。

在你的应用程序中,添加逻辑处理变得非常简单。由于控制器和视图有一个一对一的关系,所有你仅需要做的就是根据自己所需要的逻辑编写onItemSelected函数。

当grid的row被选择后,一个消息框弹出。这个消息框包含它自己的函数调用(onConfirm),onConfirm的作用域范围和控制器(controller)是相同的。

ViewControllers旨在:

  • 使用“liseners”和“reference”配置,使连接视图变的简单。
  • 视图的生命周期自动管理相关联的ViewController。从实例化到销毁,自动管理Ext.app.ViewController与引用它的组件。第二个实例相同的视图类会得到自己的ViewController实例。当这个视图被销毁时,他们相关的ViewController实例也一起被销毁。
  • 提供封装,使得嵌套视图直观。

视图模型(ViewModels)

接下来,让我们看一下ViewModel。如果你打开你的app/view/main/MainModel.js文件,您应该会看到以下代码:

Ext.define('MyApp.view.main.MainModel', {    extend: 'Ext.app.ViewModel',    alias: 'viewmodel.main',    data: {        name: 'MyApp',        loremIpsum: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'    }    //TODO - add data, formulas and/or methods to support your view});

一个视图模型是一个类,管理一个数据对象。视图需要的数据可以通过绑定ViewModel中的数据来实现,以及数据更新。ViewModel和ViewController一样都是被视图所引用。因为视图模型与一个视图相关联,在视图的组件层次中,它们是能链接到父视图模型(parent ViewModel)。这允许子视图“继承”父视图模型的数据。

我们通过在view(Main.js)中配置ViewModel将view和ViewModel链接起来。建立链接之后,允许在视图中通过绑定配置自动的设置数据。在这个MainModel.js例子中,数据是以内链的形式存在的。但是你的数据可以使任何形式,来自任何地方。数据可通过AJAX、REST等代理(proxy)的方式获得。真实应用中绝大数情况下都是通过代理从服务器端获取数据。

模型和存储(Models and Stores)

Models和Stores组成应用程序信息门户。大部分通过这两个类进行数据发送、检索、组织、和“建模”。

模型(Models)

在应用程序中,一个Ext.data.Model代表任何可持久数据的数据。每个model都有字段和函数,允许您的应用程序“model”数据。Models最常和Stores一起结合使用。store可以使用数据绑定组件,如grids,trees和charts。

我们的示例应用程序目前并不包含一个模型,因此让我们在应用程序app/model文件夹下添加User.js文件,文件中添加以下代码:

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

字段(fields)

Ext.data.Model中“fields”字段是描述records的(records包含值或属性称)。模型类可以声明这些字段使用“字段”配置。在这种情况下,“name”的类型是字符串,“age”的类型是整数。还有其他字段类型可参考可用的API文档。

尽管有很好的理由声明字段及其类型,这样做不是必需的。如果你不包括字段配置,数据会自动读取和插入到数据对象。如果您想要定义字段数据需求:

  • 验证
  • 默认值
  • 功能转换

让我们设置一个store,让store和model配合工作。

存储(Stores)

store是一个客户端记录的缓存(一个模型类的实例).Stores提供排序,过滤和查询记录的功能。

这个示例应用程序不包含一个store,但是不用担心。简单地定义您的store,并且给store配置一个model。

Ext.define('MyApp.store.Users', {    extend: 'Ext.data.Store',    alias: 'store.users',    model: 'MyApp.model.User',    data : [        {firstName: 'Seth', age: '34'},        {firstName: 'Scott', age: '72'},        {firstName: 'Gary', age: '19'},        {firstName: 'Capybara', age: '208'}    ]});

将上面的内容添加app/store/Users.js中(没有Users.js文件,自己创建它即可)。

如果你想要这个store是一个全局实例,你能通过配置增加这个store到Application.js文件中,配置信息如下:

stores: [    'Users'],

配置完stores之后,我们在classic\src\view\main文件夹下创建Users.js文件,增加以下代码:

Ext.define('MyApp.view.main.Users', {    extend: 'Ext.grid.Panel',    xtype: 'userlist',    requires: [        'MyApp.store.Users'    ],    title: 'users',    store: {        type: 'users'    },    columns: [        { text: '姓名',  dataIndex: 'name' },        { text: '年龄', dataIndex: 'age', flex: 1 }    ],    listeners: {        select: 'onItemSelected'    }});

更改classic\src\view\main文件夹下Main.js文件为:

Ext.define('MyApp.view.main.Main', {    extend: 'Ext.tab.Panel',    xtype: 'app-main',    requires: [        'Ext.plugin.Viewport',        'Ext.window.MessageBox',        'MyApp.view.main.MainController',        'MyApp.view.main.MainModel',        'MyApp.view.main.List',        'MyApp.view.main.Users'    ],    controller: 'main',    viewModel: 'main',    ui: 'navigation',    tabBarHeaderPosition: 1,    titleRotation: 0,    tabRotation: 0,    header: {        layout: {            align: 'stretchmax'        },        title: {            bind: {                text: '{name}'            },            flex: 0        },        iconCls: 'fa-th-list'    },    tabBar: {        flex: 1,        layout: {            align: 'stretch',            overflowHandler: 'none'        }    },    responsiveConfig: {        tall: {            headerPosition: 'top'        },        wide: {            headerPosition: 'left'        }    },    defaults: {        bodyPadding: 20,        tabConfig: {            plugins: 'responsive',            responsiveConfig: {                wide: {                    iconAlign: 'left',                    textAlign: 'left'                },                tall: {                    iconAlign: 'top',                    textAlign: 'center',                    width: 120                }            }        }    },    items: [{        title: 'Home',        iconCls: 'fa-home',        // The following grid shares a store with the classic version's grid as well!        items: [{            xtype: 'mainlist'        }]    }, {        title: 'Users',        iconCls: 'fa-user',        /*bind: {            html: '{loremIpsum}'        }*/        items: [{            xtype:'userlist'        }]    }, {        title: 'Groups',        iconCls: 'fa-users',        bind: {            html: '{loremIpsum}'        }    }, {        title: 'Settings',        iconCls: 'fa-cog',        bind: {            html: '{loremIpsum}'        }    }]});

使用sencha app watch启动示例应用程序,我们会看到Users标签下的变化。

在本例中,store直接包含数据。大多数真实应用场景下会要求你使用代理收集model或store。代理允许数据在数据提供者和应用程序之间传输。

你可以在Data Guide阅读更多关于models、stores和data providers的知识。

0 0
原创粉丝点击