本文以项目实例来讲解使用ExtJS的MVVM架构开发的过程。本项目实例的开发环境是Tomcat+SpringMVC+Ibatis+ExtJS,其中Tomcat、SpringMVC与Ibatis的相关配置不在本文范围。在ExtJS中与后台接口交互的地方会简单阐述并给出示例代码。
先上目录结构:
css、imags和js等静态资源文件都要放在resources目录下面。
resources/js/extjs目录:
ext-all.js是ExtJS的库文件,ext-all-debug.js是调试时使用的库文件,theme-neptune-all.css是海王星样式文件,theme-neptune目录中包含样式文件需要的资源。
resources/css目录:
css样式目录中,自定义的css样式文件放在customer目录中,本项目使用font-awesome-4.4的版本,用于统一项目中的图文符号,使用方法见后面代码示例。
home.jsp入口页面的配置:
<!DOCTYPE HTML>
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page contentType="text/html;charset=UTF-8"%>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>XXX XXX</title>
<!-- 引入ext-all.js -->
<script type="text/javascript" src="resources/js/extjs/ext-all.js"></script>
<!-- 引入theme-neptune-all.css样式文件 -->
<link rel="stylesheet" type="text/css"
href="resources/js/extjs/classic/theme-neptune/resources/theme-neptune-all.css" />
<!-- 引入font-awesome.min.css样式文件 -->
<link rel="stylesheet" href="resources/css/font-awesome-4.4.0/css/font-awesome.min.css">
<!-- 引入JS程序入口文件app.js -->
<script type="text/javascript" src="resources/js/app.js"></script>
</head>
<body>
</body>
</html>
resources/js/app.js ExtJS应用程序入口文件
Ext.Loader.setConfig({
enabled : true //Ext.Loader设置允许动态加载,默认就是true
});
Ext.application({
name : 'MyApp', //设置应用程序的命名空间
appFolder : 'resources/js/app', //设置应用程序的程序目录(程序根文件目录)
extend: 'myApp.Application' //继承自myApp.Application
});
resources/js/app/Application.js 应用程序类文件
Ext.define('MyApp.Application', {
extend: 'Ext.app.Application',
requires: ['MyApp.view.main.Main'],
launch: function () { // 启动应用程序
Ext.create('MyApp.view.main.Main'); //创建Viewport实例
}
});
resources/js/app/view目录:
resources/js/app/view/main/Main.js 主视图Viewport子类,是应用程序可视化区域,一个应用程序只能有一个Viewport生效,渲染到document.body,它是一个特殊的Container子类,用来配置它的子组件的布局,一般采用border布局来设置子组件大小和位置。
Ext.define('MyApp.view.main.Main', {
extend : 'Ext.container.Viewport', //继承自Ext.container.Viewport
requires: ['MyApp.view.main.MainController',
'MyApp.view.main.MainModel',
'MyApp.view.dashboard.Dashboard'
], //动态加载所需类
controller: 'main', //设置该视图的Controller
viewModel: 'main', //设置该视图的ViewModel
layout: 'border', //设置视图的布局方式为border
items: [{ //配置该视图的子组件
xtype: 'container', //该子组件类型为Ext.container.Container
id : 'app-header',
region : 'north', //border布局中的north region
height : 52,
layout : {
type : 'hbox', //该组件的布局方式为hbox(水平布局)
align : 'middle' //对其方式为居中
},
items : [ {
xtype : 'component', //该组件类型为Ext.Component
id : 'app-header-logo' //在已经加载的组件中(内存中)寻找id为app-header-logo的组件
}, {
xtype : 'component',
cls : 'app-header-text', //设置该组件样式,在已加载的css文件中寻找该样式
bind : 'XXX管理系统', //绑定的字符串
flex : 1 //设置大小的权重
}, {
xtype : 'component',
id : 'app-header-username',
cls : 'app-header-text',
bind : '欢迎' + current_user_name,
margin : '0 10 0 0' //组件边缘空白设置
}, {
xtype : 'button',
id : 'app-header-logout',
text : '退出',
margin : '0 10 0 0',
href : 'logout', //设置超链接的URL地址
hrefTarget : '_self' //设置超链接属性
} ]
},{
xtype: 'panel',
region : 'west',
id: 'navigator',
title : '导航栏',
width : 250,
layout : 'accordion',
split : true, //是子组件可以重新设置布局大小,该属性不能在center region中使用
collapsible : true, //该区域是可以收缩的
listeners : { //设置事件的监听
afterrender : 'requestTreeData' //添加afterrender事件的监听处理函数'requestTreeData',该函数在Controller中定义
}
},{
region : 'center', //border布局中的center region,不可缺失
xtype: 'container',
id: 'app-content',
layout: 'fit', //fit布局只会以合适的大小显示第一个子组件
items : [{
xtype: 'tabpanel', //Ext.tab.Panel组件
flex : 1,
reference : 'main', //设置该组件的引用名称,该名称'main'必须在该组件所在的视图View和该组件所配置的Controller中命名唯一
items: [{
xtype: 'app-dashboard', //该xtype是自定义的,在MyApp.view.dashboard.Dashboard类中定义
title: '首页'
}]
}]
}]
});
resources/js/app/view/mian/MainController.js Main视图的视图处理器ViewController的子类文件,下面贴出主要的函数代码:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
alias : 'controller.main',
createTab : function(rec) {
var tabs = this.lookupReference('main'), id = 'tab_'
+ rec.getId(), tab = tabs.items.getByKey(id),
viewtab, viewpath,pathArray,viewname;
if (!tab) {
viewpath = rec.get('view'); //获取record记录中的view字段内容,该字段存储视图相对路径名称(相对于view目录的)
Ext.require('MyApp.view.'+viewpath); //动态加载请求该视图的类
Ext.onReady(function(){ //增加一个监听器,当document都加载就绪(在onload之前,以及images加载之前)时执行函数体中的内容
pathArray = viewpath.split('.');
viewname = pathArray[pathArray.length-1]; //拆分视图相对路径,得到视图别名(约定别名与类名相同)
tab = tabs.add({
id: id,
title: rec.get('text'),
closable: true,
xtype: viewname //该tab的类型为该视图(viewname包含的别名所代表的视图)
});
tabs.setActiveTab(tab); //设置该tab为当前活动的tab
});
}
},
onClickLeaf : function(view, record, item, index, e) {
var leaf = record.get('leaf');
if (leaf) {
this.createTab(record); //调用当前对象的createTab函数
//...
}
},
buildTree : function(json) {
return Ext.create('Ext.tree.Panel', {
//...
listeners : {
'itemclick' : 'onClickLeaf', //设置该组件的itemclick事件处理函数为onClickLeaf函数
scope : this
}
});
},
requestTreeData : function(navigatePanel) { //加载Main视图时配置的监听处理函数,此函数逻辑是在左侧创建导航栏的内容
var me = this;
Ext.Ajax.request({
url : 'getMenuTree', //Ajax请求的URL地址
success : function(response) { //Ajax请求成功调用的函数
var json = Ext.JSON.decode(response.responseText)
Ext.each(json.data, function(el) { //对返回的jason的每一条数据遍历处理
var panel = Ext.create('Ext.panel.Panel', {
id : el.id,
title : el.text,
iconCls: el.iconCls,
layout : 'fit'
});
panel.add(me.buildTree(el)); //调用当前对象buildTree方法
navigatePanel.add(panel);
});
},
failure : function(request) { //Ajax请求失败调用的函数
Ext.MessageBox.show({
title : '操作提示',
msg : "获取菜单数据失败",
buttons : Ext.MessageBox.OK,
icon : Ext.MessageBox.ERROR
});
},
method : 'post'
});
},
//...
});
现在主视图Main的布局以及视图控制器MainController的逻辑都以及完成,那么下面就以一个功能模块的例子来看如何在左侧菜单栏中添加功能菜单,点击该菜单会在中心区域显示该功能视图。
首先,由于菜单栏的内容被设计成从后台读取,由后台控制菜单栏的内容,所以需要在后台返回的json中加入需要添加的功能菜单信息。处理Ajax请求的后台方法在HomeController.java中,路径这里就不给出了,下面给出该方法的代码:
@RequestMapping(value = "/getMenuTree", method = RequestMethod.POST)
public @ResponseBody String getMenuTree(HttpServletRequest request) {
//...
//此处为了测试,将json的内容直接拼出,按照树形结构拼出菜单栏的分层结构内容,要注意的是约定view字段的内容是视图的相对路径(相对于view目录的)
String systemconfig = "{id:'sys',text:'系统设置',leaf:false,children:["
+ "{id:'sys1',text:'系统菜单管理',view:'systemconfig.SysMenuMgmView',leaf:true}]}";
//...
String menuTree = "{success:true,data:[" + systemconfig + "]}";
return menuTree;
}
Ajax的getMenuTree请求在上面的后台方法处理后,会返回带有菜单栏数据的json格式字符串,在MainController中解析json后,会在菜单栏中创建新增功能的菜单选项。
点击该菜单栏中的“系统菜单管理”子节点,会触发itemclick事件,该事件的处理函数onClickLeaf函数会在引用名为’main’的tabpanel中显示’SysMenuMgmView’别名定义的视图(约定别名与类名相同)。接下来,我们要做的就是创建该功能视图的类。先看目录结构:
JS文件命名约定:XxxView为视图类,XxxModel为该视图的视图模型类,XxxController为该视图的视图控制器类,例如SysMenuMgmView.js是视图类,SysMenuMgmModel是该视图的视图模型类,SysMenuMgmController是该视图的视图控制器类。菜单栏的一个菜单选项节点对应一个视图类,该视图类可以用requires属性动态加载其他组件文件,如SysMenuTreeGrid这个组件。
resources/js/app/view/systemconfig/SysMenuMgmView.js 系统菜单管理视图:
Ext.define('MyApp.view.systemconfig.SysMenuMgmView',{
extend: 'Ext.Container',
alias: 'widget.SysMenuMgmView',
requires: ['MyApp.view.systemconfig.SysMenuMgmController',
'MyApp.view.systemconfig.SysMenuMgmModel',
'MyApp.view.systemconfig.SysMenuTreeGrid'], //动态加载的类
controller: 'SysMenuMgmController', //设置视图控制器
viewModel: 'SysMenuMgmModel', //设置视图模型
bodyStyle: 'background-color:#fff', //设置该视图背景颜色(白色)
layout: 'border', //设置布局
items:[{ //设置该视图子组件
region: 'center',
xtype: 'container',
layout: 'vbox',
items: [{
xtype: 'panel',
flex: 6, //设置布局大小权重
width: '100%',
heigth: 450,
padding: '0,0,5,0',
layout: 'fit',
items: [{
xtype: 'SysMenuTreeGrid', //别名为SysMenuTreeGrid的组件
id: 'SysMenuTreeGrid' //设置id属性,可以用id检索,但是注意不能存在重名id
}]
},{
xtype: 'panel',
border: false, //取消边框
flex: 3,
width: '100%',
padding: '0,10,0,0',
layout: 'fit',
items: [{
xtype: 'fieldset', //Ext.form.FieldSet
title: '修改记录',
layout: 'fit',
items:[{
xtype:'form', //Ext.form.Panel
id: 'MenuModify',
border: false,
buttonAlign: 'center',
layout: 'vbox', //vbox垂直布局
items: [{
xtype: 'panel',
border: false,
width: '100%',
layout: 'column',
columns: 3,
items: [{
xtype: 'textfield', //Ext.form.field.TextField
fieldLabel: '功能编号',
labelWidth: 65,
columnWidth: .33,
margin: '5,5,5,5',
name: 'funcNum' //设置组件name属性
},{
xtype: 'textfield',
fieldLabel: '排序号',
labelWidth: 65,
columnWidth: .33,
margin: '5,5,5,5',
name: 'sortNum'
},{
xtype: 'textfield',
fieldLabel: '功能名称',
labelWidth: 65,
columnWidth: .33,
margin: '5,5,5,5',
name: 'funcName'
}]
},{
xtype: 'panel',
border: false,
width: '100%',
layout: 'column', //column列布局
columns: 3,
items: [{
xtype: 'textfield',
fieldLabel: '功能描述',
labelWidth: 65,
columnWidth: .33,
margin: '5,5,5,5',
name: 'description'
},{
xtype: 'textfield',
fieldLabel: '模块文件名',
labelWidth: 75,
columnWidth: .33,
margin: '5,5,5,5',
name: 'linkLib'
},{
xtype: 'textfield',
fieldLabel: '类型名',
labelWidth: 65,
columnWidth: .33,
margin: '5,5,5,5',
name: 'funcType'
}]
},{
xtype: 'panel',
border: false,
width: '100%',
layout: 'column',
columns: 3,
items: [{
xtype: 'textfield',
fieldLabel: '视图名称',
labelWidth: 65,
columnWidth: .33,
margin: '5,5,5,5',
name: 'viewPath'
}]
},{
xtype: 'panel',
border: false,
width: '100%',
layout: 'hbox',
items: [{
xtype: 'label',
text: '访问快捷键:',
width: 90,
margin: '5,5,5,5'
},{
xtype: 'checkboxgroup', //Ext.form.CheckboxGroup
labelWidth: 60,
width: 200,
margin: '5,5,5,5',
fieldLabel: '修饰符:',
labelSeparator: '',
items: [
{ boxLabel: 'Ctrl', name: 'modifier', inputValue: '1' },
{ boxLabel: 'Shift', name: 'modifier', inputValue: '2', checked: true },
{ boxLabel: 'Alt', name: 'modifier', inputValue: '3' },
]
},{
xtype: 'combobox', //Ext.form.field.ComboBox
labelWidth: 30,
margin: '5,5,5,5',
fieldLabel: '键:',
width: 150
}]
}]
}]
}]
},{
xtype: 'panel',
border: false,
flex: 1,
width: '100%',
padding: '0,0,5,0',
layout: {
align: 'middle',
pack: 'center',
type: 'hbox'
},
items: [{
xtype: 'button', //Ext.button.Button
text: '新增',
scale: 'small',
margin : '5 0 5 5',
iconCls: 'fa fa-plus-circle', //设置样式
iconAlign: 'left',
handler: 'onAddClick' //设置button的handler处理函数
},{
xtype: 'button',
text: '保存',
scale: 'small',
margin : '5 0 5 5',
iconCls: 'fa fa-save',
iconAlign: 'left',
handler: 'onSaveClick'
},{
xtype: 'button',
text: '删除',
scale: 'small',
margin : '5 0 5 5',
iconCls: 'fa fa-remove',
iconAlign: 'left',
handler: 'onDeleteClick'
},{
xtype: 'button',
text: '复位',
scale: 'small',
margin : '5 0 5 5',
iconCls: 'fa fa-recycle',
iconAlign: 'left',
handler: 'onRefreshClick'
}]
}]
}]
});
iconCls的样式设置用到了font-awesome的样式定义,参见Font-Awesome官网
resource/js/app/view/systemconfig/SysMenuTreeGrid.js 系统菜单树结构表格的子组件,在上面的视图中动态加载的,代码如下:
Ext.define('MyApp.view.systemconfig.SysMenuTreeGrid',{
extend: 'Ext.tree.Panel', //集成自Ext.tree.Panel,是树结构表格的控件
alias: 'widget.SysMenuTreeGrid', //设置别名
controller: 'SysMenuMgmController', //设置该组件控制器
viewModel: 'SysMenuMgmModel', //设置该组件视图模型
listeners: { //注册事件响应函数
itemclick: 'onItemClick', //单击事件
itemdblclick: 'onItemDblClick' //双击事件
},
reserveScrollbar: true, //预留滚动条位置大小
border: false,
useArrows: true, //使用箭头符号
rootVisible: false, //设置树的根节点不可见
multiSelect: true, //设置可以多选行记录
initComponent: function() { //初始化函数
Ext.apply(this, { //Ext.apply函数
store: new Ext.data.TreeStore({ //设置当前组件(树结构表格)的store属性,新建了一个Ext.data.TreeStore实例
model: 'MyApp.model.systemconfig.MenuTreeModel', //该store的model属性为MyApp.model.systemconfig.MenuTreeModel类的实例
autoLoad:true, //自动加载
proxy: { //store的代理
type: 'ajax', //类型为ajax的代理
url: './resources/js/app/data/systemconfig/sysmenu.json' //数据来源的URL地址;此处为了测试方便读取的本地json文件,实际项目中一般是从后台的URL映射方法返回json格式的内容
},
reader: { //设置store的阅读器reader属性
type: 'json', //json格式的阅读器,将json格式数据转换成store可以识别的record对象
rootProperty: 'text' //设置读取的根节点
},
folderSort: true //自动预留的对叶节点的sorter
}),
viewConfig: {
plugins: { //设置视图应用的插件
ptype: 'treeviewdragdrop', //树结构视图拖放插件
containerScroll: true
}
},
tbar: [{ //顶部工具条
labelWidth: 40,
xtype: 'textfield',
fieldLabel: '搜索',
listeners: { //配置事件监听函数
change: function() { //change事件的处理函数
var tree = this.up('treepanel'),
v,
matches = 0;
try {
v = new RegExp(this.getValue().trim(), 'i');
Ext.suspendLayouts();
tree.store.filter({
filterFn: function(node) {
var children = node.childNodes,
len = children && children.length,
visible = node.isLeaf() ? v.test(node.get('funcName')) : false,
i;
for (i = 0; i < len && !(visible = children[i].get('visible')); i++);
if (visible && node.isLeaf()) {
matches++;
}
return visible;
},
id: 'titleFilter'
});
tree.down('#matches').setValue(matches);
Ext.resumeLayouts(true);
} catch (e) {
this.markInvalid('Invalid regular expression');
}
},
buffer: 250
}
}, {
xtype: 'displayfield',
itemId: 'matches',
labelWidth: 40,
fieldLabel: '匹配',
// Use shrinkwrap width for the label
listeners: {
beforerender: function() {
var me = this,
tree = me.up('treepanel'),
root = tree.getRootNode(),
leafCount = 0;
tree.store.on('fillcomplete', function(store, node) {
if (node === root) {
root.visitPostOrder('', function(node) {
if (node.isLeaf()) {
leafCount++;
}
});
me.setValue(leafCount);
}
});
},
single: true
}
}],
columns: [{
xtype: 'treecolumn', //treecolumn列会以树状结构显示
text: '功能名称',
flex: 2,
sortable: true, //该字段可排序
dataIndex: 'funcName' //设置该列绑定的字段名
},{
text: '功能编号',
flex: 1,
sortable: true,
dataIndex: 'funcNum'
},{
text: '排序号',
flex: 1,
dataIndex: 'sortNum',
sortable: true
}, {
text: '所属类别',
flex: 1,
sortable: true,
dataIndex: 'funcType'
}, {
text: '视图名称',
flex: 1,
sortable: true,
dataIndex: 'viewPath'
}, {
text: '功能描述',
dataIndex: 'description',
width: 120
}, {
text: '模块链接库',
flex: 1,
sortable: true,
dataIndex: 'linkLib'
}]
});
this.callParent(); //initComponent函数必须有的
}
});
SysMenuTreeGrid的store配置中用到的Model为MyApp.model.systemconfig.MenuTreeModel,该Model类的代码:
Ext.define('MyApp.model.systemconfig.MenuTreeModel', {
extend: 'MyApp.model.BaseModel',
fields: [{
//功能名称
name: 'funcName',
type: 'string'
}, {
//功能编号
name: 'funcNum',
type: 'int'
}, {
//排序号
name: 'sortNum',
type: 'int'
}, {
//所属类别
name: 'funcType',
type: 'string'
}, {
//视图名称
name: 'viewPath',
type: 'string'
}, {
//功能描述
name: 'description',
type: 'string'
}, {
//模块链接库
name: 'linkLib',
type: 'string'
}]
});
SysMenuMgmView与SysMenuTreeGrid的视图模型都是SysMenuMgmModel,视图控制器都是SysMenuMgmController,下面分别来看视图模型和视图控制器的代码:
resources/js/app/view/systemconfig/SysMenuMgmModel.js 视图模型代码:
Ext.define('MyApp.view.systemconfig.SysMenuMgmModel',{
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.SysMenuMgmModel',
requires: ['MyApp.model.systemconfig.MenuTreeModel'], //动态加载MenuTreeModel类
stores: { //设置该视图模型的stores集合,可用于在视图组件中作为绑定数据源
//mystore:{
// model: 'Xxx.Model',
// autoLoad: true,
// proxy: {},
// reader: {}
//}
}
});
SysMenuTreeGrid中in-line方式创建了一个Ext.data.TreeStore的实例作为它的数据源,还可以用绑定的方式使用它的ViewModel中定义的数据源,例如用 bind:’{mystore}’ 这样的方式绑定ViewModel中定义的mystore数据源。绑定方式的好处在于,store数据源中的数据更新会自动在视图中呈现。
resources/js/app/view/systemconfig/SysMenuMgmController.js 系统菜单管理视图控制器类:
Ext.define('MyApp.view.systemconfig.SysMenuMgmController',{
extend: 'Ext.app.ViewController',
alias: 'controller.SysMenuMgmController',
onItemClick: function(grid, rowIndex){
var store = Ext.getCmp('SysMenuTreeGrid').getStore(),
selectedrecord = store.getAt(store.indexOf(rowIndex.getData()));
Ext.getCmp('MenuModify').getForm().loadRecord(selectedrecord);
},
onItemDblClick: function(grid, rowIndex){
var store = Ext.getCmp('SysMenuTreeGrid').getStore(),
selectedrecord = store.getAt(store.indexOf(rowIndex.getData())),
win = Ext.create('MyApp.view.systemconfig.SysMenuDetailWin',{
id: 'SysMenuDetailWin'
}),
form = Ext.getCmp('MenuDetail');
form.loadRecord(selectedrecord);
win.show();
},
onAddClick: function(grid, rowIndex, colIndex, actionItem, event, record, row) {
var win = Ext.create('MyApp.view.systemconfig.SysMenuCreateWin',{
id: 'SysMenuCreateWin'
});
win.show();
},
onDeleteClick: function(){
var grid = Ext.getCmp('SysMenuTreeGrid'),
selection = grid.selection,
data = selection.data,
store = grid.getStore(),
selectedrecord = store.getAt(store.indexOf(data));
Ext.MessageBox.confirm('操作确认', '您是否确定删除功能编号为:'+data.funcNum+ ' 的功能菜单?', function(btn){
if(btn=='yes'){
//Delete the data
//isLeaf?
if(data.isLeaf){
//Delete the leaf node
store.remove(selectedrecord);
Ext.getCmp('MenuModify').reset();
this.showToast('您已经删除了所选的数据!');
}else{
//Confirm to Delete Parent Node and its childNotes?
Ext.MessageBox.confirm('操作确认', '您是否确定删除功能编号为:'+data.funcNum+ ' 的功能菜单,以及它的所有子菜单?', function(btn){
if(btn=='yes'){
//Delete All nodes
store.remove(selectedrecord);
Ext.getCmp('MenuModify').reset();
this.showToast('您已经保存了修改的数据!');
}else{
//if NO btn was chose
this.showToast('您已经取消了删除操作!');
}
}, this);
}
}else{
//if NO btn was chose
this.showToast('您已经取消了删除操作!');
}
}, this);
},
onSaveClick: function(){
//1.Validate the data
//2.Confirm the operation
var form = Ext.getCmp('MenuModify');
Ext.MessageBox.confirm('操作确认', '您是否确定修改功能编号为:'+form.down('textfield').lastValue+ ' 的功能菜单?', function(btn){
if(btn=='yes'){
//3.Save the data
this.showToast('您已经保存了修改的数据!');
}else{
//if NO btn was chose
this.showToast('您已经取消了保存操作!');
}
}, this);
},
onRefreshClick: function(){
var grid = Ext.getCmp('SysMenuTreeGrid'),
selection = grid.selection,
data = selection.data,
store = grid.getStore(),
selectedrecord = store.getAt(store.indexOf(data));
Ext.getCmp('MenuModify').getForm().loadRecord(selectedrecord);
},
onCreateSaveClick: function(){
//1.Validate the data
//2.Confirm the operation
var form = Ext.getCmp('MenuForm'),
win,
grid = Ext.getCmp('SysMenuTreeGrid');
Ext.MessageBox.confirm('操作确认', '您是否确定创建功能编号为:'+form.down('textfield').lastValue+ ' 的功能菜单?', function(btn){
win = Ext.getCmp('SysMenuCreateWin');
if(btn=='yes'){
//3.Save the data
this.showToast('您已经保存了新建数据!');
//4.Close the window
win.close(); //Close to prevent duplicated ID of this window
//5.Reload the TreeGrid
grid.getStore().reload();
}else{
//if NO btn was chose
this.showToast('您已经取消了保存操作!');
}
}, this);
},
onWinRefreshClick: function(){
var form = Ext.getCmp('MenuForm');
form.reset();
},
onCreateCancelClick: function(){
var win = Ext.getCmp('SysMenuCreateWin');
win.close();
},
onDetailWinCloseClick: function(){
var win = Ext.getCmp('SysMenuDetailWin');
win.close();
},
showToast: function(s, title) {
Ext.toast({
html: s,
closable: false,
align: 't',
slideInDuration: 200,
minWidth: 400
});
}
});
视图控制器中为各种监听事件以及按钮等控件定义监听处理函数,达到了让处理逻辑与视图呈现分离的效果。
现在这个功能基本完成,这里省略了点击添加按钮出现的视图代码,来看看现在页面的效果: