ExtJs4学习(十一)MVC应用模式

   对于Extjs来说,大客户程序一直很难写,当你为大客户端程序添加更多的功能和顷目的时候,项目的体积往往迅速增长。这样的大客户端程序很难组织和维持 ,所以,Extjs4配备了一个新的应用程序体系结构,它能结构化你的代码,那就是Extjs4 MVC。

   Extjs4 MVC有别于其他MVC架构,Extjs有他自己定义:

  1. Model是一个Field以及他的Data的集合,Modes知道如何通过Stores来表示数据,以能用于网格和其他组件。模型的工作很像Extjs3的记录集(Record class),通常通过数据加载器(Stores)渲染至网格(grid)和其他组件上边。
  2. View:用以装载任何类型的组件—grid、tree和panel等等。
  3. Controller—用来放使得app工作的代码,例如 render views , instantiating Models 或者其他应用逻辑。

   本篇文章,我们将创建一个非常简单的应用程序,即用户数据管理,最后,你就会知道如何利用Extjs4 MVC去创建简单应用程序。Extjs4 MVC应用程序架构提供应用程序的结构性和一致性。这样的模式带来了一些重要的好处:

  1. 每个应用程序的工作方式相同,我们叧需要学习一次。
  2. 应用程序乀间的代码共享很容易,应为他们所有的工作方式都相同
  3. 你可以使用EXTJS提供的构建工具创建你应用程序的优化版本。
既然是介绍Extjs4 MVC,那举,我们开始创建这个应用。

文件结构(File Structure):

Extjs4 MVC的应用程序和别的应用程序一样都遵循一个统一的目录结构。在MVC布局中,所有的类放在应用程序文件夹,它的子文件夹中包括您的命名空间,模型,视图,控制器和存储器。下面来通过简单的例子来看下怎样应用。

技术分享

   在这个例子中,我们将整个应用程序放到一个名为“account_manager”的文件夹下,“account_manager”文件夹中的结构如上图。现在编辑index.html,内容如下:
<span style="font-family:Courier New;font-size:14px;"><html>
<head>
<title>Account Manager</title>
<link rel="stylesheet" type="text/css" href="ext-4.0/resources/css/ext-all.css">
<script type="text/javascript" src="ext-4.0/ext-debug.js"></script>
<script type="text/javascript" src="app.js"></script>
</head>
<body></body>
</html></span>

创建app.js文件(Creating the application)

     所有Extjs4 MVC应用仍Ext.application的一个实例开始,应用程序中应包括全局设置、以及应用程序所使用的模型(model),视图(view)和控刢器(controllers),一个应用程序还应该包括一个发射功能(launch function)。
现在来创建一个简单的账户管理应用。首先,需要选择一个命名空间(所有extjs4应用应该使用一个单一的全局变来来作为命名空间)。暂时,使用“AM”来作为命名空间。
<span style="font-family:Courier New;font-size:14px;">Ext.application({
	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'
			} ]
		});
	}
});</span>

   以上代码,做了如下几件事。首先,调用Ext.application创建一个应用程序类的实例,设置了一个“AM”的命名空间,他将作为整个应用的全局变量,也将作为Ext.Loader的命名空间,然后通过appFolder来指定配置选顷设置相应的路径。最后,创建了一个简单的发射功能,这里仅仅创建了一个Viewport,其中包吨一个panel,使其充满整个窗口。

技术分享


定义一个控制器(Defining a Controller)

   控制器是整个应用程序的关键,他负责监听事件,幵对某些时间做出相应的动作。现在我们创建一个控刢器,将其命名为Users.js,其路径是app/controller/Users.js。然后,我们为Users.js添加如下代码:
<span style="font-family:Courier New;font-size:14px;">Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	init: function() {
		console.log('Initialized Users! This happens before the Application launch function is called');
	}
});</span>

   完成之后,我们将创建好的控制器添加到程序配置文件:app.js中:
<span style="font-family:Courier New;font-size:14px;">Ext.application({
	...
	controllers: [
	'Users'
	],
	...
});</span>

   当我们访问index.html时,用户控制器(Users.js)自动加载,因为我们在上面的app.js中的定丿中指定了。Init凼数一个最棒的地方是控制器与视图的交互,这里的交互是指控制功能,因为他很容易就可以监听到视图类的事件处理凼数,并采取相应的措施,并及时渲染相关信息到面板上来。

   编写Users.js:
