Extjs5.0(6):控制器(Controller、ViewController)和路由器(Router)

控制器

 

上一篇文章我们已经为项目添加了左侧导航栏,接下来要为导航栏添加点击事件,点击左侧导航栏,右侧界面出现相应变化。

 

为组件添加事件,就要用到控制器了。Extjs5提供了两种控制器:Controller和ViewController,这两种控制器都继承自BaseController。从功能上来讲,两者的大多数功能都是一致的,也就是说,在不严格区分的情况下,这两者选哪个都能用。从作用范围上讲,Controller范围比较大,属于应用级别的,ViewController范围比较小,属于界面级别的,也就是说Controller一般用于控制整个app,ViewController一般用于控制app中的某一个view。

 

在Extjs4中,只有Controller。Extjs4 MVC中,Application一开始时,就加载所有的Controller(有些人用了其他的方法避免了这个问题,有兴趣可以上网查下),其生命周期和我们的Application是一样的。这里就存在一些问题了,一般来讲,Application开始运行时,我们只需要加载首页的相关内容就行,其他页面由于没有显示,就无需加载了。例如我们首页是页面A,在A中点击某个按钮能跳到页面B,比较好的做法是,B页面的View和Controller我们可以在A中点击按钮之后才加载,没有必要再加载A页面的同时也加载B页面,但是前面讲了,Application开始时,就加载了所有的Controller,无论当前有没用到,都加载了,并且跟随着Application的生命周期一同存在,并且Controller里面还需要加载其控制的View,那么就相当于Application一开始,就把所有东西都加载进来了,性能和效率都是比较差的。

 

在Extjs5中,针对于Controller存在的问题,新增了ViewController。ViewController无需在Application开始时就加载,只有在使用了某个View时,才在View中加载其对应的ViewController,所以说ViewController是控制页面级别的。

 

根据前面的解释,似乎我们为导航栏添加控制器,应该使用Controller。但是,这里我还是使用ViewController。只要使用ViewController的View不销毁,那么ViewController也一样一直存在于Application中,一样能达到Controller的效果。所以,当我前面讲的都是废话吧。

 

为什么不用Controller呢?在官方的文档中有这么一段话“While Ext JS 5 is backwards compatible with current controllers, itintroduces a new type of controller designed to handle these challenges:Ext.app.ViewController.”,我翻译为:“Ext JS 5在向后兼容目前的控制器的同时,也引入了一个新的控制器设计来处理这些挑战(我们前面讲的Controller存在的问题):Ext.app.ViewController”,并且我们发现,在用senchacmd5 生成的Extjs5项目结构中,并没有controller这个包,而在extjs 4的项目中,是有这个包的。所以,我认为,Controller之所以存在,是为了兼容旧版本,新版本中,我们应该使用ViewController。但是有一点需要说明的是,ViewController对Controller做了简化,有些方法Controller里有而ViewController里没有,所以不排除有些时候你只能用Controller。

 

下面开始为导航栏添加ViewController,监听树节点的点击事件。

 

在我们的main包中,已经有一个ViewController:MainController,我们这里就不用重复创建,直接使用这个ViewController。


修改MainController.js如下:

Ext.define('MyApp.view.main.MainController', {
    extend: 'Ext.app.ViewController',

    requires: [
        'Ext.window.MessageBox',
        'MyApp.store.main.Navigation'
    ],

    alias: 'controller.main',

    control: {
        'app-navigation': {//组件别名,表示要控制的是该组件
            selectionchange: 'onTreeNavSelectionChange'
        }
    },

    onTreeNavSelectionChange:function(selModel, records){
        var record = records[0];
        alert(record.getId());
    }

});

注意我们新增的这一段代码

control: {
        'app-navigation': {//组件别名,表示要控制的是该组件
            selectionchange: 'onTreeNavSelectionChange'
        }
    },

