javascript - Marionette:基于路由正则表达式启动和停止模块

标签 javascript backbone.js coffeescript routes 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 没有填充相应的布局。请注意,在任何子模块之前都需要包含顶级应用程序定义的文件(我猜这就是路由被覆盖的原因)。我还了解到,当满足第一个匹配项时, Backbone 路由器将调用路由回调。

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

最佳答案

有关闭任务需要解决,尚未找到任何现有解决方案,因此这里是 small stub - project我已经创建了

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

1) 异步路由。像 rootRouter 这样的东西应该加载应用程序模块,而 moduleRouter 应该调用 Controller 方法

2) 在模块停止时清除 Backbone 历史处理程序。问题是即使模块停止后,路由和处理程序仍然存在于 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;
            });
        });
    }
);

太棒了,这将加载带有内部路由的模块。但是我们会遇到另一个问题 - 在子模块启动时我们初始化其路由器,但我们已经路由到子路由器初始化上的页面并且 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;
    }
})

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

关于javascript - Marionette:基于路由正则表达式启动和停止模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27656370/

相关文章:

node.js - 使用 coffeescript 进行 Mocha 测试的 Istanbul 尔代码覆盖率

javascript - CoffeeScript:抑制 "ReferenceError"

javascript - 如何禁用提交按钮,直到在特定字段中通过验证

javascript - 我的内容在媒体查询中漂浮在容器后面

backbone.js - 如何获取和过滤数据

javascript - backbone remove view 删除 el

Backbone.js:如何对父 View 和 subview 执行垃圾收集

javascript - CoffeeScript 在 `v` 中创建一个 `for v in values` 的全局变量,导致事件引用错误

javascript - 对于 JavaScript 数组和对象,我应该使用哪个 for 循环?

javascript - Android - 当互联网中断时继续使用 HTML5 javascript 播放音频 - 获取、blob