ExtJS MVC結構

概述


大型的應用在開發和運維上都存在着困難。應用功能的調整和開發人員的調動都會影響對項目的掌控。ExtJS4帶來了一種新的應用結構。這種結構不止用於組織代碼,也能有效的減小必要的代碼量。javascript

此次ExtJS4的應用結構採用了MVC的形式。在這種形式下,Models和Controllers第一次被引入了ExtJS。目前已經有了許多MVC式的結構,這些結構大部分是大同小異。這裏是咱們定義的MVC結構:css

  • Model(模型)是字段及數據的集合(好比一個用戶模型包含用戶名字段和密碼字段)。模型用於數據的展現,也能夠經過關聯關係關聯到其餘的Model上。模型的工做形式很像ExtJS3的Record類,並一般和Stores一塊兒使用將數據展現到grids和其餘組件中;
  • View(視圖)是組件的的一種類型。grids、trees和panels都是視圖;
  • Controller(控制器)用於將代碼組合起來使應用運行。

在這篇文章中咱們將創建一個簡單的用戶數據管理應用。經過這個應用能夠對ExtJS4的MVC結構有初步的瞭解。html

ExtJS4的MVC結構提供了一套結構性和一致性的規範。在開發中會發現,Ext的MVC應用是由自定義類和框架代碼構成的。按慣例,先說下采用MVC結構開發的好處:java

  • 全部的應用採用相同的模式工做,只須要一次學習;
  • 全部的應用採用相同的模式工做,便於代碼共享;
  • 能夠基於ExtJS提供的開發工具進行開發優化。

 

文件結構


使用ExtJS的MVC模式開發應用須要使用統一的文件結構 。應用全部的類都放在app目錄下。按MVC結構,在app目錄下須要創建model、view、store和controller等子目錄。下圖爲咱們的一個示例應用開發完成後的目錄結構:ajax

Folder Structure

在這個例子中咱們封裝了一個應用程序稱爲「account_manager」,ExtJS4的sdk環境置於ext-4.0目錄中。以下爲index.html的代碼:數據庫

   1:  <html>
   2:  <head>
   3:      <title>Account Manager</title>
   4:      <link rel="stylesheet" type="text/css" href="ext-4.0/resources/css/ext-all.css">
   5:      <script type="text/javascript" src="ext-4.0/ext-debug.js"></script>
   6:      <script type="text/javascript" src="app.js"></script>
   7:  </head>
   8:  <body></body>
   9:  </html>

 

從app.js開始建立應用


每一個ExtJS4的MVC應用都是經過一個Appliaction類的實例啓動。在Application中包含應用的全局定義(好比應用名稱),以及應用中所使用到的模型、視圖和控制器的引用。此外,Application類還有一個launch函數。launch函數在頁面加載完成後執行。json

接下來咱們將建立一個account_manager應用來管理用戶帳戶信息。首先須要爲這個應用定義一個全局的命名空間。全部ExtJS4的MVC應用應該只有一個全局命名空間,應用中全部的類都置於這個命名空間下。通常咱們會給這個命名空間定義一個較短的名稱,這裏咱們使用「AM」。api

   1:  Ext.application({
   2:      name: 'AM',
   3:   
   4:      appFolder: 'app',
   5:   
   6:      launch: function() {
   7:          Ext.create('Ext.container.Viewport', {
   8:              layout: 'fit',
   9:              items: [
  10:                  {
  11:                      xtype: 'panel',
  12:                      title: 'Users',
  13:                      html : 'List of users will go here'
  14:                  }
  15:              ]
  16:          });
  17:      }
  18:  });

這裏實現的功能很簡單。首先,咱們調用 Ext.application建立了一個Application類的實例,並將之命名爲「AM」。同時,這裏自動建立了一個全局變量「AM」,併爲Ext.Loader註冊了一個命名空間。而後咱們設置appFolder屬性爲app目錄。最後咱們設置了launch函數,並在函數中建立了包含一個panel的Viewport來填充整個屏幕。數組

Initial view with a simple Panel

定義一個控制器(controller)


能夠說控制器(controller)是把整個應用綁定在一塊兒的膠水。它們(控制器)執行的工做是事件監聽(一般來自view)並作出相應處理。瀏覽器

咱們繼續完成account_manager應用,接下來咱們將建立一個控制器。新建一個js文件app/controller/Users.js,代碼以下:

   1:  Ext.define('AM.controller.Users', {
   2:      extend: 'Ext.app.Controller',
   3:   
   4:      init: function() {
   5:          console.log('Initialized Users! This happens before the Application launch function is called');
   6:      }
   7:  });