'app-navigation'是我们要控制的组件的别名,我们的导航栏的别名就是'app-navigation',所以这里控制的是导航栏这个view。再看selectionchange,表示的我们要监听treepanel(导航栏)的“选择改变”事件,当改变选择的树节点时,会触发该事件。后面的'onTreeNavSelectionChange'指的是触发时使用的函数,这个函数我们定义在最下面

onTreeNavSelectionChange:function(selModel, records){
        var record = records[0];
        alert(record.getId());
    }

所以我们当前为控制器增加的功能是:改变点击树节点时,alert出所选择的树节点的id值。

控制器写好之后,要把控制器引入到其要控制的view中,我们当前的控制器要控制的是导航栏(Navigation.js),我们可以把控制器引入到Navigation.js中,由于导航栏是属于主界面(Main.js)中的,所以我们也可以把控制器引入到Main.js中,这里我选择后者,因为以后可能Header.js也需要监听器,或者主界面中其他组件也要监听,我们没必要创建那么多控制器,就把这些组件的控制事件都集中在一个控制器里面就好,然后把控制器引入到这些组件的父界面上,就可以了。

 

观察Main.js代码

Ext.define('MyApp.view.main.Main', {

    extend: 'Ext.container.Container',

    requires: [
        'MyApp.view.main.MainController',//引入controller
        'MyApp.view.main.MainModel',
        'MyApp.view.main.Header',//引入Header
        'MyApp.view.main.Navigation'//引入导航栏
    ],

    xtype: 'app-main',

    controller: 'main',//指定控制器

    viewModel: {
        type: 'main'
    },

    layout: {
        type: 'border'
    },

    items: [
        {
            region: 'north',
            xtype: 'app-header'//使用Header
        }, {
            xtype: 'app-navigation',
            region: 'west'
        }, {
            region: 'center',
            xtype: 'tabpanel',
            items: [{
                title: 'Tab 1',
                html: '<h2>Content appropriate for the current navigation.</h2>'
            }]
        }]
});

我们在requires中加载'MyApp.view.main.MainController',同时用controller:'main'指定当前界面使用的控制器,’main’指的是控制器的别名,我们前面在定义控制器的时候,有这么一段代码,就是创建别名的:alias:'controller.main'。这样子就为我们的导航栏添加好点击事件了。

浏览器中打开我们的项目,点击左侧导航栏节点,效果图


 路由器

在一个正常的网站中,用户从不同的页面导航,点击链接跳转到新的页面,可以按浏览器上的“前进”“后退”按钮跳转到下一个或者上一个页面,然而,在一个单页面应用程序中,用户的交互不加载一个新页面,这时如果用户点击后退按钮,则会退出我们整个应用。路由器就是用来解决这个问题的。

路由器可以通过使用浏览器历史堆栈来跟踪应用程序状态。路由器还允许深度链接到应用程序中,允许直接连接到一个特定的应用程序的一部分。说白了,路由器就是跟踪应用的状态,当点击浏览器“前进”或者“后退”按钮时,路由器会使我们的应用跳转到前一状态或者后一状态。

浏览器浏览互联网使用URI由许多部分组成。让我们看一个示例URI:

http://www.example.com/apps/users#user/1234

这应该比较熟悉。然而,你可能不认识#user/1234。这部分的URI被称为散列或片段标识符。散列的更多信息,请参阅http://en.wikipedia.org/wiki/Fragment_identifier这个资源。这个散列为应用程序提供了一种方法来控制浏览器的历史堆栈,而不用重新加载当前页面。随着散列的变化,浏览器添加整个URI到历史堆栈,然后允许您使用浏览器的前进/后退按钮来遍历URI。例如,如果你更新散列为:

http://www.example.com/apps/users#user/5678

用户可以单击后退按钮回到#user/ 1234散列,应用程序可以根据这个通知做相应编号。Extjs的路由器就是依赖于浏览器的允许应用程序状态跟踪和深度链接哈希功能。

