Skip to content

Commit

Permalink
Merge pull request #329 from mquandalle/subpath
Browse files Browse the repository at this point in the history
Support prefixed paths
  • Loading branch information
arunoda committed Oct 6, 2015
2 parents cc99fbc + cafa42e commit 62f7f4a
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 25 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ It exposes a great API for changing the URL and reactively getting data from the
* [Subscription Management](#subscription-management)
* [IE9 Support](#ie9-support)
* [Hashbang URLs](#hashbang-urls)
* [Prefixed paths](#prefixed-paths)
* [Addons](#addons)
* [Difference with Iron Router](#difference-with-iron-router)
* [Migrating into 2.0](#migrating-into-20)
Expand Down Expand Up @@ -637,6 +638,9 @@ WhenEverYourAppIsReady(function() {
});
~~~
## Prefixed paths
In cases you wish to run multiple web application on the same domain name, you’ll probably want to serve your particular meteor application under a sub-path (eg `example.com/myapp`). In this case simply include the path prefix in the meteor `ROOT_URL` environment variable and FlowRouter will handle it transparently without any additional configuration.
## Addons
Expand Down
56 changes: 37 additions & 19 deletions client/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Router = function () {
// holds onRoute callbacks
this._onRouteCallbacks = [];

// if _askedToWait is true. We don't automatically start the router
// if _askedToWait is true. We don't automatically start the router
// in Meteor.startup callback. (see client/_init.js)
// Instead user need to call `.initialize()
this._askedToWait = false;
Expand All @@ -27,11 +27,16 @@ Router = function () {
this.notFound = this.notfound = null;
// indicate it's okay (or not okay) to run the tracker
// when doing subscriptions
// using a number and increment it help us to support FlowRouter.go()
// using a number and increment it help us to support FlowRouter.go()
// and legitimate reruns inside tracker on the same event loop.
// this is a solution for #145
this.safeToRun = 0;

// Meteor exposes to the client the path prefix that was defined using the
// ROOT_URL environement variable on the server using the global runtime
// configuration. See #315.
this._basePath = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '';

// this is a chain contains a list of old routes
// most of the time, there is only one old route
// but when it's the time for a trigger redirect we've a chain
Expand Down Expand Up @@ -73,9 +78,9 @@ Router.prototype.route = function(pathDef, options, group) {
self._oldRouteChain.push(oldRoute);

var queryParams = self._qs.parse(context.querystring);
// _qs.parse() gives us a object without prototypes,
// _qs.parse() gives us a object without prototypes,
// created with Object.create(null)
// Meteor's check doesn't play nice with it.
// Meteor's check doesn't play nice with it.
// So, we need to fix it by cloning it.
// see more: https://github.com/meteorhacks/flow-router/issues/164
queryParams = JSON.parse(JSON.stringify(queryParams));
Expand All @@ -98,9 +103,9 @@ Router.prototype.route = function(pathDef, options, group) {

var triggers = self._triggersEnter.concat(route._triggersEnter);
Triggers.runTriggers(
triggers,
self._current,
self._redirectFn,
triggers,
self._current,
self._redirectFn,
afterAllTriggersRan
);
};
Expand Down Expand Up @@ -136,9 +141,16 @@ Router.prototype.path = function(pathDef, fields, queryParams) {
pathDef = this._routesMap[pathDef].pathDef;
}

var path = "";

// Prefix the path with the router global prefix
if (this._basePath) {
path += "/" + this._basePath + "/";
}

fields = fields || {};
var regExp = /(:[\w\(\)\\\+\*\.\?]+)+/g;
var path = pathDef.replace(regExp, function(key) {
path += pathDef.replace(regExp, function(key) {
var firstRegexpChar = key.indexOf("(");
// get the content behind : and (\\d+/)
key = key.substring(1, (firstRegexpChar > 0)? firstRegexpChar: undefined);
Expand All @@ -147,12 +159,13 @@ Router.prototype.path = function(pathDef, fields, queryParams) {

// this is to allow page js to keep the custom characters as it is
// we need to encode 2 times otherwise "/" char does not work properly
// So, in that case, when I includes "/" it will think it's a part of the
// So, in that case, when I includes "/" it will think it's a part of the
// route. encoding 2times fixes it
return encodeURIComponent(encodeURIComponent(fields[key] || ""));
});

path = path.replace(/\/\/+/g, "/"); // Replace multiple slashes with single slash
// Replace multiple slashes with single slash
path = path.replace(/\/\/+/g, "/");

// remove trailing slash
// but keep the root slash if it's the only one
Expand All @@ -173,7 +186,7 @@ Router.prototype.path = function(pathDef, fields, queryParams) {

Router.prototype.go = function(pathDef, fields, queryParams) {
var path = this.path(pathDef, fields, queryParams);

var useReplaceState = this.env.replaceState.get();
if(useReplaceState) {
this._page.replace(path);
Expand Down Expand Up @@ -244,7 +257,7 @@ Router.prototype.current = function() {

// Implementing Reactive APIs
var reactiveApis = [
'getParam', 'getQueryParam',
'getParam', 'getQueryParam',
'getRouteName', 'watchPathChange'
];
reactiveApis.forEach(function(api) {
Expand Down Expand Up @@ -353,11 +366,11 @@ Router.prototype.initialize = function(options) {
// by overriding page.js`s "show" method.
// Why?
// It is impossible to bypass exit triggers,
// becuase they execute before the handler and
// because they execute before the handler and
// can not know what the next path is, inside exit trigger.
//
// we need override both show, replace to make this work
// since we use redirect when we are talking about withReplaceState
// since we use redirect when we are talking about withReplaceState
_.each(['show', 'replace'], function(fnName) {
var original = self._page[fnName];
self._page[fnName] = function(path, state, dispatch, push) {
Expand All @@ -374,7 +387,12 @@ Router.prototype.initialize = function(options) {
// in unpredicatable manner. See #168
// this is the default behaviour and we need keep it like that
// we are doing a hack. see .path()
this._page({decodeURLComponents: true, hashbang: !!options.hashbang});
this._page.base(this._basePath);
this._page({
decodeURLComponents: true,
hashbang: !!options.hashbang
});

this._initialized = true;
};

Expand Down Expand Up @@ -433,7 +451,7 @@ Router.prototype._buildTracker = function() {
if(isRouteChange) {
// We need to trigger that route (definition itself) has changed.
// So, we need to re-run all the register callbacks to current route
// This is pretty important, otherwise tracker
// This is pretty important, otherwise tracker
// can't identify new route's items

// We also need to afterFlush, otherwise this will re-run
Expand Down Expand Up @@ -478,9 +496,9 @@ Router.prototype._invalidateTracker = function() {
// XXX: fix this with a proper solution by removing subscription mgt.
// from the router. Then we don't need to run invalidate using a tracker

// this happens when we are trying to invoke a route change
// this happens when we are trying to invoke a route change
// with inside a route chnage. (eg:- Template.onCreated)
// Since we use page.js and tracker, we don't have much control
// Since we use page.js and tracker, we don't have much control
// over this process.
// only solution is to defer route execution.

Expand Down Expand Up @@ -552,7 +570,7 @@ Router.prototype.onRouteRegister = function(cb) {
Router.prototype._triggerRouteRegister = function(currentRoute) {
// We should only need to send a safe set of fields on the route
// object.
// This is not to hide what's inside the route object, but to show
// This is not to hide what's inside the route object, but to show
// these are the public APIs
var routePublicApi = _.pick(currentRoute, 'name', 'pathDef', 'path');
var omittingOptionFields = [
Expand Down
67 changes: 61 additions & 6 deletions test/client/router.core.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ Tinytest.addAsync('Client - Router - notFound', function (test, done) {
}, 50);
});

Tinytest.addAsync('Client - Router - withReplaceState - enabled',
Tinytest.addAsync('Client - Router - withReplaceState - enabled',
function (test, done) {
var pathDef = "/" + Random.id() + "/:id";
var originalRedirect = FlowRouter._page.replace;
Expand All @@ -362,7 +362,7 @@ function (test, done) {
test.equal(params.id, "awesome");
test.equal(callCount, 1);
FlowRouter._page.replace = originalRedirect;
// We don't use Meteor.defer here since it carries
// We don't use Meteor.defer here since it carries
// Meteor.Environment vars too
// Which breaks our test below
setTimeout(done, 0);
Expand All @@ -374,7 +374,7 @@ function (test, done) {
});
});

Tinytest.addAsync('Client - Router - withReplaceState - disabled',
Tinytest.addAsync('Client - Router - withReplaceState - disabled',
function (test, done) {
var pathDef = "/" + Random.id() + "/:id";
var originalRedirect = FlowRouter._page.replace;
Expand Down Expand Up @@ -537,7 +537,7 @@ function (test, next) {
});

Tinytest.addAsync(
'Client - Router - wait - before initialize',
'Client - Router - wait - before initialize',
function(test, done) {
FlowRouter._initialized = false;
FlowRouter.wait();
Expand All @@ -549,7 +549,7 @@ function(test, done) {
});

Tinytest.addAsync(
'Client - Router - wait - after initialized',
'Client - Router - wait - after initialized',
function(test, done) {
try {
FlowRouter.wait();
Expand All @@ -560,7 +560,7 @@ function(test, done) {
});

Tinytest.addAsync(
'Client - Router - initialize - after initialized',
'Client - Router - initialize - after initialized',
function(test, done) {
try {
FlowRouter.initialize();
Expand All @@ -570,6 +570,61 @@ function(test, done) {
}
});

Tinytest.addAsync(
'Client - Router - base path - url updated',
function(test, done) {
var simulatedBasePath = '/flow';
var rand = Random.id();
FlowRouter.route('/' + rand, { action: function() {} });

setBasePath(simulatedBasePath);
FlowRouter.go('/' + rand);
setTimeout(function() {
test.equal(location.pathname, simulatedBasePath + '/' + rand);
resetBasePath();
done();
}, 100);
});

Tinytest.addAsync(
'Client - Router - base path - route action called',
function(test, done) {
var simulatedBasePath = '/flow';
var rand = Random.id();
FlowRouter.route('/' + rand, {
action: function() {
resetBasePath();
done();
}
});

setBasePath(simulatedBasePath);
FlowRouter.go('/' + rand);
});

Tinytest.add(
'Client - Router - base path - path generation',
function(test, done) {
_.each(['/flow', '/flow/', 'flow/', 'flow'], function(simulatedBasePath) {
var rand = Random.id();
setBasePath(simulatedBasePath);
test.equal(FlowRouter.path('/' + rand), '/flow/' + rand);
});
resetBasePath();
});


function setBasePath(path) {
FlowRouter._initialized = false;
FlowRouter._basePath = path;
FlowRouter.initialize();
}

var defaultBasePath = FlowRouter._basePath;
function resetBasePath() {
setBasePath(defaultBasePath);
}

function bind(obj, method) {
return function() {
obj[method].apply(obj, arguments);
Expand Down

0 comments on commit 62f7f4a

Please sign in to comment.