我正在实现一个应用程序,该应用程序在顶级应用程序模块中有两个独立的子模块。
我有一个管理模块,其路由约定以 /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/