以上是我翻译的官网上对路由器的解释。总结而言就是:路由器可以使我们的应用程序在不重新加载页面的情况下,通过浏览器上的“后退”“前进”按钮来返回到前一步操作或者前进到下一步操作。

 

我们在MainController.js中使用路由器功能,当点击导航栏节点时,在我们浏览器的url地址后面加上#id,然后我们通过路由器读取这个id,并做下一步操作。

 

修改MainController.jsonTreeNavSelectionChange这个函数为:

onTreeNavSelectionChange: function(selModel, records) {
    var record = records[0];
    if (record) {
        this.redirectTo(record.getId());
    }
}

刷新浏览器页面,点击左边树节点,会发现我们的地址栏变了

这时候可以点击浏览器的“前进”“后退”按钮,会发现地址栏跟着变。

 

接下来根据地址栏的变化,控制我们页面的变化,MainController.js中加入以下代码。

routes  : {
    ':id': 'handleRoute'//执行路由
},

继续添加'handleRoute'函数

handleRoute : function(id) {
    console.log("action"+id);
}

这时候刷新浏览器页面,打开浏览器控制台,再点击我们应用中导航栏的某个节点,观察控制台打印的信息

说明我们已经捕捉到浏览器中地址的变化,并且拿到这个id值。我们要实现的功能是,把取到的id值作为别名,通过这个别名来创建相应的组件,放在右侧的内容panel里面。这样做会遇到一个问题,就是如果没有以此id为别名的组件,那么跳转过去就会出错。所以,我们在路由跳转时应该分两步操作,第一步判断是否有此组件,有的话则跳转,没有的话,则不跳转。

修改MainController.js中的routes

routes  : {
    ':id': {
        action: 'handleRoute',//执行跳转
        before: 'beforeHandleRoute'//路由跳转前操作
    }
},

路由跳转前执行'beforeHandleRoute'函数,先判断浏览器地址栏中#号后面的id参数,是否在左侧导航栏有节点的id与之相同,如果有,则继续执行,如果没有,跳转到原始界面(刚进入项目时的界面)。

beforeHandleRoute: function(id, action) {
    var me = this,
        store =Ext.StoreMgr.get('navigation');
    var node = store.getNodeById(id);

    if (node) {
        //resume action
        action.resume();
    } else if(store.getCount() === 0){
        action.stop();
        me.redirectTo(id);
    }else {
        Ext.Msg.alert(
            '路由跳转失败',
            '找不到id为' + id + ' 的组件. 界面将跳转到应用初始界面',
            function() {
                me.redirectTo('all');
            }
        );
        //stop action
        action.stop();
    }
},

注意三个地方,第一,我们查找树节点时,是通过storeId查找到树的store,然后再通过路由传过来的id获取这个节点,所以我们要先为treestore定义一个storeId,这样才能查找到。

修改MainModel.js,为navigationStore添加storeId

Ext.define('MyApp.view.main.MainModel', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.main',

    requires:['MyApp.store.main.Navigation'],//引入必要文件

    data: {
        navigationTitle: '导航栏'//导航栏标题
    },

    stores:{
        navigationStore:{
            type:'navigation',//导航栏treestore
            storeId:'navigation'
        }
    }
});

第二个注意的地方就是这段代码“else if(store.getCount() === 0)”,这里多了这一步判断主要是因为在路由跳转过程中,有可能出现store还未加载,就已经开始判断是否有这个树节点,这时候肯定是没有这个节点的,所以如果store的数据数量为0,则表示未加载store,就停止当前跳转,重新定向。

第三个要注意的地方就是这段代码“me.redirectTo('all')”,表示找不到对应组件时,路由器时跳转到’all’,我们这里把all这个路由地址表示为我们的原始页面,也就是说,浏览器地址最后如果是#all的话,则跳转到原始界面,那么我们在应用开始时,也应该设置默认路由为’all’。

修改Application.js