<span style="font-family:Courier New;font-size:14px;">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');
	}
});</span>
   在Users.js中,init凼数使用this.control来负责监听,由于使用了新的ComponentQuery引擎,所以可以快速方便的找到页面上组件的引用(viewport > panel),这有些类似CSS选择器,通过匹配,快速的找到相匹配的组件。

   在上面的init凼数中,我们使用viewport > panel,来找到app.js中Viewport下的panel组件,然后,我们提供了一个对象的处理函数(this. onPanelRendered,注意,这里的对象是this,他的处理函数是onPanelRendered)。整个效果是,叧要符合触发render事件的任何组件,那举onPanelRendered函数将被调用。当运行我们的应用程序,我们将看到以下内容。

技术分享

定义一个视图(Defining a View)

   到目前为止,应用程序叧有几行,也叧有两个文件,app.js和app/controller/Users.js。现在我们来增加一个grid,来显示整个系统中的所有用户。作为视图组件的一个子类,我们创建一个新的文件,幵把他放到app/view/user目录下。命名为List.js。整个路径是这样的。app/view/user/List.js,下面,我们写List.js的代码:

<span style="font-family:Courier New;font-size:14px;">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);
	}
});</span>

    我们创建好的这个类,叧是一个非常普通的类,并没有任何意义,为了能让我们更好的使用这个定义好的类,所以我们使用alias来定义一个别名,这个时候,我们的类可以使用Ext.create()和Ext.widget()创建,在其他组件的子组件中,也可以使用xtype来创建。

<span style="font-family:Courier New;font-size:14px;">Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	views: [
	'user.List'
	],
	init: ...
	onPanelRendered: ...
});</span>

   修改Users.js,增加views属性,修改app.js中的launch方法,将List渲染到Viewport。

<span style="font-family:Courier New;font-size:14px;">Ext.application({
	...
	launch: function() {
		Ext.create('Ext.container.Viewport', {
			layout: 'fit',
			items: {
				xtype: 'userlist'
			}
		});
	}
});</span>


    看到这里,也许会有人开始抓狂了,这个user.List到底是怎举来的,为什么要这样写?如果你开始感到疑惑,那么不妨看看Ext.Loader是如何工作的(参见文档其他部分),在看过Ext.Loader之后,你就会明白了,User.List就是app/view/user下的List.js文件。为什么Ext要从view下找呢?因为我们在控制器中定了views: [‘user.List‘]。这就是Extjs动态加载的强大之处,具体Ext.Loader,请看本站的其他文章,你就会明白了。当我们刷新页面。

技术分享

控制网格(Controlling the grid)

   要注意的是,onPanelRendered功能仍然是被调用的。这是因为gird匹配‘viewport > panel‘。 然后我们添加一个监听,当我们双击grid中的行,就可以编辑用户。

<span style="font-family:Courier New;font-size:14px;">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'));
	}
});</span>

    这里,我们修改了ComponentQuery的选择(‘userlist‘)和事件的名称(‘itemdblclick‘)和处理函数(‘editUser‘)。

技术分享

    如果要实现真正编辑用户,那么我们需要一个真正用于用户编辑window,接着,创建一个JS文件。其路径是:app/view/user/Edit.js,代码是这样的:

<span style="font-family:Courier New;font-size:14px;">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);
	}
});</span>

   我们定义了一个子类,继承Ext.window.Window,然后使用initComponent创建了一个表单和两个按钮,表单中,两个字段分别装载用户名和电子邮件。接下来,我们修改视图控制器,使其可以载入用户数据。

<span style="font-family:Courier New;font-size:14px;">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);
	}
});</span>

首先,我们创建的视图,使用便捷的方法Ext.widget,这是相当于Ext.create(‘widget.useredit―)。然后我们利用ComponentQuery快速获取编辑用户的形式引用。在Ext JS4的每个组件有一个down函数,可以使用它快速的找到任何他的子组件。当双击Grid中的行,我们可以看到如下图所示:

技术分享

创建Model 和 Store

<span style="font-family:Courier New;font-size:14px;">Ext.define('AM.store.Users', {
	extend : 'Ext.data.Store',
	fields : [ 'name', 'email' ],
	data : [ {
		name : 'Ed',
		email : '[email protected]'
	}, {
		name : 'Tommy',
		email : '[email protected]'
	} ]
});</span>

   接下来修改两个文件:

<span style="font-family:Courier New;font-size:14px;">Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	stores: [
	'Users'
	],
	...
});</span>

   修改app/view/user/List.js,使其引用Users
<span style="font-family:Courier New;font-size:14px;">Ext.define('AM.view.user.List' ,{
	extend: 'Ext.grid.Panel',
	alias : 'widget.userlist',
	//we no longer define the Users store in the `initComponent` method
	store: 'Users',
	...
});</span>

   现在,我们定义的用户控制器已经能顺利的加载数据了,到目前,我们所定义的Store已经足够使用了,但是Extjs4提供了一个强大的Ext.data.Model类,不如,我们利用它来重构下我们Store,创建app/model/User.js

