Marionette:根据路由regexp启动和停止模块

时间:2014-12-26 11:16:23

标签: javascript backbone.js coffeescript routing marionette

我实现的应用程序在顶级应用程序模块中有两个独立的子模块。 我有一个管理模块,其中包含以/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没有填充相应的布局。请注意,在任何子模块之前都需要包含顶级应用程序定义的文件(我猜这就是覆盖路由的原因)。我也明白,在满足第一个匹配时,骨干路由器将调用路由回调。

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

1 个答案:

答案 0 :(得分:1)

有接近的任务要解决,还没有找到任何现有的解决方案,所以这里是small stub - project我已经创建了

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

1)异步路由。像rootRouter这样的东西应该加载app模块而moduleRouter应该调用控制器方法

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

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

我们需要一些会看到网址更改和加载模块的路由器,让它为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上的页面仍然相同。因此,直到下一次导航才会调用子路由器。所以我们需要特殊的路由器,这将处理这种情况。这是'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;
    }
})

请填写免费提问或聊天,我想有一点可能需要澄清。