將咱們新建的控制器Users添加到應用中,爲app.js添加屬性以下:

   1:  Ext.application({
   2:      ...
   3:   
   4:      controllers: [
   5:          'Users'
   6:      ],
   7:   
   8:      ...
   9:  });

當咱們經過index.html在瀏覽器中加載應用時,控制器Users也會被自動加載(由於咱們在上面的應用定義中作了設置),而後Users的init函數會被調用——在Application類的launch函數執行以前。

在控制器與視圖(view)的交互中,init函數的做用十分重要,一般它會和控制器的另外一個函數control一塊兒使用。使用control函數比較容易實現對視圖事件的監聽和響應。

咱們調整下Users控制器,經過control函數來看看panel何時被render(渲染):

   1:  Ext.define('AM.controller.Users', {
   2:      extend: 'Ext.app.Controller',
   3:   
   4:      init: function() {
   5:          this.control({
   6:              'viewport > panel': {
   7:                  render: this.onPanelRendered
   8:              }
   9:          });
  10:      },
  11:   
  12:      onPanelRendered: function() {
  13:          console.log('The panel was rendered');
  14:      }
  15:  });

在上面的代碼中,咱們調整了users控制器的init函數,使用this.control爲應用中的視圖創建了監聽。control函數使用新的ComponentQuery引擎簡單快速的實現了對頁面上的組件的引用。(關於ComponentQuery的內容請參看文檔。簡單地說,就是使用css式的選擇器實現了對頁面上組件的快速匹配)。

在上面的init函數中咱們使用了「viewport > panel」這樣的語句。「viewport > panel」是一個選擇器,它的含義是「找到Viewport的每一個直接子Panel」。隨後咱們提供了一個對象,在這個對象中包含事件名稱(上例中便是render)及相應的處理函數。實現的效果是任何一個匹配咱們定義的選擇器的組件觸發了render事件後即會調用onPanelRendered函數。

如今運行下應用看看效果:

Controller listener

並非一個很炫的應用,但它已經展現了使用MVC管理代碼的好處。接下來咱們給這個應用添加一個表格,使它變得豐滿些。

 

定義一個視圖


目前爲止咱們的應用只有兩個文件、極少的幾行代碼。如今咱們想給這個應用添加一個表格來展現系統中的用戶。是時候使用視圖了。

視圖只是一個組件,是繼承自ExtJS現有組件的類。咱們將定義一個用戶信息列表類,新建文件app/view/user/List.js,代碼以下:

   1:  Ext.define('AM.view.user.List' ,{
   2:      extend: 'Ext.grid.Panel',
   3:      alias : 'widget.userlist',
   4:   
   5:      title : 'All Users',
   6:   
   7:      initComponent: function() {
   8:          this.store = {
   9:              fields: ['name', 'email'],
  10:              data  : [
  11:                  {name: 'Ed',    email: 'ed@sencha.com'},
  12:                  {name: 'Tommy', email: 'tommy@sencha.com'}
  13:              ]
  14:          };
  15:   
  16:          this.columns = [
  17:              {header: 'Name',  dataIndex: 'name',  flex: 1},
  18:              {header: 'Email', dataIndex: 'email', flex: 1}
  19:          ];
  20:   
  21:          this.callParent(arguments);
  22:      }
  23:  });

如上,這個視圖類也只是一個簡單的普通類。咱們只是繼承了Grid組件,定義了一個別名,經過這個別名咱們可使用xtype調用這個類(通常是這麼用,還有別的用處)。同時咱們還添加了列表須要使用的store和columns信息。

接下來咱們須要將這個視圖添加到Users控制器中。咱們已經使用'widget.'形式設置了類的別名,因此能夠直接使用「userlist」做xtype,就跟之前使用「xtype: ‘panel’」同樣。

   1:  Ext.define('AM.controller.Users', {
   2:      extend: 'Ext.app.Controller',
   3:   
   4:      views: [
   5:          'user.List'
   6:      ],
   7:   
   8:      init: ...
   9:   
  10:      onPanelRendered: ...
  11:  });

而後咱們在應用的Viewport佈局中調用這個類。這須要在app.js中修改launch函數:

   1:  Ext.application({
   2:      ...
   3:   
   4:      launch: function() {
   5:          Ext.create('Ext.container.Viewport', {
   6:              layout: 'fit',
   7:              items: {
   8:                  xtype: 'userlist'
   9:              }
  10:          });
  11:      }
  12:  });