Ext.define('MyApp.Application', {
    extend: 'Ext.app.Application',
    
    name: 'MyApp',

    stores: [
        // TODO: add global / shared stores here
    ],
    
    launch: function () {

    },

    init: function() {
        var me = this;
        //设置默认路由
        me.setDefaultToken('all');
    }

});

这样设置之后,我们一开始运行应用时,地址栏会在后面有个#all,来标志当前状态为初始状态。

浏览器中运行项目,会发现浏览器地址栏的改变。

同时也触发了提示信息


由于当前我们左侧树节点没有id为all的节点,所以就触发了警告,我们可以为树节点的根节点添加id,设置为all。

修改store/main/Navigation.js

Ext.define('MyApp.store.main.Navigation', {
    extend: 'Ext.data.TreeStore',
    alias: 'store.navigation',
    proxy: {
        type: 'ajax',
        url: 'resources/data/Navigation.json'
    },
    root: {
        text: 'All',
         id: 'all',
        expanded: true
    }
});

再刷新页面,发现警告没了。

接下来继续修改修改MainController.js,添加'handleRoute'函数,该函数为路由跳转成功后的操作,如果当前点击的是左侧导航栏的叶子节点,右侧panel则跳转到叶子节点id对应的组件,如果点击的是非叶子节点,右侧panel则跳转到另外的用于导航的界面上。这里我们先做简单的响应,具体的响应,下篇博客再介绍。

handleRoute: function(id) {
    var me = this,
        store = Ext.StoreMgr.get('navigation'),
        node = store.getNodeById(id);
    if(node.isLeaf()){
        Ext.Msg.alert(
            '提示',
            '当前点击的是叶子节点,右侧panel将跳转到对应的组件上');
    }else{
        Ext.Msg.alert(
            '提示',
            '当前点击的是非叶子节点,右侧panel将跳转到导航界面上');
    }
}

完整的MainController.js代码:

Ext.define('MyApp.view.main.MainController', {
    extend: 'Ext.app.ViewController',

    requires: [
        'Ext.window.MessageBox',
        'MyApp.store.main.Navigation'
    ],

    alias: 'controller.main',

    control: {
        'app-navigation': {//组件别名,表示要控制的是该组件
            selectionchange: 'onTreeNavSelectionChange'
        }
    },

    routes  : {
        ':id': {
            action: 'handleRoute',//执行跳转
            before: 'beforeHandleRoute'//路由跳转前操作
        }
    },

    onTreeNavSelectionChange: function(selModel, records) {
        var record = records[0];
        if (record) {
            this.redirectTo(record.getId());
        }
    },

    beforeHandleRoute: function(id, action) {
        var me = this,
            store =Ext.StoreMgr.get('navigation');
        var node = store.getNodeById(id);

        if (node) {
            //resume action
            action.resume();
        } else if(store.getCount() === 0){
            action.stop();
            me.redirectTo(id);
        }else {
            Ext.Msg.alert(
                '路由跳转失败',
                '找不到id为' + id + ' 的组件. 界面将跳转到应用初始界面',
                function() {
                    me.redirectTo('all');
                }
            );
            //stop action
            action.stop();
        }
    },
    handleRoute: function(id) {
        var me = this,
            store = Ext.StoreMgr.get('navigation'),
            node = store.getNodeById(id);
        if(node.isLeaf()){
            Ext.Msg.alert(
                '提示',
                '当前点击的是叶子节点,右侧panel将跳转到对应的组件上');
        }else{
            Ext.Msg.alert(
                '提示',
                '当前点击的是非叶子节点,右侧panel将跳转到导航界面上');
        }
    }

});

运行一下,点击左侧导航栏不同的树节点



下篇博客介绍点击左侧导航栏,右侧跳转到相应界面的具体操作。

另外,我也把这个项目托管在github上: https://github.com/likeadog/Extjs5.0Demo

交流群:170198012



  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值