应用程序架构介绍-Introduction to Application Architecture

Ext JS 提供对 MVC 和 MVVM 应用架构的支持。两种架构方法都是按照逻辑划分应用层次,每种方法在划分应用层次方面又有各自的优势。下面介绍一下基础知识。

MVC

在 MVC 架构中,主要的类是:实体模型(Model)、视图(Views)和控制器(Controllers)。用户于视图进行交互,视图用于显示模型中的数据。控制器监控交互操作,根据需要进行实体模型和视图的变更以响应用户的交互。

由于控制器主要负责变更,所以视图和实体模型之间不能相互感知变化。总的来说,在 MVC 架构中,控制器包含了主要的逻辑。根据需要视图也可以包含一点逻辑。实体模型主要是数据的接口,包括管理数据变更的逻辑。

MVC 架构的目标是清晰定义应用中各个类的职责。职责清晰有助于将这些类从复杂的环境中分离出来,方便应用的测试、维护和代码重用。

MVVM

MVC 与 MVVM 的主要差异是 MVVM 抽象出一个视图实体模型(View Model)。视图实体模型利用数据绑定(Data Binding)技术,协调实体模型数据和视图展示间的变更。

这种架构方法使得实体模型和框架可以实现尽可能多的逻辑,减少或者消除直接维护视图的逻辑。

MVC & MVVM

在Ext JS框架下,如何选择更合适的应用架构,我们需要进一步了解两种架构中每个角色的差异:

  • (M) Model- 应用的数据。定义数据结构的一系列实体模型(Models)(例如: user 实体包括 user-name 和 password 等字段)。实体模型通过数据包(data package)持久化数据,可以通过关联关系(Association)链接到其他的实体模型。

    实体模型通常与 store 一起使用,为 grid 等组件提供数据。实体模型也可以负责一些数据逻辑(领域逻辑),例如:数据合法性验证、数据转换等。

  • (V) View- 可视化展示的组件。例如:grids、trees 和 panels 等。

  • (C) Controller- 维护视图的逻辑。负责视图渲染、路由、实体模型实例化以及其他应用逻辑。

  • (VM) ViewModel- 管理与视图相关的数据。负责维护使用该视图实体模型的组件的数据绑定,以及当数据记录发生变更时组件的更新。

这些应用架构为应用提供了清晰的结构和一致性。遵循这些约定可以获得如下收益:

  • 每个应用以同样的方式运行,只需要学习一次;

  • 可以方便不同应用间的代码共享;

  • 可以使用 Sencha Cmd 优化应用的发布版本。

构建一个简单的应用

下载和安装 Sencha CMD 以及 Ext JS,使用如下命令创建一个应用:

sencha -sdk local/path/to/ExtJS generate app MyApp ./MyApp
cd MyApp
sencha app watch

应用程序简介-Application Overview

文件结构-File Structure

Ext JS 应用程序包含统一的目录结构。建议将所有的 Store 、 Model 、 ViewModel 和 ViewController 分别放置在 app

文件夹下的对应目录中(store、实体模型-model、视图实体模型和控制器view)。 建议将 VIewController 和 ViewModel 根据逻辑进行划分,分别放置在 app/view/ 下的不同的子目录中,文件夹也以逻辑功能命名。例如:app/view/main/classic/src/view/main/ 目录)。

命名空间-Namespace

每个类的第一行是定位该类的地址。这个地址称为命名空间,其格式为:

<AppName>.<foldername>.<ClassAndFileName>

在上述示例中, "MyApp" 是应用程序名,"view" 是文件夹名称,"main" 是子文件夹(逻辑功能),"Main" 是实际的类和文件名。基于上面的信息,框架在定位一个文件Main.js 时遵循下面的规则:

// Classic
classic/src/view/main/Main.js

// Modern
modern/src/view/main/Main.js

// Core
// "MyApp.view.main.MainController" shared between toolkits would be located at:
app/view/main/MainController.js

应用程序-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)都可以集中维护在同一个地方。

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 通知应用程序创建指定的视图,并且渲染到文档对象(document body)中。

Application.js

每个 Ext JS 的应用都是 Application 类的一个实例。该类能够被 app.js 加载和实例化。使用 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)包括了应用的全局配置,例如:命名空间、共享 Store等等。当应用程序过期时(浏览器缓存的版本相对于服务器端的最新版本)将调用 onAppUpdate 方法,提示用户重新加载以便获得最新版本。