這裏還有一點須要注意,在控制器的views數組中,咱們設置了'user.List' 。這告訴了應用去調用視圖文件view/user/List.js,這樣在應用加載時就能夠直接使用這個文件了。這裏使用了ExtJS4新增的的動態調用服務以從服務器中獲取文件。如今再看看咱們的acount_manager應用:

Our first View

控制表格


請注意,代碼更新後onPanelRendered函數仍然被調用了。這表名咱們自定義的列表類仍然匹配「viewport > panel」選擇器。由於咱們自定義的userlist類繼承自Grid類,Grid類繼承自Panel類。

此時咱們添加到選擇器中的監聽仍然被Viewport直屬的Panel類或其子類所調用,咱們能夠利用這點對咱們的用戶信息列表的功能作些強化。仍是使用init函數,不過要改成監聽用戶信息記錄上的雙擊事件,使雙擊後能夠對用戶信息進行編輯。調整控制器:

   1:  views: [
   2:          'user.List'
   3:      ],
   4:   
   5:      init: function() {
   6:          this.control({
   7:              'userlist': {
   8:                  itemdblclick: this.editUser
   9:              }
  10:          });
  11:      },
  12:   
  13:      editUser: function(grid, record) {
  14:          console.log('Double clicked on ' + record.get('name'));
  15:      }

對控制器Users咱們作了以下調整:

修改了ComponentQuery的選擇器,如今只是使用‘userlist’;修改監聽的事件爲「itemdblclick」;調整事件處理函數爲「editUser」。再次運行應用,雙擊記錄時會在控制檯上輸出相應的信息:

Double click handler

應用運行的很好。可是咱們不會知足於簡單的在控制檯上輸出信息,咱們但願能真正的編輯用戶信息。這裏要建立一個新的視圖:app/view/user/Edit.js:

   1:  Ext.define('AM.view.user.Edit', {
   2:      extend: 'Ext.window.Window',
   3:      alias : 'widget.useredit',
   4:   
   5:      title : 'Edit User',
   6:      layout: 'fit',
   7:      autoShow: true,
   8:   
   9:      initComponent: function() {
  10:          this.items = [
  11:              {
  12:                  xtype: 'form',
  13:                  items: [
  14:                      {
  15:                          xtype: 'textfield',
  16:                          name : 'name',
  17:                          fieldLabel: 'Name'
  18:                      },
  19:                      {
  20:                          xtype: 'textfield',
  21:                          name : 'email',
  22:                          fieldLabel: 'Email'
  23:                      }
  24:                  ]
  25:              }
  26:          ];
  27:   
  28:          this.buttons = [
  29:              {
  30:                  text: 'Save',
  31:                  action: 'save'
  32:              },
  33:              {
  34:                  text: 'Cancel',
  35:                  scope: this,
  36:                  handler: this.close
  37:              }
  38:          ];
  39:   
  40:          this.callParent(arguments);
  41:      }
  42:  });

咱們再次定義了一個ExtJS組件的子類。此次是繼承了Ext.window.Window。咱們仍須要使用initComponent函數爲編輯視圖Edit添加表單元素和按鈕。佈局採用了「fit」形式,窗體下只有一個form面板,在form面板中有兩個文本框用以編輯姓名和郵件地址。最後還定義了兩個按鈕用以關閉窗體和保存更改。

而後咱們要作的就是把這個編輯視圖添加到控制器,並將用戶信息添加到編輯視圖。見代碼:

   1:  Ext.define('AM.controller.Users', {
   2:      extend: 'Ext.app.Controller',
   3:   
   4:      views: [
   5:          'user.List',
   6:          'user.Edit'
   7:      ],
   8:   
   9:      init: ...
  10:   
  11:      editUser: function(grid, record) {
  12:          var view = Ext.widget('useredit');
  13:   
  14:          view.down('form').loadRecord(record);
  15:      }
  16:  });

重點看一下editUser函數。在這個函數中,咱們使用了函數’Ext.widget’,其功能相似於Ext.create('widget.useredit')。而後咱們再次使用ComponentQuery創建了到編輯視圖的映射。每一個ExtJS4的組件都有一個down函數以接收ComponentQuery的選擇器並快速的查找選擇器對應的直屬子元素。

雙擊列表中的行,效果以下圖:

Loading the form

建立model和store


如今咱們已經作好了編輯視圖,也差很少能夠進行編輯和保存了。可是在作這些工做以前,咱們還須要對咱們的代碼進行一次重構。

