【问题标题】:Marionette: Starting and stopping modules based on route regexpMarionette:基于路由正则表达式启动和停止模块
【发布时间】:2015-02-23 17:18:15
【问题描述】:

我正在实现一个应用程序,它在顶级应用程序模块中有两个独立的子模块。 我有一个管理模块,其路由约定以/admin 开头,用户模块的路由以/user 开头。顶级应用程序定义了一个rootRoute,因此当您导航到http://url/ 时,您将根据权限重定向到管理员或用户页面。我想了解的是是否可以根据路线启动和停止特定模块。这是我的意思的一个例子:

假设我有一个顶级应用程序(在 coffeescript 中)

@ClientApp = do (Backbone, Marionette) ->

  App = new Marionette.Application

  navigate: (route, options = {}) ->
    Backbone.history.navigate route, options

  App.on "start", (options) ->
    if Backbone.history
      Backbone.history.start()

  App.on "stop", ->
    App.module("AdminApp").stop()
    App.module("UserApp").stop()

  class App.Router extends Marionette.AppRouter
    initialize: (options) ->
      @route /^admin(.*)/, 'startAdminApp', options.controller.startAdminApp
      @route /^user(.*)/, 'startUserApp', options.controller.startUserApp

    appRoutes:
      "": "redirectToRoot"

  App.addInitializer ->
    new App.Router
      controller: API

  API =
    redirectToRoot: ->
      # some redirect logic that will lead you to either /admin or /user

    startAdminApp: ->
      App.mainRegion.show new App.Layouts.Admin
      App.module("AdminApp").start()

    startUserApp: ->
      App.mainRegion.show new App.Layouts.User
      App.module("UserApp").start()

  App

在管理和用户子模块中我也定义了路由

@ClientApp.module "AdminApp.DashboardApp", (DashboardApp, App, Backbone, Marionette, $, _) ->

  _.extend DashboardApp, Backbone.Wreqr.radio.channel("dashboard")

  class DashboardApp.Router extends Marionette.AppRouter
    appRoutes:
      "admin/dashboard": "statistics"

  API =
    getLayout: ->
      new DashboardApp.Layout.View

    statistics: ->
      DashboardApp.StatisticsAp.start()

  DashboardApp.on "start", ->
    @layout = API.getLayout().render()
    API.statistics()

  App.addInitializer ->
    new DashboardApp.Router
      controller: API

如果我导航到/,应用程序按预期工作,我将被重定向到必要的命名空间并启动特定的子模块。但是,如果我在子模块中定义一些其他路由,它们似乎会覆盖现有的正则表达式匹配器。因此,如果我打开浏览器并导航到/admin/statistics,它将不会启动管理应用程序并且/admin/statistics 的回调将失败并出现错误。那是因为管理应用程序不会启动并且 mainRegion 没有填充相应的布局。请注意,在任何子模块之前都需要包含顶级应用程序定义的文件(我猜这就是路由被覆盖的原因)。我也知道骨干路由器会在遇到第一个匹配时调用路由回调。

所以问题是是否有可能实现一种路由管理器,它将使用正则表达式检查当前路由并启动或停止相应的应用程序(管理员或用户),所有已定义的子路由都是持久且可收藏的?