<span style="font-family:Courier New;font-size:14px;">Ext.define('AM.model.User', {
	extend : 'Ext.data.Model',
	fields : [ 'name', 'email' ]
});</span>

   创建好模型之后,我们将他引入用户控制:

<span style="font-family:Courier New;font-size:14px;">//the Users controller will make sure that the User model is included on the page and available to our app
Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	stores: ['Users'],
	models: ['User'],
	...
});
// we now reference the Model instead of defining fields inline
Ext.define('AM.store.Users', {
	extend: 'Ext.data.Store',
	model: 'AM.model.User',
	data: [
	       {name: 'Ed', email: '[email protected]'},
	       {name: 'Tommy', email: '[email protected]'}
	]
});</span>

   完成上面的代码后,刷新页面,看到的结果和以前的一样。
技术分享

通过model保存数据

    现在双击Grid中的行,会弹出编辑用户的window,实现Save来保存用户数据,我们需要修改init函数。
<span style="font-family:Courier New;font-size:14px;">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');
	}
});</span>

   在this.control中,我们增加了一个选择项,‘useredit button[action=save]‘,当ComponentQuery找到符合的组件(button按钮,并且action动作为save),给他增加一个方法click,事件为updateUser。如图:
技术分享
上图中,可以看到正确的Click事件,在updateUser函数中,需要一个正式的逻辑,来完成用户数据的更新。
<span style="font-family:Courier New;font-size:14px;">updateUser:function(button) {
	var win = button.up('window'),
	form = win.down('form'),
	record = form.getRecord(),
	values = form.getValues();
	record.set(values);
	win.close();
}</span>

   这里,当我们点击Save按钮,我们将按钮本身传入函数updateUser,这时,我们使用button.up(‘window‘)来获取用户window的引用,然后使用win.down(‘form‘)来获取表单的引用,然后获取表单的记录,获取表单中的值,再设置记录为新的值,最后关闭window。
 
   修改数据后点击Save,更新完成。

技术分享
    实际应用过程中,我们需要将数据保存到服务器,所以,需要修改文件,达到想要的目的。

将数据保存到服务器

   这很容易做到。使用AJAX来实现即可。
<span style="font-family:Courier New;font-size:14px;">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'
		}
	}
});</span>

   在AM.store.Users,移除data,用一个代理(proxy)取代它,用代理的方式来加载和保存数据。在Extjs4中,代理的方式有AJAX, JSON-P 和 HTML5 localStorage,这里使用AJAX代理。数据从data/users.json中得到。

   我们还用reader来解析数据,还指定了successProperty配置。具体请查看Json Reader,把以前的数据复制到users.json中。得到如下形式:

<span style="font-family:Courier New;font-size:14px;">{
	success:true,
	users:[
	        {id: 1, name: 'Ed', email: '[email protected]'},
	        {id: 2, name: 'Tommy', email: '[email protected]'}
	]
}</span>

   到这里,唯一的变化是将Stroe的autoLoad设置为了true,这要求Stroe的代理要自动加载数据,当刷新页面,将得到和之前一样的效果。

   最后一件事情,是将修改的数据发送到服务器,对于本例,服务端只用了一个静态的JSON,所以我们不会看到有任何变化,但至少可以确定这样做是可行的,相信服务端处理数据能力,大家都应该有。本例用,做个小小的变化,即新的代理中,使用api来更新一个新的URL。

<span style="font-family:Courier New;font-size:14px;">proxy: {
	type: 'ajax',
	api: {
		read: 'data/users.json',
		update: 'data/updateUsers.json'
	},
	reader: {
		type: 'json',
		root: 'users',
		successProperty: 'success'
	}
}</span>


   再来看看是如何运行的,我们还在读取users.json中的数据,而任何更新都将发送到updateUsers.json,在updateUsers.json中,创建一个虚拟的回应,从而让我们知道事件确实已经发生。updateUsers.json只包含了{"success": true}。而我们唯一要做的事情是服务的同步编辑。在updateUser函数增加这样一个功能。

<span style="font-family:Courier New;font-size:14px;">updateUser: function(button) {
	var win = button.up('window'),
	form = win.down('form'),
	record = form.getRecord(),
	values = form.getValues();
	record.set(values);
	win.close();
	this.getUsersStore().sync();
}</span>

   现在,运行这个完整的例子,当双击grid中的某一样并进行编辑,点击Save按钮后,得到正确的请求和回应。

技术分享
   至此,整个MVC模式全部完成,下一节,将讨论控制器(Controller)和模式(patterns),而这些,可以使应用代码更少,更容易维护

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。