如今的AM.view.user.List組件中,store是寫在List類中。雖然如今應用也運行的很好,可是咱們更傾向於將store獨立出來,這樣也便於咱們對store中的數據進行處理。如今咱們開始將store中從List視圖中拆分出來放到app/store/Users.js中:

   1:  Ext.define('AM.store.Users', {
   2:      extend: 'Ext.data.Store',
   3:      fields: ['name', 'email'],
   4:      data: [
   5:          {name: 'Ed',    email: 'ed@sencha.com'},
   6:          {name: 'Tommy', email: 'tommy@sencha.com'}
   7:      ]
   8:  });

而後咱們還須要作兩處小的調整。

首先在Users控制器中添加對store的調用:

   1:  Ext.define('AM.controller.Users', {
   2:      extend: 'Ext.app.Controller',
   3:      stores: [
   4:          'Users'
   5:      ],
   6:      ...
   7:  });

而後更新app/view/user/List.js,添加對store的引用:

   1:  Ext.define('AM.view.user.List' ,{
   2:      extend: 'Ext.grid.Panel',
   3:      alias : 'widget.userlist',
   4:   
   5:      //we no longer define the Users store in the `initComponent` method
   6:      store: 'Users',
   7:   
   8:      ...
   9:  });

在控制器中引用store的目的是動態的調用store定義頁,併爲調用的store賦上一個簡短的storeId。這使在視圖中引用store更加方便(在上例中只是添加了一個屬性 store: 'Users')。

如今咱們是將字段(name和email)定義在store中。這樣也能夠工做了,可是ExtJS4提供了一個頗有用的類Ext.data.Model來幫助咱們。咱們將使用Model再次重構咱們的代碼,Model定義於app/model/User.js中:

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

完成model的定義後,須要在控制器和store中添加對Model的引用,並從store去掉對應字段的聲明:

   1:  //the Users controller will make sure that the User model is included on the page and available to our app
   2:  Ext.define('AM.controller.Users', {
   3:      extend: 'Ext.app.Controller',
   4:      stores: ['Users'],
   5:      models: ['User'],
   6:      ...
   7:  });
   8:   
   9:  // we now reference the Model instead of defining fields inline
  10:  Ext.define('AM.store.Users', {
  11:      extend: 'Ext.data.Store',
  12:      model: 'AM.model.User',
  13:   
  14:      data: [
  15:          {name: 'Ed',    email: 'ed@sencha.com'},
  16:          {name: 'Tommy', email: 'tommy@sencha.com'}
  17:      ]
  18:  });

咱們所作的重構工做不會立刻產生效果,不過會有助於下一環節的工做。再次刷新頁面運行應用會發現並沒有任何變化。不過是時候完成編輯功能了。

Loading the form

在Model中保存數據


如今咱們有一個用戶信息列表了,也能夠雙擊記錄打開編輯窗口了。接下來須要實現的任務就是保存用戶編輯結果。如今咱們的用戶信息編輯窗口有一個表單和一個保存按鈕「save」。接下來咱們須要調整控制器的init函數,添加一個對「save」按鈕的監聽:

   1:  Ext.define('AM.controller.Users', {
   2:      init: function() {
   3:          this.control({
   4:              'viewport > userlist': {
   5:                  itemdblclick: this.editUser
   6:              },
   7:              'useredit button[action=save]': {
   8:                  click: this.updateUser
   9:              }
  10:          });
  11:      },
  12:   
  13:      updateUser: function(button) {
  14:          console.log('clicked the Save button');
  15:      }
  16:  });

咱們在控制器中添加了第二個ComponentQuery選擇器:‘'useredit button[action=save]'’。它和第一個選擇器工做模式相同,首先根據xtype‘useredit’找到咱們定義的用戶信息編輯窗體,而後在窗體中查找action屬性爲‘save’的按鈕。在咱們定義用戶信息編輯窗體類時,咱們給「save」按鈕設置action:’save’屬性就給咱們提供了一個能夠快速定位到這個按鈕的方法。

咱們很高興看到在咱們點擊save按鈕時updateUser函數被調用並執行了。

Seeing the save handler

咱們已經將save按鈕點擊事件的監聽與處理完成了。接下來要作的就是完善updateUser這個函數,真正實現用戶信息的編輯。在updateUser函數中咱們須要從表單中獲取信息並保存到用戶信息store中。讓咱們看看功能是怎樣實現的:

   1:  updateUser: function(button) {
   2:      var win    = button.up('window'),
   3:          form   = win.down('form'),
   4:          record = form.getRecord(),
   5:          values = form.getValues();
   6:   
   7:      record.set(values);
   8:      win.close();
   9:  }

