zoukankan      html  css  js  c++  java
  • Extjs 4.2.0 MVC 架构

    内容:
    1.
    文件结构
    2.
    创建项目
    3.
    定义控制器
    4.
    定义视图
    5.
    控制Grid
    6.
    创建ModelStore
    7.
    通过Model保存数据
    8. 保存到服务器端


    大型客户端程序通常都难写,难组织,难以维护。项目经常由于增加功能,增加开发人员而很快失控。Ext JS 4提出新的项目结构,不仅组织你的代码,并且减少代码量。

    我们的系统结构延续MVC模式,第一次引入Models(模型)和Controllers(控制器)的概念。现在有很多MVC架构,他们或多或少有细微差别。以下是我们对MVC的定义:

    • Model是字段和对应数据的组合(例如User Modelusernamepassword两个字段)。Models知道如何通过数据包(data package)持久化,还可以通过associations(联系)同其他models关联。Models很类似于Ext Js 3里的Record类,通常与Stores一起将数据展现到grids和其他components上。
    • View可以是任何类型的componentgrids, treespanels都是视图。
    • Controllers是一个特殊的地方,用来放使application工作的代码 - 可以是渲染视图的,初始化models的,或者其他的应用逻辑。

    在这篇文档中我们将创建一个管理用户数据的简单应用,最终你将指导如何使用Ext JS 4应用架构去组织你的应用。

    对系统架构来说,提供结构和保持一致性,与实际的类和framework代码同样重要。遵循我们的惯例可以带来一系列非常重要的好处:

    • 所有的应用都以同一种方式工作,所以你只需要学习一次。
    • 不同应用之间可以共享代码,因为他们都以同种方式工作
    • 你可以用我们的build工具来创建你的系统的优化版本供production使用

    1. 文件结构
    --------------------------------------------------------------------------------------------------
    Ext JS 4对所有应用定义相同的目录结构。关于应用的基本文件结构的详细解释请参考Getting Started Guide.应用MVC结构,所有的类都在app/文件夹下,依次包含子文件夹来命名你的models, views, controllersstores。以下是我们做完这个简单的例子后,最终的文件结构:

    在这个例子里,我们将整个application放到”account_manager”文件夹下,Ext JS 4 SDK里的必要文件放到 ext-4/文件夹下。因此我们的index.hmtl内容如下:

    <html>

    <head>

        <title>Account Manager</title>

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

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

        <scripttype="text/javascript"src="app.js"></script>

    </head>

    <body></body>

    </html>

    2.app.js里创建项目

    Ext JS 4的所有应用都以Application这个实例作为入口。Application里包含你的应用的全局设置(例如应用的名称),并且维护此应用中所有models, views controllers的引用。Application同事包含launch方法,这个方法在加载时自动运行。

    现在让我们创建一个简单的Account Manager应用来帮助我们管理用户账号。首先我们定义一个全局namespace。所有Ext JS的应用程序应该只使用一个单一的全局变量,应用所有的类都嵌套在这个全局变量中。通常我们希望这个全局变量短一点,所以在这个例子里我们使用“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'

                   }

               ]

            });

        }

    });

    这里发生了几件事情。首先我们invoke(触发)了Ext.application来创建一个Application类的实例,传了一个名称“AM“给它。这将自动为我们创建一个全局变量AM,代表由”appFoder”配置的“app”文件夹,并将这个namespace注册到Ext.Loader。其次,为我们提供了一个简单的launch方法,创建了一个Viewport,这个Viewport包含一个填充整个screenPanel

    3.定义控制器

     Controllers-控制器是用来绑定整个application的胶水。它们真正做的就是监听视图(通常来自于视图)以及做出响应。接着Account Manager这个例子,我们创建一个控制器。在app/Controller下创建一个User.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');

        }

    });

    现在让我们在app.js里加上刚创建的Users控制器:

    Ext.application({

        ...

        controllers: [

            'Users'

        ],

        ...

    });

    当我们在浏览器里访问index.html时,Users控制器会自动加载(因为前面我们在Application里指定了),init方法在Applicationlaunch方法之前被调用。

    Init方法用来定义你的controllerview如何交互,通常与”control”这个控制器方法一起使用。”control”方法用来监听视图类的事件,以handler方法来控制行为。现在让我们更新Users控制器来告诉我们panel是什么时候被渲染的。

    Ext.define('AM.controller.Users', {

        extend: 'Ext.app.Controller',

        init: function() {

            this.control({

               'viewport > panel': {

                   render: this.onPanelRendered

               }

            });

        },

        onPanelRendered: function() {

            console.log('The panel was rendered');

        }

    });

    我们更新了init方法,以this.control方法来监听视图。Control方法使用了新的组件查询(ComponentQuery)机制,可以快速方便地获得页面组件的引用。如果你对ComponentQuery不熟悉,请参考ComponentQuery Documentation里的详细解释。简单说来,它使我们传类似CSS的选择器就可以找到页面上所有匹配的组件。

    init方法里我们使用了‘viewport > panel ‘,被解析成“找出viewport的所有为Panel的直接子组件“。然后我们提供一个处理对象,对应事件名(在这个例子里是render)。效果是,当任何匹配选择器的组件触发render事件时,都将调用onPanelRendered方法。

    现在启动我们的程序可以看到以下效果:

    虽然不像其它很多程序那样让人兴奋,但这个例子体现了开始组织代码是如此简单。现在给我们的程序加上一个grid.

    4.定义视图
    --------------------------------------------------------------------------------------------------------------------------------

    截止到目前为止,我们的程序只有两个文件,app.jsapp/controller/User.js。现在我们想增加一个grid现实所有用户,是时候更好地组织我们的逻辑,并开始使用视图了。

    View其实也是组件,通常被定义为Ext JS组件的子类。我们创建一个新文件app/view/user/List.js,加上一下代码:

    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: 'ed@sencha.com'},

                   {name: 'Tommy', email:'tommy@sencha.com'}

               ]

            };

            this.columns = [

               {header: 'Name', dataIndex: 'name', flex: 1},

               {header: 'Email', dataIndex:'email', flex: 1}

            ];

            this.callParent(arguments);

        }

    });

    我们的View类不过是一个普通类。在这个例子里我们继承了Grid组件,设置一个别名,这样我们就能把它作为xtype使用了(后面会讲到)。同时我们设置了grid渲染所需的storecolumns参数。

    接下来我们需要把这个视图加到User控制器里。因为我们设置了“widget.”这种形式的别名,我们就可以像之前使用”panel”那样将userlist作为xtype使用。

    Ext.define('AM.controller.Users', {

        extend: 'Ext.app.Controller',

        views: [

            'user.List'

        ],

        init: ...

        onPanelRendered: ...

    });

    然后在app.jslaunch方法里加载它。

    Ext.application({

        ...

        launch: function() {

            Ext.create('Ext.container.Viewport', {

               layout: 'fit',

               items: {

                   xtype: 'userlist'

               }

            });

        }

    });

    这里还需要注意的是,我们在视图数组里指定’user.list’。这是告诉程序自动加载这个文件,这样当启动时我们就可以它。程序使用了Ext JS 4的新自动加载系统,从服务器端自动获取此文件。现在刷新页面可以看到如下效果:

    5.控制Grid
    ------------------------------------------------------------------------------------------------------------

    注意onPanelRendered方法仍会被调用。因为grid类仍满足‘viewport > panel’的条件。因为我们的类继承Grid,从而也继承了Panel

    此时,我们给这个选择器增加的监听事件会被所有viewport的直接子组件调用,如果这个子组件是PanelPanel子类。为了使程序逻辑更严谨,我们用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'));

        }

    });

    注意我们把选择器改成了更简单的’userlist’,事件名改成了’itemdbclick”,事件处理方法改成了’editUser’这里当我们双击用户时,打印出用户名。

    在控制台打印出来也不错,不过我们实际想坐的是编辑用户。让我们实现这个功能吧,增加一个新视图app/view/user/Edit.js:

    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.我们仍使用initComonent指定更复杂的itemsbuttons对象。我们使用了’fit’的布局方式,和一个包含了编辑姓名、邮箱地址的’form’。然后我们创建了两个按钮,一个用来关闭窗口,一个用来保存我们的改动。

    我们需要做的就是把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获得编辑窗口的引用。所有Ext JS 4组件都有一个‘down’方法,这个方法可以用ComponentQuery快速找到所有子组件。

    双击grid的行,现在像这样:

    6.创建ModelStore
    --------------------------------------------------------------------------------------------

    现在已经有了编辑框,是时候编辑用户并保存了。在此之前,我们重构一下我们的代码。

    现在AM.view.user.List组件是在内部创建的Store.这样也可以运行,但我们更希望引用其它地方的Store,这样我们就可以更改里面的数据了。我们将把Store拆分到属于它自己的文件里– app/store/Users.js:

    Ext.define('AM.store.Users', {

        extend: 'Ext.data.Store',

        fields: ['name','email'],

        data: [

            {name: 'Ed',   email: 'ed@sencha.com'},

            {name: 'Tommy', email:'tommy@sencha.com'}

        ]

    });

    然后我们需要做两个小改动,首先我们要在控制器里加入这个Store

    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',

        // we no longer define the Users store in the `initComponent` method

        store: 'Users',

        initComponent: function() {

            this.columns = [

            ...

    });

    通过在Users控制器加了store,store就能自动加载到页面,并且可以在view里应用它(这个例子里只需简单配置store:’Users’)。

    此时,fields’name’’email’)是在store内部定义的。在Ext JS 4里我们有功能强大的Ext.data.Model,我们在编辑用户时可以使用它。让我们使用Model重构Store,将以下代码放到app/model/User.js里。

    Ext.define('AM.model.User', {

        extend: 'Ext.data.Model',

        fields: ['name','email']

    });

    这就是我们定义Model所需要做的。先让我们更新Store,引用Model名而不是提供内部fields定义:

    Ext.define('AM.store.Users', {

        extend: 'Ext.data.Store',

        model: 'AM.model.User',

        data: [

            {name: 'Ed',   email: 'ed@sencha.com'},

            {name: 'Tommy', email:'tommy@sencha.com'}

        ]

    });

    我们还需要在Users控制器里引用User Model

    Ext.define('AM.controller.Users', {

        extend: 'Ext.app.Controller',

        stores: ['Users'],

        models: ['User'],

        ...

    });

    重构只是让接下来的部分更简单,但对程序现在的行为不会有影响。如果我们刷新页面,双击一行,我们可以看到编辑用户窗口仍然弹出。接下来是时候完成编辑的功能了:

    7.通过Model保存数据
    -------------------------------------------------------------------------------------------

    现在我们已经有一个用户grid加载数据,双击一行用户可以打开编辑窗口,我们希望可以保存对用户信息的更改。编辑用户的窗口包含一个form(里面有姓名有荷香地址)和一个保存按钮。首先我们更新下控制器的init方法监听save按钮的单击事件。

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

        }

        ...

    });

    我们给this.control增加了一个选择器调用,这次是’useredit button[action=save]’. 这和第一个选择器一样,使用’useredit’xtype定位编辑用户的窗口,然后找窗口里actionsave的所有按钮。在编辑用户窗口里我们已经给保存按钮定义了{action:’save’},可以很容易定位到这个按钮。

    这样当点击保存按钮,就调用了updateUser这个方法。

    我们看到,我们的处理事件被正确地绑定到保存按钮的单击事件上了。然后我们来实现updateUser的真实逻辑。在这个方法里我们要从form里取得数据,更新用户信息,然后保存会前面创建的用户store。看我们应该怎么做:

    updateUser: function(button) {

        var win   = button.up('window'),

            form   = win.down('form'),

            record = form.getRecord(),

            values = form.getValues();

        record.set(values);

        win.close();

    }

    我们来分析下。单击事件提供了一个用户点击的按钮的引用,但我们实际想得到的是包含数据的formwindow。这这里我们再次使用了组件查询,首先通过button.up(‘window’)获得编辑用户窗口的引用,然后通过win.down(‘form’)获得form.

    然后我们获取当前加载的记录,然后根据用户的输入更新记录。最后我们关闭窗口,回到grid。现在再run程序,把姓名字段改成’Ed Spencer’然后单击保存:

    8.保存到服务器端
    -------------------------------------------------------------------------------------------

    接下来我们让程序可以和服务器端交互。现在我们是把两条用户信息hard codeUsers Store里的。所以首先替换成AJAX读取数据:

    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'

            }

        }

    });

    这里我们以Proxy取代了‘data’属性。Proxies(代理)是Ext JS 4里从Store,Model加载和保存数据的方法。有AJAX,JSON-PHTML5 localStorage以及其它的代理。这里我们用了一个简单的AJAX代理,告诉程序从’data/users.json’这个url去加载数据。

    同时我们给Proxy设置了ReaderReader用来将服务器响应解析成Store可以识别的格式。这里我们使用了JSON Reader,还指定了rootsuccessProperty。最后我们创建一个data/user.json,然后加上之前的data

    {

        "success":true,

        "users": [

            {"id": 1,"name":'Ed',   "email":"ed@sencha.com"},

            {"id": 2,"name":'Tommy',"email":"tommy@sencha.com"}

        ]

    }

    另外的对store的改动,是设置autoLoadtrue,意思是Store即时通过Proxy请求数据。如果我们现在刷新页面,效果跟之前一样,不同的是程序的数据不再是hard code进去的。

    最后我们想把改动返回到服务器端。在这个例子里我们是用的静态JSON文件,所以我们看不到数据库数据的改变,不过至少,我们可以验证下一切组合到一起是没有问题的。首先我们改下proxy,告诉它发送update请求到另一个url:

    proxy: {

        type: 'ajax',

        api: {

            read: 'data/users.json',

            update: 'data/updateUsers.json'

        },

        reader: {

            type: 'json',

            root: 'users',

            successProperty: 'success'

        }

    }

    我们仍然从users.json读数据,但改动将会被发送到updateUsers.json。这是为了不改动我们的测试数据,又能验证结果。更新了一条用户记录后,updateUsers.json文件只是包含 {“success”:true}。由于更新是通过HTTP POST请求的,我们需要新建一个空白文件,以防遇到404错误。

    另一个改动是,我们在更新后需要告诉Store去同步数据,所以在updateUser方法里需要增加一行代码如下:

    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();

    }

    现在我们可以运行整个程序,确保一切运行正常。我们编辑一行,点击保存按钮,看到请求的确发送到updateUser.json了。

     

  • 相关阅读:
    6个laravel常用目录路径函数
    Laravel上传产品图片Uploading img
    Laravel删除产品-CRUD之delete(destroy)
    Laravel编辑产品-CRUD之edit和update
    Laravel展示产品-CRUD之show
    Laravel创建产品-CRUD之Create and Store
    内存泄露从入门到精通三部曲之排查方法篇
    P问题、NP问题、NPC问题、NP难问题的概念
    二维码的生成细节和原理
    一分钟认识GitHub
  • 原文地址:https://www.cnblogs.com/riskyer/p/3237128.html
Copyright © 2011-2022 走看看