资讯专栏INFORMATION COLUMN

extjs-mvc结构实践(四):导航菜单与控制器模块联动

figofuture / 3395人阅读

摘要:根据模块创建模块失败。在中,我们配置了标明了这是一个控制器模块,点击后会去触发控制器加载动作。正常情况下同一个模块的只加载一次。

前面几篇文档,我们基本实现了一个静态的extjs页面,本篇开始,实现左侧导航树与右侧内容的联动,也就是点击导航菜单,加载对应模块页面和业务逻辑,实现js文件的按需加载。
业务需求是这样的:

左侧的treelist,当点击某个节点的时候,系统根据tree数据里配置的模块信息,加载这个模块,并且把模块对应的主页面显示在中间区域的tabpanel里。

改造主控制器:app/luter/controller/MainController.js

监听导航树的node点击事件,进行后续处理:

 "navlist": {
    "itemclick":function(el, record, opt){
        //可以通过如下方式获取点击节点的数据。
        var nodeData = record.node.data;
    
    }
}

完整的代码如下:

Ext.define("luter.controller.MainController", {
    extend: "Ext.app.Controller",
    views: ["main.ViewPort"],
    stores: ["NavTreeStore"],
    init: function (application) {
        var me = this;
        this.control({
            "viewport": {//监听viewport的初始化事件,可以做点其他事情在这里,如有必要,记得viewport定义里的alias么?
                "beforerender": function () {
                    console.log("viewport begin render at:" + new Date());
                },
                "afterrender": function () {
                    console.log("viewport  render finished at:" + new Date());
                },
            },
            "syscontentpanel": {
                "afterrender": function (view) {
                    console.log("syscontentpanel rendered at:" + new Date());
                }
            },
            "navlist": {
                "itemclick": function (el, record, opt) {
                    var nodeData = record.node.data;//当前点击节点的数据

                    var tabpanel = Ext.getCmp("systabpanel");//中间tabpanel
                    var tabcount = tabpanel.items.getCount();//当前tabpanel已经打开几个tab了。
                    var maxTabCount = 5;//最大打开的tab个数
                    if (tabcount && tabcount > 5) {
                        showFailMesg({
                            title: "为了更好的使用,最多允许打开5个页面",
                            msg: "您打开的页面过多,请关掉一些!"
                        });
                        return false;
                    }
                    if (nodeData.leaf) {//是打开新模块,否则是展开树节点
                        var moduleID = nodeData.module_id;//找到控制器ID,定义在tree的数据里modole_id
                        if (!moduleID || moduleID == "") {
                            showFailMesg({
                                title: "创建模块失败.",
                                msg: "模块加载错误,模块id为空,创建模块失败"
                            });
                            return false;
                        }
                        console.log("to add module with id:" + moduleID);
                        //开始加载控制器
                        try {
                           //尝试加载这个控制器,这个过程就是按需ajax加载js文件的过程。
                          //如果这个模块被加载过,则不会重复加载。
                            var module = luterapp.getController(moduleID);
                        } catch (error) {
                            showFailMesg({
                                msg: "根据模块ID:" + moduleID + "创建模块失败。" +
                                "
可能的原因 :
1、该模块当前没有实现." + "
2、模块文件名称与模块名称不一致,请检查" + "
Error: " + error + "" }); return false; } finally { } //判断模块是否加载下来,因为是ajax加载,所以还是判断一下比较好 if (!module) { showFailMesg({ msg: "B:load module fail,the module object is null." + "
maybe :the module is Not available now." }); return false; } //加载到之后,默认去获取控制器里views:[]数组里的第一个作为主视图 var viewName = module.views[0]; console.log("will create a tab with view ,id:" + viewName); var view = module.getView(viewName); console.log("get the view el:" + view); if (!view) { showFailMesg({ msg: "Sorry ,to get the module view fail..." }); return false; } //判断一下这个视图是不是已经加载到tabpanel里去了 var tabid = me.getTabId(moduleID); console.log("will create a tab with id:" + tabid); var notab = tabpanel.getComponent(tabid); var viewEL = view.create(); if (!viewEL) { showFailMesg({ msg: "Sorry ,to get the module viewEL fail..." }); return false; } if (!notab && null == notab) {//不存在新建 //不管是啥,都放到一个panel里面。 notab = tabpanel.add(Ext.create("Ext.panel.Panel", { tooltip: nodeData.text + ":" + nodeData.qtip, id: tabid, // tab的唯一id title: nodeData.text, // tab的标题 layout: "fit", // 填充布局,它不会让load进来的东西改变大小 border: false, // 无边框 closable: true, // 有关闭选项卡按钮 iconCls: nodeData.iconCls, listeners: { // 侦听tab页被激活里触发的动作 scope: this, destroy: function () { console.log("tab :" + tabid + ",has been destroyed") } }, items: [view.create()] })); //新建之后focus tabpanel.setActiveTab(notab); } else {//如果这个tab已经存在了,则focus到这个tab tabpanel.setActiveTab(notab); } } else { //如果leaf =false,则说明这不是一个最底层节点,是目录,展开。 console.log("tree node expand") } } } }); }, //这个方法从tab id里分离出控制器名称 getTabId: function (mid) { var winid = mid; var c = winid.split("."); winid = c.pop(); return winid + "-tab"; } });
左侧菜单树对应的测试数据:app/testdata/menu.json

一般情况下,这个菜单数据是保存在后端的,通过权限判断加载用户可访问的资源形成树结构返回给前端使用。leaf标明了这是一个目录还是一个模块,module_id对应的是控制器的路径。 比如下面这个测试数据中。 "leaf": true, "module_id": "sys.UserController", 在app.js中,我们配置了appFolder:‘app/luter’, leaf标明了这是一个控制器模块,点击后会去触发控制器加载动作。 所以这个模块的实际路径(也就是js文件)就是:${appFolder}/controller/${module_id}.js 即:app/luter/controller/sys.UserController.js

 [
  {
    "id": "111",
    "text": "系统管理",
    "href": null,
    "leaf": false,
    "iconCls": "fa fa-home",
    "module_id": "no sign",
    "qtip": "这个地方显示鼠标悬停提示",
    "children": [
      {
        "id": "11111",
        "text": "用户管理",
        "href": null,
        "leaf": true,
        "iconCls": "fa fa-user",
        "module_id": "sys.UserController",
        "qtip": "系统用户管理",
        "children": []
      }
    ]
  }

]

导航菜单与tabpanel 联动完成,下面弄个控制器实验一下效果,以新建一个系统管理分类下的用户管理模块功能为例:
系统管理部分模块放在sys目录下,so:

控制器:app/luter/controller/sys/UserController.js

模型:app/luter/model/UserModel.js

Store:app/luter/store/UserStore.js

视图主入口:app/luter/model/view/sys/user/User.js

列表视图:app/luter/model/view/sys/user/UserList.js

......

用户管理控制器 :app/luter/controller/sys/UserController.js
Ext.define("luter.controller.sys.UserController", {
    extend: "Ext.app.Controller",
    stores: ["UserStore"], //用户store
    views: ["sys.user.User"], //主view ,tab里会加载第一个视图。
    init: function () {
        this.control({
            "userlistview": {
                "beforerender": function (view) {
                    console.log("beforerender   list......   ");
                },
                "afterrender": function (view) {
                    console.log("afterrender   list......   ");
                     // this.getUserStoreStore().load();//如果UserStore里没设置autoLoad: true,就可以在这里加载用户数据
                }
            }
        });

    }
});

用户模型Model:app/luter/model/UserModel.js
Ext.define("luter.model.UserModel", {
    extend: "Ext.data.Model",
    fields: [
        {name: "id", type: "string"},
        {name: "username", type: "string"},
        {name: "gender", type: "string"},
        {name: "real_name", type: "string"}
    ]
});
用户Store:app/luter/store/UserStore.js
Ext.define("luter.store.UserStore", {
    extend: "Ext.data.Store",
    autoLoad: true,//自动加载数据
    model: "luter.model.UserModel",//使用的模型
    pageSize: 15,//每页数据多少
    proxy: {
        type: "ajax",//ajax获取数据
        actionMethods: {
            create: "POST",
            read: "POST",
            update: "POST",
            destroy: "POST"
        },
        api: {
            read: "app/testdata/user.json"//从这个地方获取数据,当然,这里用测试数据
        },
        reader: {//返回数据解析器
            type: "json",
            root: "root",//用户列表数据在这个字段下
            successProperty: "success",//成功与失败的标志位是这个字段
            totalProperty: "total"//记录总数在这个字段
        },
        listeners: {
            exception: function (proxy, response, operation, eOpts) {
                DealAjaxResponse(response);//监听ajax异常提示错误
            }
        }
    },
    remoteSort: true,//服务器端排序
    sortOnLoad: true,//加载就排序
    sorters: {//拿ID排序
        property: "id",
        direction: "DESC"
    }

});
用户管理模块主视图:app/luter/model/view/sys/user/User.js
Ext.define("luter.view.sys.user.User", {
    extend: "Ext.panel.Panel",
    alias: "widget.userview",
    layout: "fit",
    requires: ["luter.view.sys.user.UserList"],//引入用户列表模块
    border: false,
    initComponent: function () {
        var me = this;
        me.items = [{
            xtype: "userlistview",
            layout: "fit"

        }]
        me.callParent(arguments);
    }
});
用户列表视图:app/luter/model/view/sys/user/UserList.js
Ext.define("luter.view.sys.user.UserList", {
    extend: "Ext.grid.Panel",
    alias: "widget.userlistview",//其他地方就可以这么用:xtype:‘userlistview’
    requires: [],
    store: "UserStore",//用到的store
    itemId: "userGrid",//自己的itemid
    columnLines: true,//是否显示表格线
    viewConfig: {
        emptyText: "暂无数据"//store没数据的时候显示这个
    },
    initComponent: function () {
        var me = this;
        me.columns = [{
            xtype: "rownumberer",
            text: "序号",
            width: 60
        }, {
            header: "操作",
            xtype: "actioncolumn",
            width: 60,
            sortable: false,
            items: [{
                text: "删除",
                iconCls: "icon-delete",
                tooltip: "删除这条记录",
                handler: function (grid, rowIndex, colIndex) {
                    var record = grid.getStore().getAt(rowIndex);
                    if (!record) {
                        toast({
                            msg: "请选中一条要删除的记录"
                        })
                    } else {
                        showConfirmMesg({
                            message: "确定删除这条记录?",
                            fn: function (btn) {
                                if (btn === "yes") {
                                    Ext.Ajax.request({
                                        url: "sys/user/delete",
                                        method: "POST",
                                        params: {
                                            id: record.get("id")
                                        },
                                        success: function (response, options) {
                                            DealAjaxResponse(response);
                                            Ext.data.StoreManager.lookup("User").load();
                                        },
                                        failure: function (response, options) {
                                            DealAjaxResponse(response);
                                        }
                                    });
                                } else {
                                    return false;
                                }
                            }

                        })

                    }

                }
            }]
        }, {
        
            header: baseConfig.model.user.id,
            dataIndex: "id",
            hidden: false,
            flex: 1
        },

            {
                header: baseConfig.model.user.username,
                dataIndex: "username",
                flex: 1
            },
            {
                header: baseConfig.model.user.real_name,
                dataIndex: "real_name",
                flex: 1
            }
        ]

        me.bbar = Ext.create("Ext.PagingToolbar", {
            store: me.store,
            displayInfo: true,
            displayMsg: "当前数据 {0} - {1} 总数: {2}",
            emptyMsg: "没数据显示",
            plugins: [new Ext.create("luter.ux.grid.PagingToolbarResizer", {
                options: [5, 10, 15, 20, 25, 50, 100]
            })]
        })
        me.dockedItems = [{
            xtype: "toolbar",
            items: [{
                text: "添加",
                iconCls: baseConfig.appicon.add,
                tooltip: "添加",
                handler: function () {
                    var win = Ext.create("luter.view.sys.user.UserAdd");
                    win.loadView();
                    win.show();

                }
            }]
        }]
        me.listeners = {
            "itemdblclick": function (table, record, html, row, event, opt) {
                if (record) {
                    var id = record.get("id");
                    var view = Ext.create("luter.view.sys.user.UserEdit", {title: "编辑数据"});
                    view.loadView();
                    loadFormDataFromDb(view, "sys/user/view?id=" + id);
                } else {
                    showFailMesg({
                        msg: "加载信息失败,请确认。"
                    })
                }

            }
        }
        me.plugins = []
        me.callParent(arguments);
    }
});

//这里的baseConfig定义在公共配置文件config.js中,如下:
公共配置参数定义文件:app/luter/config.js

别忘记在app.html中app.js之前引入这个文件。

/**
 * icon_prefix font字体前缀定义
 * baseConfig 全局配置
 */
var icon_prefix = " fa blue-color ", baseConfig = {
    /**
     * 全局常量定义
     */
    cons: {
        noimage: "app/resource/images/noimage.jpg",
        /**
         * 静态服务器的地址
         */
        static_server: ""
    },
    /**
     * 渲染器,对Boolean类型的表格列的显示内容进行渲染
     */
    renders: {
        trueText: "",
        falseText: "",
        cancel: ""
    },
    /**
     * 图标定义
     */
    appicon: {
        home: icon_prefix + "fa-home",
        add: icon_prefix + "fa-plus",
        update: icon_prefix + "fa-edit",
        trash: icon_prefix + "fa-trash",
        delete: icon_prefix + "fa-remove red-color",
        set_wallpaper: icon_prefix + "fa-image",
        setting: icon_prefix + "fa-gears",
        desktop: icon_prefix + "fa-desktop",
        pailie: icon_prefix + "fa-cubes",
        logout: icon_prefix + "fa-power-off",
        avatar: icon_prefix + "fa-photo",
        key: icon_prefix + "fa-key",
        user: icon_prefix + "fa-user",
        refresh: icon_prefix + "fa-refresh blue-color",
        close: icon_prefix + "fa-close",
        male: icon_prefix + "fa-male",
        female: icon_prefix + "fa-female",
        role: icon_prefix + "fa-users",
        user_add: icon_prefix + "fa-user-plus",
        undo: icon_prefix + "fa-undo",
        search: icon_prefix + "fa-search",
        reset: icon_prefix + "fa-retweet",
        yes: icon_prefix + "fa-check green-color",
        no: icon_prefix + "fa-close red-color",
        list_ol: icon_prefix + " fa-list-ol",
        list_alt: icon_prefix + " fa-list-alt",
        ban: icon_prefix + "fa-ban",
        log: icon_prefix + "fa-tty",
        printer: icon_prefix + "fa-print",
        fax: icon_prefix + "fa-fax",
        download: icon_prefix + "fa-cloud-download",
        upload: icon_prefix + "fa-cloud-upload",
        comment: icon_prefix + " fa-commenting-o",
        credit: icon_prefix + "fa fa-gift"
    },
    /**
     * 模型定义
     */
    model: {
        /**
         * 系统用户模型
         */
        user: {
            id: "ID",
            username: "用户名",
            real_name: "真实姓名"
        }
    }
};

最后,附上用户列表的测试数据(当然,瞎编的......):app/testdata/user.json

{
  "total": 33,
  "root": [
    {
      "id": "aaa",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "ccc",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "ffffd",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "eee",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    },
    {
      "id": "fff",
      "username": "user",
      "real_name": "用户"
    }
  ],
  "success": true
}

如上,没问题的话,刷新页面,应该能看到如下所示:

上图中,一些Extjs默认的样式经过了hack。不是默认样式。
最终,整个项目的目录结构如下:

判断是不是动态按需加载?

1、打开chrome的开发控制台,切换到network面板的js下。
2、刷新页面
3、重复点击左侧用户管理,查看JS加载情况。正常情况下同一个模块的js只加载一次。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92699.html

相关文章

  • extjs-mvc结构实践(三):完善基本页面2

    摘要:结构实践三完善基本页面一般经典的后台管理系统,都是左侧菜单右侧结构布局。不免俗,咱也这么实现定义左侧导航菜单新建采用的组件构建一个导航菜单为了显示图标,引入字体图标,在引入引入定义导航菜单数据功能菜单展开节点。 extjs-mvc结构实践(三):完善基本页面2 一般经典的后台管理系统,都是左侧菜单右侧tabs结构布局。不免俗,咱也这么实现! 定义左侧导航菜单 新建:app/luter/...

    X1nFLY 评论0 收藏0
  • extjs-mvc结构实践(三):完善基本页面

    摘要:上篇实现了基本的代码架构,控制器动态加载功能以及一个基础的页面布局,本节开始,将陆续完善这个页面。页面底部区域,主要显示版权信息等,也可以显示个时间啥的。。。头部和底部定义完毕后,需要在中引入对应位置。 上篇实现了基本的代码架构,控制器动态加载功能以及一个基础的页面布局,本节开始,将陆续完善这个页面。 目标 如前所述,我们的页面包含这么几个区域: header: UI顶部区域,显示系...

    zhkai 评论0 收藏0
  • extjs-mvc结构实践(二):基本页面

    摘要:接着来,上一篇搭建了基本的项目骨架,到最后,其实啥也没看见。。。目标全屏显示左侧导航菜单,右侧标签页切换操作内容区域。一般模型与你后台返回的数据结构一一对应。给其他组件提供一致接口使用数据。整个构成一个所谓的。 接着来,上一篇搭建了基本的项目骨架,到最后,其实啥也没看见。。。书接上回,开始写UI效果。 目标 全屏显示、左侧导航菜单,右侧标签页切换操作内容区域。包含header和foo...

    changfeng1050 评论0 收藏0
  • extjs-mvc结构实践(一):搭建基础架构

    摘要:今天开始,一点点记录一下使用搭建一个基础结构的过程。没办法,记性差这种结构的前端,主要是面向后台信息管理系统,可以最大限度的规范前端代码结构和数据结构。 今天开始,一点点记录一下使用extjs6.2.0搭建一个基础MVC结构的过程。没办法,记性差:)这种结构的UI前端,主要是面向后台信息管理系统,可以最大限度的规范前端代码结构和数据结构。做网站 或者手机端,这种方式全引入了extjs,...

    kamushin233 评论0 收藏0
  • 商城“智能”导航实践

    摘要:细心的用户可能会发现,在或者等大型网站中,当鼠标在一级导航栏中垂直移动时,二级菜单可以无延迟的响应展示。很显然,用户希望在选择某一级菜单下的子菜单时,想要以斜向最短路径移动鼠标,而其他掠过的一级菜单也并不会激活。 需求与目标 在电商的大屏主页上,一般都会有一个显眼的品类导航栏,作为整个商城的重要分流入口,客户体验就必须要做到自然、极致。细心的用户可能会发现,在jd.com或者tmall...

    sixgo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<