视图-Views

一个视图就是一个组件,是 Ext.Component 的子类,是应用的可视化部分。

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。视图还有两个重要的配置属性: controllerviewModel ,稍后介绍。

controller 配置

配置属性 controller 用于配置视图的控制器。当通过这种方式设置了视图控制器,控制器就成为事件处理(event handlers)和引用(references)的容器(container),建立起组件和事件处理的对应关系。

viewModel 配置

配置属性 viewModel 用于配置视图的实体模型。视图模型为组件及其子视图提供数据,一般情况下,这些数据绑定到组件,显示和编辑这些数据。

在 "Main" 视图中,tabpaneltitle 属性绑定到视图模型,这表示 title 属性的值来自于视图模型的 dataname 属性值。如果视图模型的 data 方式变化,title 的值自动更新。

控制器-Controllers

接下来我们看看控制器,上述示例自动创建的视图控制器 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.js,设计了一个函数来处理 grid 的选择事件 select event。由于该视图未指定控制器,所以这个事件处理程序映射到父视图 Main.js 的控制器的 onItemSelected 方法。 不需要额外的处理,控制器就能处理对应的事件。

这意味着可以非常容易地为应用程序添加逻辑。由于控制器与视图具有一对一的关系,唯一需要的就是定义具体的处理方法。

视图控制器用于:

  • 通过 “listeners” 和 “reference” 配置显式地连接到视图;

  • 视图在生命周期中自动管理其关联的控制器。从实例化到销毁,Ext.app.ViewController 依附于引用它的视图。同一个视图的另一个实例拥有自己的视图控制器实例。当视图被销毁时,其关联的控制器也将被销毁;

  • 为嵌套视图提供封装;

视图实体模型-ViewModels

再来看看视图实体模型:

Ext.define('MyApp.view.main.MainModel', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.main',

    data: {
        name: 'MyApp',

        loremIpsum: 'Lorem ipsum dolor sit amet ...'
    }

    //TODO - add data, formulas and/or methods to support your view
});

视图实体模型管理数据对象。允许视图绑定该数据,并且在数据发生变化后通知视图。视图实体模型与视图控制器一样,属于引用它的视图。由于视图实体模型关联到一个视图,它也可以连接到组件层次结构中上级组件的视图实体模型。所以允许子视图实体模型简单继承父视图实体模型。

在上述示例的 Main.js 文件中,通过配置属性 viewModel 建立起视图与视图实体模型之间的链接。这个链接以声明式(declarative fashion)定义了数据绑定,自动将实体模型的数据设置到视图上。数据定义在 MainModel.js 文件中,数据可以是来自任何地方的任何数据。可以通过各种代理获取数据(例如 AJAX、 REST 等)。

Models & Stores

Models 和 Stores 构成了应用的信息接口。数据的发送、接收、组织以及模型化(modeled)都是通过这两个类实现的。

Models

Ext.data.Model 表示应用中可持久化的数据类型。每个实体模型包括字段和方法,实现应用数据的模型化。 通常,Models 与 Stores 一起使用。Stores 作为数据绑定组件(例如:grids、 trees 和 charts等)的数据源。Model 的示例代码如下:

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

根据上述命名空间的约定,应该使用 User.js 文件存储,并且放置在 app/model/ 目录中。

Fields

Ext.data.Model 描述了数据记录包含的值或属性,称为 fields。 实体模型类通过配置属性 fields 定义这些字段。建议定义字段和字段的数据类型,但不是必须的。如果未定义 fields 配置属性,将自动读取数据并添加到数据对象中。下列情况下,需要定义字段:

  • 合法性验证(Validation)
  • 缺省值(Default values)
  • 转换函数(Convert functions)

Stores

Store 是客户端的数据记录(Model 的实例)缓存。Store 提供了数据的排序、筛选和查询等功能。Store 的示例如下:

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'}
    ]
});

上述的文件应存储在 Users.js 文件中,放置在 app/store/ 目录下。

如果想要全局更新 Store 实例,可以将该 Store 添加到 Application.jsstores 配置属性中:

stores: [
    'Users'
],

results matching ""

    No results matching ""