【问题讨论】:

    标签: javascript backbone.js coffeescript routing marionette


    【解决方案1】:

    有关闭的任务要解决,没有找到任何现有的解决方案,所以这是我创建的small stub - project

    要解决这样的任务,需要解决的问题很少:

    1) 异步路由。 rootRouter 之类的东西应该加载应用程序模块,moduleRouter 应该调用控制器方法

    2) 在模块停止时清除主干历史处理程序。问题是即使模块停止后,路由和处理程序仍然存在于 BB 历史中

    所以我的 hack,我的意思是解决方案 :)

    我们需要一些路由器来监视 URL 变化和加载模块,让它成为ModuleManager

    define(
        [
            'application'
        ],
        function(App) {
    
            App.module('ModuleManager', function(ModuleManager, Application, Backbone, Marionette) {
    
                var currentPageModule = false,
                    stopModule = function(name) {
                        name && Application.module(name).stop();
                    },
                    startModule = function(name) {
                        Application.module(name).start();
                    };
    
                ModuleManager.getModuleNameByUrl = function() {
                    var name = Backbone.history.getHash().split('/')[0];
                    return name ? (name.charAt(0).toUpperCase() + name.slice(1)) : 'Home'
                };
    
                ModuleManager.switchModule = function(name) {
    
                    if (!name) return;
    
                    stopModule(currentPageModule);
                    startModule(name);
    
                    currentPageModule = name;
    
                };
    
                ModuleManager.requireModule = function(name, callback) {
                    require(['apps/pages/' + name + '/index'],
                        callback.bind(this),
                        function() {
                            require(['apps/pages/404/index'], function() {
                                ModuleManager.switchModule('404');
                            })
                        }
                    );
                };
                /*
                * this is key feature - we should catch all routes
                * and load module by url path
                */
                ModuleManager.FrontRouter = Marionette.AppRouter.extend({
    
                    routes: {
                        '*any': 'loadModule'
                    },
    
                    loadModule: function() {
                        var name = ModuleManager.getModuleNameByUrl();
    
                        ModuleManager.requireModule(name, function() {
                            ModuleManager.switchModule(name);
                        })
                    }
                });
    
                ModuleManager.addInitializer(function() {
                    new ModuleManager.FrontRouter;
                });
    
                ModuleManager.addFinalizer(function() {
                    delete ModuleManager.FrontRouter;
                });
            });
        }
    );
    

    太好了,这将加载带有内部路由的模块。但是我们会遇到另一个问题 - 在子模块启动时我们初始化它的路由器,但我们已经路由到子路由器初始化的页面并且 URL 仍然相同。所以在下一次导航之前不会调用子路由器。所以我们需要特殊的路由器来处理这种情况。这是'ModuleRouter':

    App.ModuleRouter = Marionette.AppRouter.extend({
    
        forceInvokeRouteHandler: function(routeRegexp, routeStr, callback) {
            if(routeRegexp.test(Backbone.history.getHash()) ) {
                this.execute(callback, this._extractParameters(routeRegexp, routeStr));
            }
        },
    
        route: function(route, name, callback) {
    
            var routeString = route,
                router = this;
    
            if (!_.isRegExp(route)) route = this._routeToRegExp(route);
    
            if (_.isFunction(name)) {
                callback = name;
                name = '';
            }
    
            if (!callback) callback = this[name];
    
            // проблема - RouterManager уже стригерил событие route, загрузил саб-роутер.
            // при создании саб роутера его колбэк уже вызван не будет, поскольку адрес страницы не изменился
            // при добавлении роутов используется нативный ВВ route - который вещает колбэк на указанный фрагмент
            // расширяем - если мы уже находимся на фрагменте на который устанавливается колбэк - принудительно вызвать
            // выполнение обработчика совпадения фрагмента
    
            /*
            * PROBLEM : AppsManager already triggered 'route' and page fragments still same,
            * so module router will not be checked on URL matching.
            *
            * SOLUTION : updated route method, add route to Backbone.history as usual, but also check if current page
            * fragment match any appRoute and call controller callback
            * */
    
            this.forceInvokeRouteHandler(route, routeString, callback);
    
                Backbone.history.route(route, function(fragment) {
                    var args = router._extractParameters(route, fragment);
    
                    router.execute(callback, args);
                    router.trigger.apply(router, ['route:' + name].concat(args));
                    router.trigger('route', name, args);
    
                    Backbone.history.trigger('route', router, name, args);
                });
    
                return this;
            },
            // implementation destroy method removing own handlers anr routes from backbone history
            destroy: function() {
    
                var args = Array.prototype.slice.call(arguments),
                    routKeys = _.keys(this.appRoutes).map(function(route) {
                        return this._routeToRegExp(route).toString();
                    }.bind(this));
    
                Backbone.history.handlers = Backbone.history.handlers.reduce(function(memo, handler) {
                    _.indexOf(routKeys, handler.route.toString()) < 0  && memo.push(handler)
    
                    return memo;
                }, []);
    
                Marionette.triggerMethod.apply(this, ['before:destroy'].concat(args));
                Marionette.triggerMethod.apply(this, ['destroy'].concat(args));
    
                this.stopListening();
                this.off();
    
                return this;
        }
    })
    

    请随意填写提问或聊天,我想有些地方可能需要澄清。

    【讨论】:

    • 嗨@Evgeniy 你甚至无法想象你是如何帮助我的。我正在考虑一种类似的方法,并最终根据您提供的代码找到了一个可行的解决方案。非常感谢你。这是一个很好的、不言自明的例子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-25
    • 2014-10-29
    • 2012-06-28
    • 2015-10-23
    • 1970-01-01
    • 2020-10-10
    • 2019-01-01
    相关资源
    最近更新 更多