router5 / router5 Goto Github PK
View Code? Open in Web Editor NEWFlexible and powerful universal routing solution
Home Page: https://router5.js.org
License: MIT License
Flexible and powerful universal routing solution
Home Page: https://router5.js.org
License: MIT License
I thought it was a bit strange to use scripts
as the main source folder instead of just src
.
This seems to be common on all of the router5 repos, however scripts
is usually used for build scripts in a project. It might be just a preference but I feel like as it's still early days and router5 has such potential, maybe it would be easy to fix it up now.
Let me explain what I mean.
For example, we have a non-matching route http://localhost:8080/#/non-existent
. If I copy-paste it inside a browser address bar or try to change it from existing route in address bar, then the page is navigated to the defaultRoute, but I don't see transition error ROUTE_NOT_FOUND being thrown.
If I make defaultRoute undefined, then I can see ROUTE_NOT_FOUND error. Is that intentional?
Router5 contains functions which can be either sync or async. This is not only strange (what occurs on error if callback is not provided? only source code can answer...), but also leads to errors. There are a plenty of them already.
navigate(name, params = {}, opts = {}, done) { // syntax error!
if (!this.started) {
done(constants.ROUTER_NOT_STARTED) // forgot to wrap in if (done)
return
}
...
if (!path) throw new Error(`Could not find route "${name}"`) // why this error not passed to done?!
}
I think this is overcomplicated and we need to drop either sync or async versions in each case.
What errors can occur at the transition time btw?
I see in code it's something about cancelation but is this really an error to bother?
Can't we just ignore some corner cases and make navigation sync-only?
Hello!
I have next router:
const routes = [
{ name: 'search', path: '/search?:query'},
{ name: 'detail', path: '/detail/:id'},
];
const router = new Router5(routes, {
useHash: true,
defaultRoute: 'search',
})
.usePlugin(historyPlugin())
.usePlugin(listenersPlugin())
.start();
When I make navigation like this:
router.navigate('detail', {id: id++});
I can back to previous id. But when I make navigation like this:
router.navigate('search', {query: query++});
I can't back to previouse query value. Why?
It looks like issue #11 is back (at least on Chrome 44 and Firefox 40). When pressing the back button, I can see the forward button change quickly from enabled to disabled, and in practice it is never usable.
Router5 0.4.3 seems to be fine, but the problem has reappeared in 0.5.0. Verified on "Router5 with React" example, so the problem seems to be in the library.
When I navigating from example.com/#/ to example.com/#/mailbox and then click browser back button everything works as expected. Url has changed to example.com/#/ and router noticed it. However from this point I should be able to click forward button and go to example.com/#/mailbox again. Unfortunately i'm not able to do that. Forward button is disabled.
http://router5.github.io/docs/navigation.html
Invoking the .start([startPathOrState ,] done) function will:
Navigate to the default route if the current URL does not match an existing route, or if the matched route cannot be activated
Start listening to popstate events (triggered by back and forward buttons, and a manual change in the URL bar)
Enable navigation
What does startPathOrState
change in this description?
Duplicated 'path' error was thrown when I tested the code below.
new Router5()
.addNode('users', '/users') // #/users
.addNode('groups', '/groups') // #/groups
.addNode('groups.users', '/users'); // #/groups/users
I think this logic RouteNode.add()
for checking duplication may target the only nodes on the same level.
This is the error message.
Error: Path "/users" is already defined in route node
at RouteNode.add (http://172.21.81.201:8081/node_modules/router5/dist/amd/router5.js:717:27)
at RouteNode.addNode (http://172.21.81.201:8081/node_modules/router5/dist/amd/router5.js:766:22)
at Router5.addNode (http://172.21.81.201:8081/node_modules/router5/dist/amd/router5.js:1103:31)
at Router.start (http://172.21.81.201:8081/src/core/router.js:67:14)
at http://172.21.81.201:8081/node_modules/async/dist/async.min.js:1:2339
at P.forEachOf.P.eachOf (http://172.21.81.201:8081/node_modules/async/dist/async.min.js:1:5585)
at L (http://172.21.81.201:8081/node_modules/async/dist/async.min.js:1:2319)
at Object.P.parallel (http://172.21.81.201:8081/node_modules/async/dist/async.min.js:1:9044)
at App._startPhase (http://172.21.81.201:8081/src/app.js:96:15)
at r (http://172.21.81.201:8081/node_modules/async/dist/async.min.js:1:9224)
Currently, my router resembles the following:
const router = new Router5( [], {
useHash: true,
trailingSlash: true,
defaultRouter: 'home',
} )
.addNode( 'home', '/' )
.addNode( 'login', '/login' )
.addNode( 'signup', '/signup' )
.addNode( 'profile', '/profile', authenticatedRoute )
.addNode( 'users', '/users', authenticatedRoute )
.addNode( 'user', '/user/:id', authenticatedRoute )
.addNode( '404', '/404' );
router.addRouteListener( 'home', ( toState, fromState ) => mountPage( 'app' ) );
router.addRouteListener( 'login', ( toState, fromState ) => mountPage( 'login' ) );
router.addRouteListener( 'signup', ( toState, fromState ) => mountPage( 'signup' ) );
router.addRouteListener( 'users', ( toState, fromState ) => mountPage( 'list-users' ) );
router.addRouteListener( 'user', ( toState, fromState ) => mountPage( 'user' ) );
router.addRouteListener( 'profile', ( toState, fromState ) => mountPage( 'user' ) );
router.addRouteListener( '404', ( toState, fromState ) => mountPage( '404' ) );
The route listener portion feels cumbersome. I thought that RouteNode would be the right way to go about it, but it doesn't have this capability.
What'd I'd like to do is be able to is sort of combine both RouteNode and the POJO syntaxes together to make it more powerful:
const router = Router5( [
routeNode( {
name: 'home',
path: '/',
canActivate: ( toState, fromState ) => authenticatedRoute,
onRoute: ( toState, fromState ) => {
// Equates to .addRouteListener
},
onNode: ( toState, fromState ) => {
// Equates to .addNodeListener
},
onNavigate: ( toState, fromState ) => {
// Equates to .addListener
},
} ),
] );
If I specify a base, I can't seem to match routes properly.
If I don't specify a base, everything works fine. If I want to specify a base, then I can't get routes to match at all.
For example, here are some routes:
routes: [
{ name: 'home', path: '/' },
{ name: 'foo', path: '/foo' }
],
If I want to use a base
of '/admin'
(i've also tried location.origin + '/admin'
) then the home route doesn't match at all when location.pathname
is '/admin'
, and if i try to set the home path to '/admin'
then router5 ends up redirecting me to '/admin/admin'
.
What am I doing wrong? Is this a bug?
console.log(location.href); // http://localhost:3000/admin
console.log(location.origin + location.pathname); // http://localhost:3000/admin
console.log(router.matchPath(location.href)); // null
console.log(router.matchPath(location.origin)); // null
console.log(router.matchPath(location.pathname)); // null
console.log(router.matchPath(location.origin + location.pathname)); // null
console.log(router.matchPath(location.origin + '/')); // null
console.log(router.matchPath(location.origin + '/admin')); // null
console.log(router.matchPath(location.origin + '/admin/')); // null
console.log(router.matchPath(location.origin + '/admin/foo')); // null
console.log(router.matchPath(location.origin + '/foo')); // null
console.log(router.matchPath('/')); // Object {name: "home", params: Object, path: "/"}
console.log(router.matchPath('/admin')); // null
console.log(router.matchPath('/admin/')); // null
console.log(router.matchPath('/admin/foo')); // null
console.log(router.matchPath('/foo')); // Object {name: "foo", params: Object, path: "/foo"}
I have problem with url containing parameters. For example if I added a node with path "/something/:id" and in application I navigated to this particular path - everything works. If from this point (http://example.com/#/something/321") i will try to reload app, router will init default route (which is in my case 404 page). I guess this is not proper behavior. From my point of view, url http://example.com/#/something/321" should match to "/something/:id".
It would be good if users could opt in to using Google's AJAX crawling mechanism. See https://developers.google.com/webmasters/ajax-crawling/docs/getting-started
This would require allowing urls beginning with #! instead of the current #. Probably the best way would be to implement a prefix
option.
Hi,
I created a test based in your example: http://router5.github.io/docs/universal-applications.html
But the router5 is not working, it's causing reload in page!!
Am I doing something wrong?
https://github.com/lagden/router5-playground
In my test, I created workaround.js
https://github.com/lagden/router5-playground/blob/master/public/workaround.js#L6-L16
I put event.preventDefault()
for all <a>
and call router.navigate
. In this way the router5
behaves as it should, but I believe the router5-history
should make this work.
I'm running into some unexpected problems when defining nested paths. I was hoping you could set me on the right path (no pun intended ๐). I have created a small example to demonstrate.
var Router5 = require('router5').default;
var router = new Router5();
router
.addNode('personList', '/persons/')
.addNode('personDetail', '/persons/:personId');
var match = router.matchPath('/persons/jwoudenberg');
console.log(match); //=> null
In the example above, the path /persons/jwoudenberg
does not result in a match. I would expect it to match the personDetail
route.
After looking through the code I see what happens. The path is a partial match with the personList
route. Router5 looks to complete the match by going through the child router of personList
, but none exist, so it gives up. Removing the personList
route makes /persons/jwoudenberg
resolve correctly.
I can imagine I could get it to work by defining personDetail
as a child of personList
. I was kind of hoping though that I could get around using this extra functionality, which I otherwise don't need. Certainly for new users, I think this can be confusing (as it was for me ๐).
Some possible solutions I see:
I'm looking forward to hearing your thoughts!
Disclaimer: I know that this kind of request is unpopular. I'm not trying to annoy anyone.
I'm the maintainer of an isomorphic starterkit for React. Some time ago I seriously considered renaming my project to "universal starterkit". However I then came across the following article: https://medium.com/@ghengeveld/isomorphism-vs-universal-javascript-4b47fb481beb Now I strongly think that even though Javascript code can be universal, we should keep calling the applications that do run similar, but divergent code between server and client, isomorphic.
Is it possible to use router5 isomorphically/universally? When using it server side I run into problems as there is no window object defined, error:
/project-path/node_modules/router5/dist/commonjs/router5.js:70
this.base = this.options.base || window.location.pathname.replace(
^
ReferenceError: window is not defined
I have a simple path like page/action/#a=1&b=2&c=3 ...
and I really don't know all parameters names. How I can receive them in object?
{
a: 1,
b: 2,
c: 3,
//...many other parameters
}
Currently in Router5 hashes are nuked off urls once the router starts and successfully transitions. I was thinking that hashes, when hash-routing is off, should be treated as special params and then re-added to the URL when building it if they exists.
This would be in the same vein as your preserve-params plugin, just with hashes and a slight modification to the buildPath
function.
Psuedo-code:
buildPath( pathName ) {
// ...
if( Object.keys(params).includes( 'hash' ) && !this.options.useHash ) {
path = base + route + '#' + hash + '?' + params;
} else {
path = base + route + '?' + params
}
return path;
}
Back and forward buttons doesn't calls transition route
I'd love to see router5 integrated with Cycle.js https://github.com/cyclejs/cycle-core/
For what I see, it would be required to write a router5 Cycle driver http://cycle.js.org/drivers.html but I'm not exactly sure yet, how to accomplish that.
@cgeorg or @staltz are you working on such a driver already?
arr=1&arr=2
this scheme has several problems:
arr=1
), it parsed as a string
. arr[]=1
allow to parse it as array with single element. In my case i had to manually convert single value to array.arr=1&arr=2
as an array, it gets only last value (http://stackoverflow.com/a/9547490)@troch I made router5-link-interceptor. Maybe someone will find it useful ๐
When not using hash for routes, the following can happen:
http://www.example.com/app//home
for route /home
http://www.example.com/app/home
because router might not be able to guess that the base path is app/ (depending on and server)Related to #8
onTransitionSuccess
does not pass the final state piped from middleware, $$success
is invoked with the old toState
instead of the final state
passed by _transition
, I think that does not make sense. Besides, router5.start
works properly with the middleware.
Code ref:
Line 502 in 89227b4
Should node listener registered on unnamed root node be called on the first transition: going from null state to state A?
How is an application bootstrapped?
Asynchronous bootstrapping
let router = new Router5(..., ...)
.start((err, toState) => {
myApp.bootstrap(router.getState())
})
Synchronous bootstrapping
let router = new Router5(..., ...)
.start((err, toState) => {
myApp.update(toState)
})
myApp.bootstrap(null)
Synchronous bootstrapping (2)
let router = new Router5(..., ...)
.start()
myApp.bootstrap(null)
router.addNodeListener('', (fromState, toState) => {
myApp.update(toState)
})
In the last example the node listener won't be called. Should I call one on start if present? If the highest high-order component of an application has been loaded and wishes to register a node listener, I don't see why not.
Suppose we're on the page /about
which is not declared in the routes.
We want to display "page-not-found" widget (component) in this case.
We add listeners but router is silent if no routes matched. Why?
Shouldn't it raise an event with some special toState
or toState == undefined
right after router.start()
to notify about this case? I was pretty sure Not found is the same valid and crucial information as Found to listen to. Did I miss something in the docs?
I'm currently refining the onActivate
Redux middleware example so that the callback is fired for all active route segments, including children. As it stands, routes that are defined under children
in the POJO route definitions file, don't have these callbacks fired.
Is there a better API, other than looping through the routes
definitions? Can I access nested routes by their name?
http://router5.github.io/docs/configuring-routes.html says
If you pass to Router5 an array, it will automatically create a rootNode with an empty name and empty path (new RouteNode('', '')), which is the recommended way.
However it is impossible to either new Router5({name: '', path: ''})
or router.addNode('', '')
because of Uncaught Error: Route constructor expects routes to have an name and a path defined..
Not to mention that '' can't match anything.
So it is both:
Given the sample code below...
import Router5, {loggerPlugin} from 'router5';
import historyPlugin from 'router5-history';
import listenersPlugin from 'router5-listeners';
const routes = [
{ name: 'loan', path: '/loan/:id/detail'},
{ name: 'loan.pricing', path: '/loan/:id/pricing'}
];
const options = {
useHash: true,
defaultRoute : 'dash',
base: '/static/'
};
function createRouter() {
let router = new Router5(routes, options);
router.usePlugin(listenersPlugin());
router.usePlugin(historyPlugin());
return router;
}
let router = createRouter(routes, options);
console.log(router.buildUrl('loan', {id:1}));
console.log(router.buildUrl('loan.pricing', {id:1}));
The output is:
$ babel-node test_url.js
/static/#/loan/1/detail
/static/#/loan/1/detail/loan/1/pricing
Why is the second url generated concatenated?
We want the 'loan' route at the node level so we can do this...
export default connect(routeNodeSelector('loan'))(OurComponent);
but it incorrectly generates the links, manually, and using BaseLink, for the secondaries.
We need the URLs to be...
/static/#/loan/1/detail
/static/#/loan/1/pricing
I'm dealing at the moment with the question how to deal with children nodes which acts as default routes under the parent. Consider the following routes definition:
const routes = {
'system': '/system',
'system.cpu': '/cpu',
'system.memory': '/memory',
'foo': '/foo'
};
Whereas /system
is the defaultRoute
. What I want to achieve is that if the user navigates to /system
that she gets automatically redirected to /system/cpu
.
Could you point me in the right direction? Ah, btw. I'm using router5
in combination with redux-router5
.
Thanks in advance :)
It'd be helpful for testing, examples, and sites that don't need a build step to be able to use Router5 from a CDN like cdnjs.com and jsdelivr.com.
This would also let us put running examples in the docs using jsbin and such.
I have installed router5 via npm and got version 3.0.0
I used it with React using react-router5. When render my component which contains BaseLink
this error: Uncaught TypeError: this.buildPath is not a function
was thrown inside function navigate(name)
at the line: toState.path = this.buildPath(name, params);
I also examine the source code and it looks like class Router5
doesn't contain buildPath
function.
While trying to build a custom framework around router5, heavily utilizing the implementation of nested states, I've found myself in need of abstract states - state that has optional path and can't be navigated to, but trigger relevant events and can be used in custom pipeline.
For instance - I've got two layout pages public
and private
. These would both trigger their events when transitioning from one child state to another (or just be used in custom pipeline), but there would be no way to navigate to them directly.
.addAbstractNode('public') // abstract state without url
.addAbstractNode('public.account', '/account') // abstract state with url (for inheritance to child states)
.addNode('public.account.signIn', '/sign-in') // normal state
.addAbstractNode('private') // abstract state without url
.addNode('private.home', '/') // normal state
Such instance would only allow going to /
and /account/sign-in
.
let router = new Router5({}, {useHash: false});
// Uncaught Error: Route constructor expects routes to have an name and a path defined.
let router = new Router5(null, {useHash: false});
// Uncaught Error: Route constructor expects routes to have an name and a path defined.
let router = new Router5(undefined, {useHash: false});
// this works
Looks like a minor API quirk.
Either as this
or as an additional parameter (personally I'd prefer additional parameter).
Bonus points for supporting something like router.addCanActivateListener(canActivate);
First of all: thanks for putting so much effort into router5 - it's beautiful!
We are building a pretty large app with react + redux and router5 as the routing backbone. At the moment we are struggling with the question how to deal with a universal dispatch mechanism which will be triggered before the very first route transition. We have to perform several steps while bootstrapping the application and want to display some kind of splash screen while performing those steps. How would you realize such a use case?
Thanks in advance :)
Load url
site.com/prices/#!/group/1/product/1/?programID=1¶ms[]=2___1¶ms[]=3___0
Code and output
console.log('window.location =>', window.location.hash);
// window.location => #!/group/1/product/1/?programID=1¶ms[]=2___1¶ms[]=3___0
router.start((err, state) => {
console.log('state =>', state.params.params); // state => ["3___0"]
console.log('path =>', state.path); // path => /group/1/product/1/?programID=1¶ms[]=2___1¶ms[]=3___0
And after router start url hash has been changed to
site.com/prices/#!/group/1/product/1/?programID=1¶ms[]=3___0
If I set up a route "/foo", it is ignored when the path is "/foo/". Bug or intended behaviour?
I am setting up my router like [{name: "index", path: "/"}, {name: "test", path: "/test"}.....], defaultRoute = 'index' and it not working right. If directly entering www.sitename.com/test router redirects www.sitename.com
{name: "index", path: "/index"} is working good. But this time default page url displays form of www.sitename.com/index .
Because partialMatch function matches on path: "/" settings. https://github.com/router5/router5/blob/master/dist/browser/router5.js#L208 So..
_urlMatch("/test", new RegExp('^' + "\/")) returns {}
_urlMatch("/test", new RegExp('^' + "\/index")) returns null
I'm having a heck of a time trying to get my routes intercepted by router5. The arguments I'm using initially can be seen in this snippet of my app's configuration code:
router: {
routes: [
{ name: 'home', path: '/', 'children': [
{ name: 'search', path: '/search' },
{ name: 'new-resource', path: '/new-resource' },
{ name: 'my-data', path: '/my-data' },
{ name: 'browse', path: '/browse' }
]}
],
options: {
defaultRoute: 'home',
base: 'http://localhost:50431'
}
}
I have a link on my page that is href="/new-resource"
, and when I click it, it tries to natively navigate to that page, as though router5 isn't active. No listener fires at all. I've tried both addListener(callback)
and addNodeListener('home', callback)
, and they only fire for the default route that fires on startup.
base
option.Nothing. No matter what I do, it won't intercept my link clicks. It's entirely possible I'm making a huge incorrect assumption about what this router is supposed to do. If that is the case, how does one prevent native navigation from occurring?
Edit: for the record, the link in question is dynamically generated via Cycle.js, though I'm assuming navigation interception would be a window-level event handler.
Hey, i think in the new version there is a bug.
I found that even your examples stop working. Check this out in free time.
http://router5.github.io/docs/with-react.html#/inbox.
I don't know if this is a bug, however in my case router doesn't react on first page load. I think this something what each router should do. Should be able to recognize initial state. I will try to prepare some examples later in jsfiddle. I really like idea behind your router (if i understand it correctly), however it doesn't seems to work as it is described at the page. Maybe those information are not up to date ?
Is there a way to, if canActivate
fails, redirect to another route?
Hi,
I've been having a hard time tracking exceptions in my application and have just discovered the reason: When working with promises in a transition middleware, exceptions get swallowed when they occure while resolving the promise.
By adding a catch
that rethrows the error to the async handler here:
https://github.com/router5/router5/blob/master/modules/async.js#L21
My exceptions are correctly displayed in the console.
I'm not sure if this is the best way to handle this issue, but it would be nice to be able to get these errors out of the router.
const routes = [
{
name: 'page',
path: '/*',
canActivate: (to, from, done) => {},
onActivate: (dispatch) => (params) {},
}
];
router.add(routes);
In the case above, where routes
is a POJO that is then later added to the router, canActivate
is never fired.
Is this an acceptable way to define routes, or should I be using RouteNode
objects?
I'm wondering, wouldn't it be highly desirable to (optionally) use promises to handle any async operations (animated transitions, server calls) within event handlers? This would fit in nicely with the plan to support async canDeactivate
handling.
Hi,
I'm making a React router based on Router5 and all seems to be working fine for now except for query parameters.
I like the way the query parameters and the url parameters are just one big collection, but I need to be able to make my query parameters optional AND provide them in intermediary nodes, i.e.:
var myRouter = new Router5([
{name: 'app', path: '/', children: [
{name: 'calendar', path: 'calendar/:id/', children: [
{name: 'period', path: 'week/?date', children: [
{name: 'details', path:'details/?mode'}
]},
]},
]}
]);
As you can see, the node period
specifies an optional query parameter date
and the following child node details
also specified an optional query parameter mode
.
Is there I was I can make this work with the current router? Or would it be possible to get this supported?
A Solution I was considering was to pre-parse all routes, extract the query parameters and create routes with all possible combinations of query parameters, but this seems like a lot of hassle for something that is quite commonly used?
Usually you'd want the final dist
file to be a single bundled umd-style file.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.