I'm trying to conditionally block a route from being accessed. I think this can be done with guardRoute: http://durandaljs.com/documentation/Router/
function guardRoute(routeInfo, params, instance) : object - Before any route is activated, the guardRoute function is called. You can plug into this function to add custom logic to allow, deny or redirect based on the requested route. To allow, return true. To deny, return false. To redirect, return a string with the hash or url. You may also return a promise for any of these values.
I'm not sure how to specify which route should be accessed though, or how to re-route to another view if I need to deny access. Can someone post an example of its use in this manner?
You should use guardRoute before activating the router e.g. in shell.js.
The example is taken from a Durandal 2.0 alpha site. AFAIK guardRoute hasn't changed from 1.2, but setting a breakpoint will allow you to figure out what arguments are passed in for 1.2.
As a general rule, return true to allow navigation, false to prevent it and a hash or url value to redirect.
define(['plugins/router'], function (router) {
// Redirecting from / to first route in route.map
router.guardRoute = function(routeInfo, params, instance){
if (params.fragment === ''){
return routeInfo.router.routes[0].hash;
}
return true;
};
...
return {
...
router: router,
activate: function () {
router.map([
---
]);
return router.activate();
}
};
});
I am a newbie to durandaljs 2.0 having only used it for a couple of weeks so this was one of my first stumbling blocks. see my code below
router.guardRoute = function (routeInfo, params, instance) {
var insecureRoutes = ['login', 'terms','signup','password'];
if ($.inArray(params.fragment, insecureRoutes) || mymodule.isAuthenticated()) {
return true;
} else {
return 'login/' + params.fragment;
}
};
I also define a route for the login page like this
{ route: 'login/(:returnroute)', moduleId: 'viewmodels/login', nav: false },
This allows you to optionally specify a return rout so that after login you can redirect the user to where ever it is they were initially trying to go to. I hope it helps someone
Related
I tried to find how to change the default page path (when you access the server on the "/" path) but couldn't find anything. I have a structure in the pages directory in the form index/index.jsx and I want this page to be returned by default when accessing the server. I did not find a similar question on this forum, maybe someone will need your help, except me.
The way pages structure works in Next.js is very opiniated. If you need to send a file from a different pages structure when accessing / you would need to configure your own little express server.
See this link for official doc.
You would then have something like :
// Some code
if (pathname === '/') {
app.render(req, res, '/index', query) // Or /index/index.jsx
}
// Some code
Also if you do not want to create your own express server, you could have an index.js that redirects to your index page with something like this :
import Router from 'next/router'
const Index = () => null
Index.getInitialProps = async ({ res }) => {
if (res) {
res.writeHead(302, {
Location: `/index`
})
res.end()
}
else
Router.push(`/index`)
return {}
}
export default Index
It's evident how to provide route-specific data, i.e. through the use of a controller:
PostController = RouteController.extend({
layoutTemplate: 'PostLayout',
template: 'Post',
waitOn: function () { return Meteor.subscribe('post', this.params._id); },
data: function () { return Posts.findOne({_id: this.params._id}) },
action: function () {
this.render();
}
});
But how does one provide data for the application in general? In the case that every route needs to be subscribed to the same subset of information, so that the pub/sub doesn't need to be re-done on every route change. Thanks!
It sounds to me like you are looking for a completely general publication/subscription scheme so that you do not have to define the waitOn/data option combination for every single route or route controller that you define. In that case, you can simply publish a given set of data on the server like so:
Meteor.publish('someData', function() {
return SomeDataCollection.find({});
});
and subscribe to that set of data on the client like so:
Meteor.subscribe('someData');
With this publication/subscription pair setup, you will have access to the data provided in all routes. You just have to make sure that you check for non-existent data in your code to handle the first load of any given template when the data has not been loaded on the client yet. In this manner, you would never have to actually define a the waitOn and data options for any route or route controller.
If you would like to utilize Iron Router in a different way than through route controllers, you also have the option of waiting on one/many subscriptions globally by using the Router.configure({}); function. To use the example above:
Router.configure({
waitOn: function() {
return Meteor.subscribe('someData');
}
});
For information about this route option and all of the other options that you have available at a global level, check this out.
For the URL to which a route applies I have a part defined in settings.json, like this
baseUrl: '/private'
My settings are published and accessible through the collections 'Settings' (on the client). So I tried the following:
Meteor.subscribe('settings');
Deps.autorun(function () {
var settings = Settings.findOne():
if (settings) {
Router.map(function () {
this.route('project', {
path: settings.baseUrl + '/:projectId,
controller: 'ProjectController'
});
});
}
});
The problem is that during initialisation the data is not yet on the client available, so I have to wait until the data is present. So far this approach doesn't work (yet). But before spending many hours I was wondering if someone has done this before or can tell me if this is the right approach ?
Updated answer:
I published solution in repository : https://github.com/parhelium/meteor-so-inject-data-to-html
. Test it by opening url : localhost:3000/test
In this case FastRender package is useless as it injects collection data in the end of head tag -> line 63.
Inject-Initial package injects data in the beginning of head tag -> line 106.
Needed packages:
mrt add iron-router
mrt add inject-initial
Source code:
Settings = new Meteor.Collection("settings");
if (Meteor.isClient) {
var settings = Injected.obj('settings');
console.log(settings);
Router.map(function () {
this.route('postShow', {
path: '/'+settings.path,
action: function () {
console.log("dynamic route !");
}
});
});
}
if (Meteor.isServer){
if(Settings.find().count() == 0){
Settings.insert({path:"test",data:"null"});
}
Inject.obj('settings', Settings.findOne());
}
Read about security in the bottom of the page : https://github.com/gadicc/meteor-inject-initial/
OLD ANSWER :
Below solution won't work in this specific case as FastRender injects data in the end of head tag. Because of that Routes are being initialized before injected data is present.
It will work when data from Settings collection will be sent together with html.
You can do that using package FastRender.
Create file server/router.js :
FastRender.onAllRoutes(function(path) {
// don't subscribe if client is downloading resources
if(/(css|js|html|map)/.test(path)) {
return;
}
this.subscribe('settings');
});
Create also publish function:
Meteor.publish('settings', function () {
return Settings.find({});
});
The above code means that if user open any url of your app then client will subscribe to "settings" publication and data will be injected on the server into html and available for client immediately.
I use this approach to be able to connect many different domains to meteor app and accordingly sent proper data.
I have a simple list and details view using two collections.
When I navigate back to the list view Meteor removes the single document added to the details collection and undoes the change to the other collection.
I want this data to remain so the client doesn't have to keep reloading it...
Both the 'league' and the 'standings' subscriptions are 'undone' on navigation back to the the root. The league and leagues route both use the 'weeks' Mongo collection. When navigating to a league detail I add to the single document. Navigation to the detail works fine ... its when I navigate back to the list that I loose the collection data.
I need all this data 'cached' and am obviously not going about it correctly....
Router.map(function () {
this.route('leagueList', {
path: '/'
});
this.route('league', {
path: '/league/:league',
template: 'standings',
waitOn: function () {
console.log(this.params.league);
return [Meteor.subscribe('league', this.params.league),
Meteor.subscribe('standings', this.params.league) ];
},
data: function () {
return {theLeague: Leagues.findOne({league: this.params.league}),
theStandings: Standings.findOne()};
}
});
});
Server:
Meteor.publish('leagues', function(){
console.log('all league names sent');
return Leagues.find({}, {fields: {weeks: 0}});
});
Meteor.publish('league', function(theLeague){
console.log('sending weeks detail for: ' + theLeague);
return Leagues.find({league: theLeague});
});
Meteor.publish('standings', function(theLeague){
console.log('standings: ' + theLeague);
var file = Leagues.findOne({league: theLeague}).weeks[0].file;
return Standings.find({file: file});
});
client:
Leagues = new Meteor.Collection('weeks');
Standings = new Meteor.Collection('details');
Meteor.subscribe('leagues');
There's work in progress in iron router to allow (and optimize) this (not immediately stopping the subscriptions when you route to another page). See the sub-manager branch.
But if you create the subscription apart from the waitOn call, I think the subscription is never stopped. For example, in the code below, the routes a and c will wait for the initialData to be received (which will be fetched directly when the user loads the page (even if it uses route b)), and the subscription for it will never stop, even if you leave, for example, route a. However, I don't think you can use this approach if you need to use some parameters in the route (you can probably fix something with setInterval, but it will be ugly).
var handleToDataIMostlyNeed = Meteor.subscribe('initialData')
Router.map(function(){
this.route('a', {
waitOn: function(){
return handleToDataIMostlyNeed
}
})
this.route('b', {
waitOn: function(){
return [] // Wait for nothing.
}
})
this.route('c', {
waitOn: function(){
return handleToDataIMostlyNeed
}
})
})
I'm trying to use params on my main route. But in fact the params are not set and they are used in the path :
Router.map(function funcClientRouterMap(){
this.route('home', {
path: '/:_redirect?',
action: function funcClientRouterMapAction(){
console.log(this.path, this.params);
}
})
});
now if i try manual redirection here is what i get :
Router.go('home'); // it redirects on / => ok
Router.go('home', {_redirect: test}); // this.path = /test, and this.params is empty
How can i use _redirect like a params and not a route ?
Thanks
Router.go accepts a path as its first argument (per the docs). So if you were trying to programmatically cause the same result as the user going to /redirectMeSomewhere, you just use:
Router.go('/redirectMeSomewhere');
And this.params._redirect should be 'redirectMeSomewhere'.
Note that like #apendua implies, if you have other routes it could cause chaos to have a route defined as /:anything, because the other routes may never get triggered. If the above doesn't do the trick, try commenting out all your other routes to see if that changes anything.