一、ExtJs 4.x MVC模式的原理與作用
大規模客戶端應用通常不好實現不好組織也不好維護,因為功能和人力的不斷增加,這些應用的規模很快就會超出掌控能力,ExtJS4帶來了一個新的應用架構,不但可以組織代碼,還可以減少實現的內容。
新的應用架構遵照一個類MVC的模式,模型(Models)和控制器(Controllers)首次被引入。業界有很多種MVC架構,基本大同小異,ExtJS4的定義如下:
a.Model模型:模型是字段和它們的數據的集合,例如User模型帶有username和password字段,模型知道如何持久化自己的數據,並且可以和其他模型關聯,模型跟ExtJS 3 中的Record類有點像(區別是,Record只是單純的扁平結構,而Model可以nest),通常都用在Store中去展示grid和其他組件的數據。
b.View視圖:視圖是組件的一種,專注於界面展示 – grid, tree, panel 都是view。
c.Controllers控制器:一個安放所有使你的app正確工作的代碼的位置,具體一點應該是所有動作,例如如何渲染view,如何初始化model,和app的其他邏輯。
請注意:MVC是一個框架,不是設計模式,更多的內容請參考: 百度百科
框架與設計模式雖然相似,但卻有着根本的不同。設計模式是對在某種環境中反復出現的問題以及解決該問題的方案的描述,它比框架更抽象;框架可以用代碼表示,也能直接執行或復用,而對模式而言只有實例才能用代碼表示;設計模式是比框架更小的元素,一個框架中往往含有一個或多個設計模式,框架總是針對某一特定應用領域,但同一模式卻可適用於各種應用。可以說,框架是軟件,而設計模式是軟件的知識。
簡而言之:設計模式是大智慧,用來對軟件設計進行分工;框架模式是小技巧,對具體問題提出解決方案,以提高代碼復用率,降低耦合度。
二、ExtJs 4 MVC框架搭建
2.1、文件結構
ExtJS 4 應用都遵循一個統一的目錄結構,每個應有都相同 MVC中,所有類都放在app
目錄里面,這個目錄可以有子目錄,代表的是命名空間(一個子目錄對應一個命名空間),使用不同的目錄存放views
,models
,controllers
,stores
。當我們完成例子的時候,目錄結構應該和下圖一樣:
ExtJS SDK必須的文件在目錄ext4中,因此,index.html
應該引入extjs必須的js和css,以及app.js文件
2.2、在app.js中創建應用
每個ExtJS 4的應用都從一個Application
類的實例開始,這個實例包含應用的全局配置(例如應用的名字),這個實例也負責維護對全部模型、視圖、控制器的引用的維護,還有一個launch
函數,會在所有加載項加載完成之后調用。
首先需要選擇一個全局命名空間,所有ExtJS4應用都需要有一個全局命名空間,以讓所有應用中的類安放到其中:
Ext.application({ requires: ['Ext.container.Viewport'], name: 'FWY',//定義的命名空間 appFolder: 'app',//指明應用的根目錄 launch: function() { Ext.create('Ext.container.Viewport', { layout: 'fit', items: [ { xtype: 'panel', title: '標題', html : '內容' } ] }); } });
2.3、定義一個控制器
控制器是應用的粘合劑,它們所作的事情就是監聽事件並執行動作,繼續我們的應用,創建一個控制器。創建app/controller/Students.js
這個文件,並添加如下代碼:
Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', init: function() { console.debug("trigger controller init event"); } });
接下來在app.js
中添加對Students控制器的引用:
Ext.application({ ... controllers: [ 'Students' //對應於controller文件夾下面的Students.js ], ... });
當我們通過index.html查看應用,Students控制器會被自動加載(因為在app.js的Application中增加了引用),並且Students的init方法會在launch之前調用。
init
方法是個極好的地方,可以用來設置如何和view交互,通常都使用Controller的一個方法control,control方法使得監聽view的事件變的容易,更新一下控制器,讓它告知我們panel何時渲染:
Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', init: function() { this.control({ 'viewport > panel': { render: this.onPanelRendered } }); }, onPanelRendered: function() { console.debug('該panel被渲染了'); } });
我們已經更新了init方法,使用this.controll給視圖設置監聽器。這個controll方法,使用最新的組件查詢引擎(ComponentQuery)可以快速方便的找到頁面上的組件。如果你對ComponentQuery不熟悉,可以查看ComponentQuery文檔進行詳細了解。簡要一點,ComponentQuery可以允許我們使用一個類似css選擇器的方式找到組件。
在例子的init方法中我們應用了'viewport > panel',可以解釋為“查找Viewport直接后代中的所有Panel組件”,然后我們提供了一個對象匹配事件名稱(這個例子中只用了render)來提供響應函數。全部的影響就是無論哪個組件符合我們的選擇器,當它的render事件觸發時,我們的onPanelRendered函數都會被調用。
三、創建ExtJs4 MVC應用
1、定義一個視圖
直到現在,我們的應用只有很少代碼,只有兩個文 件 app.js 和 app/controller/Students.js,現在我們想增加一個grid顯示所有系統中的學生列表,修改3處:
(1)、添加view/List.js視圖
是時候更好的組織一下邏輯並開始使用視圖了。
視圖也是組件,通常都是ExtJS現有組件的子類,現在准備創建學生表,先創建 app/view/student/List.js
,添加代碼:
Ext.define('FWY.view.student.List' ,{ extend: 'Ext.grid.Panel', alias : 'widget.studentlist', title : '學生信息列表', initComponent: function() { this.store = { fields: ['id','name', 'age','sex'], data : [ {id:1,name: 'zhangsan', age: 18,sex:'boy'}, {id:2,name: 'lishi', age: 20,sex:'girl'} ]}; this.columns = [ {header: '編號', dataIndex: 'id', flex: 1}, {header: '姓名', dataIndex: 'name', flex: 1}, {header: '年齡', dataIndex: 'age', flex: 1}, {header: '性別', dataIndex: 'sex', flex: 1} ]; this.callParent(arguments); } });
(2)、修改controller/Students.js
我們的視圖類就是一個普通的類,這個例子中我們擴展了 Grid 組件,並設置了別名,這樣我們可以用 xtype 的方式調用這個組件,另外我們也添加了 store 和 columns 的配置。 接下來我們需要添加這個視圖到 Students控制器。因為我們用 'widget.studentlist' 設置了別名,所以我們可以使用 studentlist 作為xtype,就像我們使用之前使用的 'panel'
Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', views: [ 'student.List'//添加view視圖 ], init: ... onPanelRendered: ... });
(3)、修改app.js,加載視圖
接下來修改 app.js 讓視圖在viewport中渲染,需要修改 launch 方法
Ext.application({ ... launch: function() { Ext.create('Ext.container.Viewport', { layout: 'fit', items: { xtype: 'studentlist' } }); } });
唯一需要注意的是我們在views數組中指定了 'student.List' ,這告訴應用去自動加載對應的文件,ExtJS4 的動態加載系統會根據規則從服務器自動拉取文件,例如student.List就是規則,把.替換成/就是文件存放路徑。刷新一下頁面即可看到效果
2、添加對列表的控制
分三步完成對對編輯窗體的控制
(1)、為grid綁定雙擊事件
注意 onPanelRendered 方法依然被調用,因為我們的grid依然滿足 'viewport > panel' 選擇器,因為我們的視圖繼承自 Grid ,從而繼承自 Panel。現在我們需要收緊一下選擇器,我們使用xtype作為選擇器替換之前的 'viewport > panel' ,監聽雙擊事件,以便繼續做編輯用戶信息:
Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', views: [ 'student.List' ], init: function() { this.control({ 'studentlist': { itemdblclick: this. editStudent//添加行雙擊事件 } }); }, editStudent: function(grid, record) { console.log('Double clicked on ' + record.get('name')); } });
注意我們更換了組件查詢選擇器為 'studentlist' ,監聽的事件更改為 'itemdblclick' ,響應函數設置為 editStudent,現在只是簡單的日志出雙擊行的name屬性。
(2)、創建view/student/Edit.js視圖
可以看到日志是正確的,但我們實際想做的是編輯用戶信息,讓我們現在做,創建一個新的視圖 app/view/student/Edit.js
Ext.define('FWY.view.student.Edit', { extend: 'Ext.window.Window', alias : 'widget.studentedit', title : '修改學生信息', layout: 'fit', autoShow: true, initComponent: function() { this.items = [ { xtype: 'form', items: [ { xtype: 'textfield', name : 'name', fieldLabel: '姓名' }, { xtype: 'textfield', name : 'age', fieldLabel: '年齡' }, { xtype: 'textfield', name : 'sex', fieldLabel: '性別' } ] } ]; this.buttons = [ { text: '保存', action: 'save' }, { text: '取消', scope: this, handler: this.close } ]; this.callParent(arguments); } });
(3)、修改控制器加載視圖
接下來我們要做的就是在控制器加載這個視圖,渲染並且加載用戶信息:
Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', views: [ 'student.List', 'student.Edit'//添加edit視圖 ], init: ... editStudent: function(grid, record) { var view = Ext.widget('studentedit');//注冊組件,顯示窗口 view.down('form').loadRecord(record);//加載數據到表單中 } });
首先我們用 Ext.widget 方法創建了視圖,這個方法等同於 Ext.create('widget.studentedit')
,然后我們又一次借助組件查詢找到了窗口中的表單,每個ExtJS4中的組件都有一個 down
方法,可以借助組件查詢支持的選擇器來迅速找到任意下層的組件,雙擊表格中的一行可以看到彈窗效果。
3、創建Store和Model進行重構
現在我們有了表單,可以開始編輯和保存用戶信息了,但是這之前需要做一點點重構。 FWY.view.student.List 創建了一個內聯的 Store ,這樣可以工作但是我們需要把 Store 分離出來以便我們在應用的其他位置可以引用並更新其中的信息,我們把它放在它應該在的文件中 app/store/Students.js
:
Ext.define('FWY.store.Students', { extend: 'Ext.data.Store', fields: ['id','name', 'age','sex'], data: [ {id:1,name: '張三', age: 30,sex:'男'}, {id:2,name: '李四', age: 20,sex:'女'} ] });
現在我們需要做兩處變更,首先我們需要讓 Students 初始化的時候加載這個 Store :
Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', stores: ['Students'],//加載store ... });
然后我們要把之前直接在視圖中內聯的store更改掉,
Ext.define('FWY.view.student.List' ,{ extend: 'Ext.grid.Panel', alias : 'widget.studentlist', store: 'Students',//引用Store ... });
控制器的代碼中中引入了store,store會被自動加載到頁面並賦予一個storeId,這讓視圖中使用store變的容易(這個例子中,只要配置 store: 'Students' 就可以了) 現在我們只是在store中內聯的定義了四個字段 (id,name,age,sex),這樣可以工作了。
進一步重構:
ExtJS4中有一個強大的 Ext.data.Model類,在編輯用戶的時候我們可以借助它,使用Model重構下Store,在 app/model/Student.js
中創建一個Model:
Ext.define('FWY.model.Student', { extend: 'Ext.data.Model', fields: ['id','name','age','sex'] });
這就是定義我們的Model需要做的,現在需要讓Store引用Model替換掉使用內聯字段的方式,並且讓控制器也引用Model:
//修改控制器,引用Model Ext.define('FWY.controller.Students', { extend: 'Ext.app.Controller', stores: ['Students'], models: ['Student'], ... }); //修改store,引用Model Ext.define('FWY.store.Students', { extend: 'Ext.data.Store', model: 'FWY.model.Student', data: [ {id:1,name: '張三1', age: 30,sex:'男'}, {id:2,name: '李四1', age: 21,sex:'女'} ] });
4、利用模型保存數據
現在我們有了一個用戶數據表,雙擊每⼀一行都能打開一個編輯窗口,現在要做的是保存編輯變更,編輯窗口有一個編輯表單,還有保存按鈕,現在我們更新一下控制器讓保存按鈕有響應:
Ext.define('FWY.controller.Students', { init: function() { this.control({ 'viewport > studentlist': { itemdblclick: this.editStudent }, 'studentedit button[action=save]': {//獲取studentedit視圖中的button配置action=‘save’的按鈕事件 click: this.updateStudent } }); }, updateStudent: function(button) { console.log('clicked the Save button'); } });
接下來填充 updateStudent 真正的邏輯。我們需要把數據從表單中取出,再 設置回store中:
updateStudent: function(button) { var win = button.up('window'), form = win.down('form'), record = form.getRecord(), values = form.getValues(); record.set(values); win.close(); }
5、保存到服務器
讓我們增加和服務器端的交互完成這個例子。現在我們還是應編碼了兩行表格的數 據,現在讓我們通過ajax加載:
Ext.define('FWY.store.Students', { extend: 'Ext.data.Store', model: 'FWY.model.Student', autoLoad: true, proxy: { type: 'ajax', url: 'data/students.json', reader: { type: 'json', root: 'students', successProperty: 'success' } } });
這里我們去除了 'data' 屬性,替換成 proxy ,代理是讓Store或者Model加載和保存數據的一個方式,有AJAX,JSONP,HTML5的localStorage本地存儲等。這里我們使用了一個簡單的AJAX代理,讓它通過URL 'data/students.json' 加載數據。
我們同時給代理附加了一個reader,reader是用來把服務器返回的數據解碼成Store能理解的格式,這次我們使用了JSON reader,並且指定了root和 successProperty 配置(JSON reader的詳細配置看文檔),最后我們創建一下數據文件 data/students.json
,輸入內容:
{ success: true, users: [ {id: 1, name: 'zhang', email: 'zhang@126.com'}, {id: 2, name: 'lishi', email: 'lishi@126.com'} ] }
其他的變更就是我們給Store設置了 autoLoad 屬性並設置為 true ,這意味着Store生成之后會自動讓Proxy加載數據,刷新⼀一下頁面應該是看到和之前同樣的結果,不同的是現在不是在程序中存在硬編碼數據了,最后的事情是將變更傳回服務器端,這個例子中我們使用靜態的JSON文件,沒有使用數據庫,但足夠說明我們例子的了,首先做一點點變化告知proxy用於更新的url:
proxy: { type: 'ajax', api: { read: 'data/students.json', update: 'data/updateStudents.json', }, reader: { type: 'json', root: 'students', successProperty: 'success' } }
依然從 students.json 讀取數據,但是變更會發送到 updateStudents.json ,這里我們做⼀個模擬的應答回包以讓我們知道程序可以正確工作, updateStudents.json 只需要包含{"success":true}
,其他要做的就是讓Store在編輯之后進行同步,需要在 updateStudent 函數中增加一行代碼:
updateStudent: function(button) { var win = button.up('window'), form = win.down('form'), record = form.getRecord(), values = form.getValues(); record.set(values); win.close(); this.getStudentsStore().sync();//將數據同步到store中 }
最后附上本篇的代碼:點擊下載
--本篇完--