打造Ext2.0模块化单页系统(一)
大多数web应用系统都会包含功能菜单和显示页面,功能菜单可以是页面左面的一棵树,也可以是一个可以切换的多标签页,而显示页面无非就是一个空白区域,点击相应的功能菜单,切换不同的内容。 经常看到有人讨论如何用Ext加载iframe,这不失为一种简便的办法,但是它的弊端也是显而易见的。 1.每个页面都需要引用庞大的Ext类库,这样的系统在局域网里还可以接受,但作为一个公网系统就让人无法忍受了。 2.每个iframe中的页面自成一体,相互间的结合并不是那么紧密,数据互访也有一定难度。 3.通常系统中都会有弹出窗口,并且在弹出窗口下面会有遮罩盖住整个页面来阻止用户进行非法操作,而iframe中的弹出窗口只能在本页范围内拖动,下面的遮罩也只能盖住本页。 既然选择了Ext2.0这个庞大的框架,为什么我们不更Ajax一点呢,下面就谈谈如何运用Ext2.0框架来打造一个真正的模块化的单页系统。 一个单页系统应该有以下特点: 1.一个模块基类来封装所有模块的公共属性和方法。 2.封装每个模块成单独的js文件,并按需加载。 3.整个程序有一个主程序类来管理所有的模块的加载与销毁。 先来看看模块基类的代码:
-
- Ext.namespace('demo');
- demo.module = function(main){
- this.main = main;
- this.init();
- }
-
- Ext.extend(demo.module, Ext.util.Observable, {
- init : Ext.emptyFn
- });
view plain copy to clipboard print ?
-
//定义程序的命名空间Ext.namespace('demo');demo.module = function(main){ this.main = main; this.init();}Ext.extend(demo.module, Ext.util.Observable, { init : Ext.emptyFn});
首先定义一个程序的命名空间(这是个好习惯,省得在代码海洋中找不到哪些是自己定义的类,哪些是系统本身的类。) 模块既然是被主窗口加载进来的,那么相对这个模块来说它必然有个父窗口来显示它,我们把这个父窗口作为参数传给模块,让它知道在哪里显示自己。这样对每个继承它的子类来说,this.main就是它的父窗口。 模块基类还包含一个init方法,这个方法将被模块子类重载,模块子类的界面渲染就交给这个方法了。 当然你还可以在这个基类中添加每个模块必需的公共属性和方法,让我们的模块实现高度统一。 模块基类写好了,接下来写主程序类。用面向对象的方法写js刚开始会有点不习惯,我们早已习惯了function嘛,但是如果你能坚持改变你的代码写法,你会渐渐的发现这种写法的优越性,尤其在js动辄几百行的ajax时代。 在面向对象的世界中,尘归尘、土归土,一切皆对象。 在这个demo中,整个程序分为3个模块,用TabPanel来导航。 主程序类:
- demo.app = function(){
- this.init();
- }
- Ext.extend(demo.app, Ext.util.Observable, {
- init: function(){
- this.tab1 = new Ext.Panel({
- title: '模块一',
- id: 'module1',
- layout: 'fit'
- });
-
- this.tab2 = new Ext.Panel({
- title: '模块二',
- id: 'module2',
- layout: 'fit'
- });
-
- this.tab3 = new Ext.Panel({
- title: '模块三',
- id: 'module3',
- layout: 'fit'
- });
-
- this.body = new Ext.TabPanel({
- region:'center',
- margins:'0 5 0 5',
- autoScroll: true,
- items: [this.tab1, this.tab2, this.tab3]
- });
-
- var viewport = new Ext.Viewport({
- layout:'border',
- items:[
- new Ext.BoxComponent({region:'north', el:'header', height:60}),
- new Ext.BoxComponent({region:'south', el:'footer', height:50}),
- this.body
- ]
- });
-
- this.body.on('tabchange', this.tabActive, this);
- this.loadMask = new Ext.LoadMask(this.body.body);
- this.body.activate(this.tab1);
- },
- tabActive: function(p,t){
- if(this[t.id]){
- return false;
- }
- this.loadMask.show();
- Ext.Ajax.request({
- method:'GET',
- url: 'modules/'+t.id+'.js',
- scope: this,
- success: function(response){
- var module = eval(response.responseText);
- this[t.id] = new module(t);
- this.loadMask.hide();
- }
- });
- }
- });
-
- Ext.onReady(function(){
- Ext.QuickTips.init();
- myApp = new demo.app();
- });
view plain copy to clipboard print ?
- demo.app = function(){ this.init();}Ext.extend(demo.app, Ext.util.Observable, { init: function(){ this.tab1 = new Ext.Panel({ title: '模块一', id: 'module1', layout: 'fit' }); this.tab2 = new Ext.Panel({ title: '模块二', id: 'module2', layout: 'fit' }); this.tab3 = new Ext.Panel({ title: '模块三', id: 'module3', layout: 'fit' }); this.body = new Ext.TabPanel({ region:'center', margins:'0 5 0 5', autoScroll: true, items: [this.tab1, this.tab2, this.tab3] }); var viewport = new Ext.Viewport({ layout:'border', items:[ new Ext.BoxComponent({region:'north', el:'header', height:60}), new Ext.BoxComponent({region:'south', el:'footer', height:50}), this.body ] }); this.body.on('tabchange', this.tabActive, this); this.loadMask = new Ext.LoadMask(this.body.body); this.body.activate(this.tab1); }, tabActive: function(p,t){ if(this[t.id]){ return false; } this.loadMask.show(); Ext.Ajax.request({ method:'GET', url: 'modules/'+t.id+'.js', scope: this, success: function(response){ var module = eval(response.responseText); this[t.id] = new module(t); this.loadMask.hide(); } }); }});Ext.onReady(function(){ Ext.QuickTips.init(); myApp = new demo.app();});
demo.app = function(){ this.init();}Ext.extend(demo.app, Ext.util.Observable, { init: function(){ this.tab1 = new Ext.Panel({ title: '模块一', id: 'module1', layout: 'fit' }); this.tab2 = new Ext.Panel({ title: '模块二', id: 'module2', layout: 'fit' }); this.tab3 = new Ext.Panel({ title: '模块三', id: 'module3', layout: 'fit' }); this.body = new Ext.TabPanel({ region:'center', margins:'0 5 0 5', autoScroll: true, items: [this.tab1, this.tab2, this.tab3] }); var viewport = new Ext.Viewport({ layout:'border', items:[ new Ext.BoxComponent({region:'north', el:'header', height:60}), new Ext.BoxComponent({region:'south', el:'footer', height:50}), this.body ] }); this.body.on('tabchange', this.tabActive, this); this.loadMask = new Ext.LoadMask(this.body.body); this.body.activate(this.tab1); }, tabActive: function(p,t){ if(this[t.id]){ return false; } this.loadMask.show(); Ext.Ajax.request({ method:'GET', url: 'modules/'+t.id+'.js', scope: this, success: function(response){ var module = eval(response.responseText); this[t.id] = new module(t); this.loadMask.hide(); } }); }});Ext.onReady(function(){ Ext.QuickTips.init(); myApp = new demo.app();});
首先定义了一个初始化方法来渲染页面,这个页面采用Ext.Viewport的'border'布局,分为头部、中部和脚部3个区域,头部和脚步是 html上的2个div,用来放程序的logo和版权信息什么的。中间的区域是个TabPanel,其中每个Tab对于一个模块的显示窗口,也就是模块的 this.main。 动态加载模块的行为就由TabPanel的tabchange来触发,考虑到加载模块需要一定时间,我们还需要在TabPanel的body中定义一个 Ext.LoadMask来让用户知道程序在读取数据。在模块加载之前让它show出来,加载结束后hide。 需要注意的是这个TabPanel在配置参数中并没有设置activeTab,而是在定义完Ext.LoadMask后才让它手动激活第一个Tab。 好了,接下来该定义tabActive方法了,这个方法接受2个参数:p是TabPanel,t是当前被激活的Tab(请参阅API)。 思考一个问题,我们如何让主程序知道某个模块已经加载,而防止重复加载模块的JS文件呢?这里提供了一个简便的办法: 我们利用每个Tab的id作为模块被实例化后的对象名,这样就可以根据当前被激活Tab的id来反射出相应的模块对象是否已经被创建了。 另外为了简便,我们把每个模块的js文件也命名为相应Tab的id,这样就可以根据当前被激活Tab的id来加载相应的JS文件。 来看tabActive方法,先不去管开始的if语句,看下面的Ext.Ajax.request。很明显,这行语句根据当前被激活Tab的id异步加载 了一个相应的JS模块文件,response.responseText表示返回的数据字符。感谢JS强大的eval方法,这个方法可以把一段字符串当作 代码来执行(就像SQL里面的EXEC语句)。执行完这行后我们得到了什么?对了,就是模块子类。 接下来把模块子类实例化,把当前激活的Tab作为它的main传入,把这个模块对象用Tab的id来命名。 this[t.id] = new module(t);这里的this指向主程序类,也就是说模块作为主程序的一个属性。 最后把Ext.LoadMask隐藏吧,模块对象以及被创建好了。 回头来看if语句,当一个模块被实例化后,主程序相应的模块属性就被创建,这里就可以判断一下相应的模块有没有被实例化过,已经存在就返回,不执行下面的 Ajax请求了。 最后把主程序类实例化,大功告成。 题外话:有人说用panel的autoload也能实现上面的功能。没错,但是这样做失去了一些灵活性,其实autoload的实现方法和上面的差不多。 下次我们再来看看每个模块是怎么写的。