這裏須要打斷一下作些說明。經過按鈕的點擊事件咱們獲取了對‘save’按鈕的引用,可是咱們真正須要的是包含數據的表單及用戶信息編輯窗體。爲了更快的解決問題咱們再次使用了ComponentQuery的選擇器,首先使用button.up('window')獲取了用戶信息編輯窗體,然後使用win.down('form')獲取了表單。

這以後咱們要作的工做就很簡單了,獲取加載到表單中的record,並用用戶編輯後的信息更新record。最後關閉窗體,回到用戶信息列表上。下圖是應用運行後的結果,咱們將第一條記錄的姓名改成了「Ed Spencer」:

The record in the grid has been updated

保存記錄到服務器上


剛剛的工做很簡單吧。咱們還須要完成最後一步——實現與服務器端的交互。以前咱們只是生硬地將用戶信息寫在store中,接下來咱們將嘗試使用Ajax獲取用戶信息:

   1:  Ext.define('AM.store.Users', {
   2:      extend: 'Ext.data.Store',
   3:      model: 'AM.model.User',
   4:      autoLoad: true,
   5:   
   6:      proxy: {
   7:          type: 'ajax',
   8:          url: 'data/users.json',
   9:          reader: {
  10:              type: 'json',
  11:              root: 'users',
  12:              successProperty: 'success'
  13:          }
  14:      }
  15:  });

這裏咱們去掉了data屬性,取而代之的是proxy屬性。proxy是Extjs4中獲取和保存數據的一種方式。ExtJS4中有多種形式的proxy,這裏咱們使用了比較簡單的ajax proxy,咱們告訴它從'data/users.json'中獲取信息。

從上面的代碼中看到,咱們給proxy提供了一個reader對象。reader的做用是解碼服務器端反饋的數據集信息。此次咱們使用的是JSON Reader,並設置了它的root和successProperty屬性。最後咱們還須要建立一個'data/users.json'文件,將咱們之前使用的數據複製進去:

   1:  {
   2:      success: true,
   3:      users: [
   4:          {id: 1, name: 'Ed',    email: 'ed@sencha.com'},
   5:          {id: 2, name: 'Tommy', email: 'tommy@sencha.com'}
   6:      ]
   7:  }

在store中的另外一處改動是將autoLoad屬性設置爲true。這意味着stroe會主動向proxy發出請求加載數據。如今咱們刷新頁面從新加載應用並不能看到任何變化。可是實際上獲取數據的方式已經不同了。

咱們要作的最後一件事就是將對數據所作的改變返回到服務器上。在咱們這個例子中咱們是使用靜態的JSON文件在服務器端存儲數據,因此咱們不能看到任何數據庫的變化。可是至少咱們能夠驗證應用是在正常工做的。首先咱們須要對store的proxy作出一點小的調整告訴它將更新信息發送到另外一個url:

   1:  proxy: {
   2:      type: 'ajax',
   3:      api: {
   4:          read: 'data/users.json',
   5:          update: 'data/updateUsers.json'
   6:      },
   7:      reader: {
   8:          type: 'json',
   9:          root: 'users',
  10:          successProperty: 'success'
  11:      }
  12:  }

咱們仍然會從users.json中獲取數據,可是全部的更新都會發送到updateUsers.json。這隻會返回一個虛擬的相應,讓咱們知道應用在正常運行。updateUsers.json中的內容是{"success": true}。而後還要作的就是告訴Store在編輯完成後同步一次記錄,爲此咱們還要在updateUser函數中添加一行代碼:

   1:  updateUser: function(button) {
   2:      var win    = button.up('window'),
   3:          form   = win.down('form'),
   4:          record = form.getRecord(),
   5:          values = form.getValues();
   6:   
   7:      record.set(values);
   8:      win.close();
   9:      this.getUsersStore().sync();
  10:  }

如今咱們的這個示例應用已經完成了。咱們再運行這個應用一次,編輯一條記錄,點擊保存按鈕,而後查看request是否正確的發送給了updateUser.json文件:

The record in the grid has been updated

示例應用account_manager的源碼可在ExtJS4的文檔中找到,所在目錄是examples/app/simple。

做者注:本文譯自Extjs4.0文檔中的《MVC Architecture》一文。限於在下的英文水平及對Ext的理解,因此不免有些不足之處。不過是抱着本身學習也方便你們的心思,拋磚引玉勉強翻譯了全文。若是有不通之處還請及時指正。

相關文章
相關標籤/搜索