天天看點

Extjs 4.2.0 MVC 架構

内容:

1. 檔案結構

2. 建立項目

3. 定義控制器

4. 定義視圖

5. 控制Grid

6. 建立Model和Store

7. 通過Model儲存資料

8. 儲存到伺服器端

大型用戶端程式通常都難寫,難組織,難以維護。項目經常由于增加功能,增加開發人員而很快失控。Ext JS 4提出新的項目結構,不僅組織你的代碼,并且減少代碼量。

我們的系統結構延續“類MVC模式”,第一次引入Models(模型)和Controllers(控制器)的概念。現在有很多MVC架構,他們或多或少有細微差别。以下是我們對MVC的定義:

  • Model是字段和對應資料的組合(例如User Model有username和password兩個字段)。Models知道如何通過資料包(data package)持久化,還可以通過associations(聯系)同其他models關聯。Models很類似于Ext Js 3裡的Record類,通常與Stores一起将資料展現到grids和其他components上。
  • View可以是任何類型的component,grids, trees和panels都是視圖。
  • Controllers是一個特殊的地方,用來放使application工作的代碼 - 可以是渲染視圖的,初始化models的,或者其他的應用邏輯。

在這篇文檔中我們将建立一個管理使用者資料的簡單應用,最終你将指導如何使用Ext JS 4應用架構去組織你的應用。

對系統架構來說,提供結構和保持一緻性,與實際的類和framework代碼同樣重要。遵循我們的慣例可以帶來一系列非常重要的好處:

  • 所有的應用都以同一種方式工作,是以你隻需要學習一次。
  • 不同應用之間可以共享代碼,因為他們都以同種方式工作
  • 你可以用我們的build工具來建立你的系統的優化版本供production使用

1. 檔案結構

--------------------------------------------------------------------------------------------------Ext JS 4對所有應用定義相同的目錄結構。關于應用的基本檔案結構的詳細解釋請參考Getting Started Guide.應用MVC結構,所有的類都在app/檔案夾下,依次包含子檔案夾來命名你的models, views, controllers和stores。以下是我們做完這個簡單的例子後,最終的檔案結構:

Extjs 4.2.0 MVC 架構

在這個例子裡,我們将整個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包含一個填充整個screen的Panel。

Extjs 4.2.0 MVC 架構

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控制器:

    ...

    controllers: [

        'Users'

    ],

當我們在浏覽器裡通路index.html時,Users控制器會自動加載(因為前面我們在Application裡指定了),init方法在Application的launch方法之前被調用。

Init方法用來定義你的controller和view如何互動,通常與”control”這個控制器方法一起使用。”control”方法用來監聽視圖類的事件,以handler方法來控制行為。現在讓我們更新Users控制器來告訴我們panel是什麼時候被渲染的。

        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方法。

現在啟動我們的程式可以看到以下效果:

Extjs 4.2.0 MVC 架構

雖然不像其它很多程式那樣讓人興奮,但這個例子展現了開始組織代碼是如此簡單。現在給我們的程式加上一個grid.

4.定義視圖

--------------------------------------------------------------------------------------------------------------------------------

截止到目前為止,我們的程式隻有兩個檔案,app.js和app/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: '[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類不過是一個普通類。在這個例子裡我們繼承了Grid元件,設定一個别名,這樣我們就能把它作為xtype使用了(後面會講到)。同時我們設定了grid渲染所需的store和columns參數。

接下來我們需要把這個視圖加到User控制器裡。因為我們設定了“widget.”這種形式的别名,我們就可以像之前使用”panel”那樣将userlist作為xtype使用。

    views: [

        'user.List'

    init: ...

    onPanelRendered: ...

然後在app.js的launch方法裡加載它。

           items: {

               xtype: 'userlist'

這裡還需要注意的是,我們在視圖數組裡指定’user.list’。這是告訴程式自動加載這個檔案,這樣當啟動時我們就可以它。程式使用了Ext JS 4的新自動加載系統,從伺服器端自動擷取此檔案。現在重新整理頁面可以看到如下效果:

Extjs 4.2.0 MVC 架構

5.控制Grid

------------------------------------------------------------------------------------------------------------

注意onPanelRendered方法仍會被調用。因為grid類仍滿足‘viewport > panel’的條件。因為我們的類繼承Grid,進而也繼承了Panel。

此時,我們給這個選擇器增加的監聽事件會被所有viewport的直接子元件調用,如果這個子元件是Panel或Panel子類。為了使程式邏輯更嚴謹,我們用xtype來指定。這裡我們監聽輕按兩下grid的行,然後編輯使用者。

           'userlist': {

               itemdblclick: this.editUser

    editUser: function(grid, record) {

        console.log('Double clicked on ' + record.get('name'));

注意我們把選擇器改成了更簡單的’userlist’,事件名改成了’itemdbclick”,事件處理方法改成了’editUser’。這裡當我們輕按兩下使用者時,列印出使用者名。

Extjs 4.2.0 MVC 架構

在控制台列印出來也不錯,不過我們實際想坐的是編輯使用者。讓我們實作這個功能吧,增加一個新視圖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,

        this.items = [

           {

               xtype: 'form',

               items: [

                   {

                       xtype: 'textfield',

                       name : 'name',

                       fieldLabel: 'Name'

                   },

                       name : 'email',

                       fieldLabel: 'Email'

                   }

               ]

        this.buttons = [

               text: 'Save',

               action: 'save'

           },

               text: 'Cancel',

               scope: this,

               handler: this.close

這次我們繼承自另一個已存在的元件– Ext.window.Window.我們仍使用initComonent指定更複雜的items和buttons對象。我們使用了’fit’的布局方式,和一個包含了編輯姓名、郵箱位址的’form’。然後我們建立了兩個按鈕,一個用來關閉視窗,一個用來儲存我們的改動。

我們需要做的就是把view加到控制器裡,渲染,并加載使用者。

        'user.List',

        'user.Edit'

        var view = Ext.widget('useredit');

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

這裡我們通過一個非常友善的方法 Ext.widget來建立視圖,這個方法等同于 Ext.create(‘widget.useredit’). 然後我們再次使用了ComponentQuery獲得編輯視窗的引用。所有Ext JS 4元件都有一個‘down’方法,這個方法可以用ComponentQuery快速找到所有子元件。

輕按兩下grid的行,現在像這樣:

Extjs 4.2.0 MVC 架構

6.建立Model和Store

--------------------------------------------------------------------------------------------

現在已經有了編輯框,是時候編輯使用者并儲存了。在此之前,我們重構一下我們的代碼。

現在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: '[email protected]'},

        {name: 'Tommy', email:'[email protected]'}

    ]

然後我們需要做兩個小改動,首先我們要在控制器裡加入這個Store:

    stores: [

然後我們更新app/view/user/List.js,通過id引用Store:

    extend: 'Ext.grid.Panel',

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

    store: 'Users',

        ...

通過在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定義:

    model: 'AM.model.User',

我們還需要在Users控制器裡引用User Model:

    stores: ['Users'],

    models: ['User'],

重構隻是讓接下來的部分更簡單,但對程式現在的行為不會有影響。如果我們重新整理頁面,輕按兩下一行,我們可以看到編輯使用者視窗仍然彈出。接下來是時候完成編輯的功能了:

Extjs 4.2.0 MVC 架構

7.通過Model儲存資料

-------------------------------------------------------------------------------------------

現在我們已經有一個使用者grid加載資料,輕按兩下一行使用者可以打開編輯視窗,我們希望可以儲存對使用者資訊的更改。編輯使用者的視窗包含一個form(裡面有姓名有荷香位址)和一個儲存按鈕。首先我們更新下控制器的init方法監聽save按鈕的單擊事件。

           'viewport > userlist': {

           'useredit button[action=save]': {

               click: this.updateUser

    updateUser: function(button) {

        console.log('clicked the Save button');

我們給this.control增加了一個選擇器調用,這次是’useredit button[action=save]’. 這和第一個選擇器一樣,使用’useredit’的xtype定位編輯使用者的視窗,然後找視窗裡action為save的所有按鈕。在編輯使用者視窗裡我們已經給儲存按鈕定義了{action:’save’},可以很容易定位到這個按鈕。

這樣當點選儲存按鈕,就調用了updateUser這個方法。

Extjs 4.2.0 MVC 架構

我們看到,我們的處理事件被正确地綁定到儲存按鈕的單擊事件上了。然後我們來實作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();

}

我們來分析下。單擊事件提供了一個使用者點選的按鈕的引用,但我們實際想得到的是包含資料的form和window。這這裡我們再次使用了元件查詢,首先通過button.up(‘window’)獲得編輯使用者視窗的引用,然後通過win.down(‘form’)獲得form.

然後我們擷取目前加載的記錄,然後根據使用者的輸入更新記錄。最後我們關閉視窗,回到grid。現在再run程式,把姓名字段改成’Ed Spencer’然後單擊儲存:

Extjs 4.2.0 MVC 架構

8.儲存到伺服器端

接下來我們讓程式可以和伺服器端互動。現在我們是把兩條使用者資訊hard code到Users Store裡的。是以首先替換成AJAX讀取資料:

    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-P和HTML5 localStorage以及其它的代理。這裡我們用了一個簡單的AJAX代理,告訴程式從’data/users.json’這個url去加載資料。

同時我們給Proxy設定了Reader。Reader用來将伺服器響應解析成Store可以識别的格式。這裡我們使用了JSON Reader,還指定了root和successProperty。最後我們建立一個data/user.json,然後加上之前的data:

{

    "success":true,

    "users": [

        {"id": 1,"name":'Ed',   "email":"[email protected]"},

        {"id": 2,"name":'Tommy',"email":"[email protected]"}

另外的對store的改動,是設定autoLoad為true,意思是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方法裡需要增加一行代碼如下:

    // synchronize the store after editing the record

    this.getUsersStore().sync();

現在我們可以運作整個程式,確定一切運作正常。我們編輯一行,點選儲存按鈕,看到請求的确發送到updateUser.json了。