木偶:根据路由正则表达式启动和停止模块

Marionette: Starting and stopping modules based on route regexp

本文关键字:模块 启动 正则表达式 路由 木偶      更新时间:2023-09-26

我正在实现一个应用程序,该应用程序在顶级应用程序模块中有两个单独的子模块。我有一个管理模块,其中包含以/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的回调将失败并出错。这是因为管理应用程序不会启动,并且主区域没有填充相应的布局。请注意,在任何子模块之前都需要包含顶级应用程序定义的文件(我想这就是路由被覆盖的原因)。我也明白,当遇到第一个匹配项时,骨干路由器会调用路由回调。

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

有接近的任务要解决,没有找到任何现有的解决方案,所以这里有一个小存根 - 我创建的项目

要解决此类任务,需要解决的问题很少:

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

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

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

我们需要一些路由器来监视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;
            });
        });
    }
);

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

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;
    }
})

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