Git Product home page Git Product logo

navaid's Introduction

navaid
A navigation aid (aka, router) for the browser in 865 bytes~!

Install

$ npm install --save navaid

Usage

const navaid = require('navaid');

// Setup router
let router = navaid();
// or: new Navaid();

// Attach routes
router
  .on('/', () => {
    console.log('~> /');
  })
  .on('/users/:username', params => {
    console.log('~> /users/:username', params);
  })
  .on('/books/*', params => {
    console.log('~> /books/*', params);
  });

// Run as single instance
router.run('/');
//=> "~> /"
router.run('/users/lukeed');
//=> "~> /users/:username { username: 'lukeed' }"
router.run('/books/kids/narnia');
//=> "~> /books/* { wild: 'kids/narnia' }"

// Run as long-lived router w/ history & "<a>" bindings
// Also immediately calls `run()` handler for current location
router.listen();

API

navaid(base, on404)

Returns: Navaid

base

Type: String
Default: '/'

The base pathname for your application. By default, Navaid assumes it's operating on the root.

All routes will be processed relative to this path (see on()). Similarly, while listening, Navaid will not intercept any links that don't lead with this base pathname.

Note: Navaid will accept a base with or without a leading and/or trailing slash, as all cases are handled identically.

on404

Type: Function
Default: undefined

The function to run if the incoming pathname did not match any of your defined defined patterns.
When defined, this function will receive the formatted uri as its only parameter; see format().

Important: Navaid only processes routes that match your base path!
Put differently, on404 will never run for URLs that do not begin with base.
This allows you to mount multiple Navaid instances on the same page, each with different base paths. ๐Ÿ˜‰

format(uri)

Returns: String or false

Formats and returns a pathname relative to the base path.

If the uri does not begin with the base, then false will be returned instead.
Otherwise, the return value will always lead with a slash (/).

Note: This is called automatically within the listen() and run() methods.

uri

Type: String

The path to format.

Note: Much like base, paths with or without leading and trailing slashes are handled identically.

route(uri, replace)

Returns: undefined

Programmatically route to a path whilst modifying & using the history events.

uri

Type: String

The desired path to navigate.

Important: Navaid will prefix your uri with the base, if/when defined.

replace

Type: Boolean
Default: false

If true, then history.replaceState will be used. Otherwise history.pushState will be used.

This is generally used for redirects; for example, redirecting a user away from a login page if they're already logged in.

on(pattern, handler)

Returns: Navaid

Define a function handler to run when a route matches the given pattern string.

pattern

Type: String

The supported pattern types are:

  • static (/users)
  • named parameters (/users/:id)
  • nested parameters (/users/:id/books/:title)
  • optional parameters (/users/:id?/books/:title?)
  • any match / wildcards (/users/*)

Note: It does not matter if your pattern begins with a / โ€” it will be added if missing.

handler

Type: Function

The function to run when its pattern is matched.

Note: This can also be a Promise or async function, but it's up to you to handle its result/error.

For patterns with named parameters and/or wildcards, the handler will receive an "params" object containing the parsed values.

When wildcards are used, the "params" object will fill a wild key with the URL segment(s) that match from that point & onward.

let r = new Navaid();

r.on('/users/:id', console.log).run('/users/lukeed');
//=> { id: 'lukeed' }

r.on('/users/:id/books/:title?', console.log).run('/users/lukeed/books/narnia');
//=> { id: 'lukeed', title: 'narnia' }

r.on('/users/:id/jobs/*').run('/users/lukeed/jobs/foo/bar/baz/bat');
//=> { id: 'lukeed', wild: 'foo/bar/baz/bat' }

run(uri)

Returns: Navaid

Executes the matching handler for the specified path.

Unlike route(), this does not pass through the history state nor emit any events.

Note: You'll generally want to use listen() instead, but run() may be useful in Node.js/SSR contexts.

uri

Type: String
Default: location.pathname

The pathname to process. Its matching handler (as defined by on()) will be executed.

listen(uri?)

Returns: Navaid

Attaches global listeners to synchronize your router with URL changes, which allows Navaid to respond consistently to your browser's BACK and FORWARD buttons.

These are the (global) events that Navaid creates and/or responds to:

  • popstate
  • replacestate
  • pushstate

Navaid will also bind to any click event(s) on anchor tags (<a href="" />) so long as the link has a valid href that matches the base path. Navaid will not intercept links that have any target attribute or if the link was clicked with a special modifier (eg; ALT, SHIFT, CMD, or CTRL).

uri

Type: String
Default: undefined

(Optional) Any value passed to listen() will be forwarded to the underlying run() call.
When not defined, run() will read the current location.pathname value.

unlisten()

Returns: undefined

Detach all listeners initialized by listen().

Note: This method is only available after listen() has been invoked.

License

MIT ยฉ Luke Edwards

navaid's People

Contributors

aryelgois avatar benmccann avatar jacwright avatar lukeed avatar paulocoghi avatar timgates42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

navaid's Issues

404 flashes on page refresh

Hi @lukeed thanks for your work on this router! I'm having some trouble with 404 page not found handling and was wondering if you could point me in the right direction. Whenever I do a Ctrl+r to reload the page I see the 404 page flicker before loading the correct page (sometimes it doesn't reach the correct page and just loads the 404). Here are some project details in case it's helpful:

  • I have static HTML fallbacks for each page that hydrate into svelte components
  • The static HTML paths are in the format /about/index.html so I match the urls with trailing slashes and redirect them to the same page but without the slash in the spa (e.g. /about)
  • I dynamically create the routes using a data source (nodes.js)
Click to see router code
<Html {route} {node} {allNodes} />

<script>
  import Navaid from 'navaid';
  import nodes from './nodes.js';
  import Html from '../global/html.svelte';

  let route, node, allNodes;

  const getNode = (uri, trailingSlash = "") => {
    return nodes.find(node => node.path + trailingSlash == uri);
  }

  let uri = location.pathname;
  node = getNode(uri);
  if (node === undefined) {
    node = getNode(uri, "/");
  }
  allNodes = nodes;

  function draw(m) {
    node = getNode(uri);
    if (node === undefined) {
      // Create a placeholder node so props are complete
      node = {
        "path": "/404",
        "type": "404",
        "filename": "404.json",
        "fields": {}
      }
    }
    route = m.default;
    window.scrollTo(0, 0);
  }

  function track(obj) {
    uri = obj.state || obj.uri;
  }

  addEventListener('replacestate', track);
  addEventListener('pushstate', track);
  addEventListener('popstate', track);

  const router = Navaid('/', () => import('../content/404.js').then(draw));

  allNodes.forEach(node => {
    router.on(node.path, () => {
      // Check if the url visited ends in a trailing slash (besides the homepage).
      if (uri.length > 1 && uri.slice(-1) == "/") {
        // Redirect to the same path without the trailing slash.
        router.route(node.path, false);
      } else {
        import('../content/' + node.type + '.js').then(draw);
      }
    }).listen();

  });

</script>

Thanks!

Consider support for hash-based routing

For SPA's that need to work regardless of hosting environment, using hash-based navigation is usually the best solution, especially if link-sharing is integral to the app.

I made these small changes to enable hash support:

if ((uri = fmt(uri || location.href.replace(location.origin, "")))) {
//uri = uri.match(/[^\?#]*/)[0];

and

...
		addEventListener("click", click);
		addEventListener("hashChange", run);
		$.unlisten = function() {
...
			removeEventListener("click", click);
			removeEventListener("hashChange", run);
		};

Given the regex I assume you did not want support for hashes in this library, but happy to write a test and submit a pull if you are open to it.

Allow for state params in pushState

Starting to use your library with Svelte 3. This is either a question or feature request...

I see the pushState events are basically using the state param for the uri. This removes ability to history.pushState(someRouteState, ...) right?

Seems like it would be nice to have pushState state param, say, default to null but be available to use.... when caching some route data like here:

https://twitter.com/ryanflorence/status/1111272046372093953

None of the other generic routers seem to handle this well OR they don't handle search params well or something. Yours is best so far and small. But having state available would be nice.

Nested routes wildcard *

In the sample project https://codesandbox.io/s/trusting-lamarr-zdmlb App.svelte has:

const router = Navaid()
    .on("", () => draw(Home))
    .on("teams", () => draw(Teams))
    .on("teams/*", () => draw(Teams))
    .listen();

As you can see I have:

.on("teams", () => draw(Teams))
.on("teams/*", () => draw(Teams))

Is it possible to use just one? Something like:

.on("teams*", () => draw(Teams))

Clear route array on .unlisten()

Not sure if this was intended behavior or not, but I'd like the routes array to be cleared on .unlisten(). I have a different routing structure depending on mobile/desktop view and old handlers will still be called after I unlisten to the first router instance. I can submit a PR if interested?

Hosting requirements

Hello!

All we know that example: https://codesandbox.io/s/reverent-tesla-c0kq0
But when you update sirv-cli from 0.3.1 to last version 1.0.8 you will get strange behavior.
All svelte project become rerendering multiple times and it will be difficult to call it SPA as for me.
You should see console after changing sirv-cli version.
For example: 1 click to "Go to Teams Page" and you will get 9 same messages "~> App is drawing ::
PageTeamList ".

I will go with other serving software in production and it could be no problem but i am not sure.

What is the problem here? And how i could fix it? And how i could name that problem correctly?

Thank you!

Various code size optimisations can be made

You seem to care about code size (since you describe it so very early on), but there was a fair bit left on the table. Iโ€™m just dumping this here, it should be semantically equivalent, except for not using non-standard events, and fixing the pushState/replaceState arguments; but I have no inclination to follow anything up on it. If one truly wants to minimise size, regexparam would need to be inlined, and its objects turned into arrays. Anyway, you may do what you will with this, down to and including nothing. Enjoy!

import convert from 'regexparam';

export default (base, on404) => {
	let rgx, routes=[], handlers={}, $, hist = history;

	let fmt = uri => {
		if (!uri) return uri;
		uri = '/' + uri.replace(/^\/|\/$/g, '');
		return rgx ? rgx.test(uri) && (uri.replace(rgx, '') || '/') : uri;
	};

	if ((base=fmt(base)) === '/') base = '';
	if (base) rgx = new RegExp('^/?' + base.substring(1) + '(?=/|$)', 'i');

	return $ = {
		format: fmt, 

		route(uri, replace) {
			hist[replace ? 'replaceState' : 'pushState']('', '', base + uri);
		},

		on(pat, fn) {
			handlers[pat] = fn;
			let o = convert(pat);
			o.route = pat;
			routes.push(o);
			return $;
		},

		run(uri) {
			uri = fmt(uri || location.pathname);
			for(var i=0, len=routes.length;i<len;i++){
				let obj=routes[i], arr=obj.pattern.exec(uri);
				if (arr) {
					let keys=obj.keys, params={};
					// Ainโ€™t variable reuse fun?
					i=keys.length;
					while (i--) params[keys[i]]=arr[i] || null;
					handlers[obj.route](params); // todo loop?
					return $;
				}
			}
			if (uri && on404) {
				on404(uri);
			}
			return $;
		},

		listen() {
			let off = removeEventListener;
			let on = addEventListener;
			let run = () => $.run();
			let click = e => {
				let x = e.target.closest('a');
				if (!x || !x.href || x.target) return;
				if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button) return;
				if (x = fmt(x.getAttribute('href'))) {
					e.preventDefault();
					$.route(x);
				}
			};

			let pushState = hist.pushState;
			let replaceState = hist.replaceState;

			hist.pushState = (state, title, uri) => {
				pushState.call(hist, state, title, uri);
				run();
			};
			hist.replaceState = (state, title, uri) => {
				replaceState.call(hist, state, title, uri);
				run();
			};

			on('popstate', run);
			on('click', click);

			$.unlisten = () => {
				hist.pushState = pushState;
				hist.replaceState = replaceState;
				off('popstate', run);
				off('click', click);
			};

			return $.run();
		},
	};
};

Feeding this through Terser for minification (without inlining the dependency), normal/gzipped: before, 1274/671 bytes; after, 1061/608 bytes. Roughly 16%/8% savings.

There are various further optimisations that can be performed to shave fifty or more bytes off the minified result, but at the cost of a few bytes gzippedโ€”turning two or three similar things into a function that you call two or three times can often save of ungzipped size, at the cost of gzipped size because the repetition compresses so well.

P.S. I strongly dislike the history.{pushState, replaceState} replacement. $.route() and the interception should be the only way to do that. Oh yeah, a couple of things that are missing here: support for imagemaps, and absolute URLsโ€”for the latter, I suggest using HTMLHyperlinkElementUtils.href, which is a full URL, and prefix-matching on a full URL base (which will probably be an origin plus a slash). Iโ€™m done now.

svelte + snowpack + navaid

This is not a real issue, I just want to share that navaid works!
(after finding atm other routers having problem with snowpack)

Hope this helps ๐Ÿ˜ƒ

In stores.js

import { writable } from 'svelte/store'

export const component = writable(null)

In routes.js

import navaid from 'navaid'
import { component } from './stores'

import Dashboard from './views/Dashboard.svelte'
import NotFound from './views/NotFound.svelte'

let router = navaid()

router.on('/', () => component.set(Dashboard))
router.on('*', () => component.set(NotFound))

export default router

In App.svelte

<script>
  import { component } from './stores'
</script>

<svelte:component this="{$component}" />

Show loading before load and hide after load

Hi,

How i can receive a generic event before load component and after load it?

My components .js file are loaded dynamically, so it still on home until the .js file is loaded. So i want show a loading toast and hide after it was loaded.

const router = Navaid("/")
        .on("/", () => run(import("../routes/Home.svelte")))
        .on("/about", () => run(import("../routes/About.svelte")))

Can you help me?

Thanks.

How to write in Async/Await style?

Hi Luke, here's an example.

function fetchPromise() {
  httpie.get('someApi').then(res => console.log(res.data))
}

async function fetchAsync() {
  const res = await httpie.get('someApi')
  console.log(res.data)
}

let router = navaid()
router.on('/', () => fetchPromise())
router.listen()

fetchPromise() works, but fetchAsync() failed.

Thank you.

Svelte update (3.5.1 > 3.5.2) and BOOOM! (problems, not joys)

Taking your https://codesandbox.io/s/reverent-tesla-c0kq0 from #13.

  • If you upgrade to 3.5.1 is ok:

(one click on "Go to Teams Page")
image

  • If you upgrade to 3.5.2 is not:

(one click on "Go to Teams Page")
image

What can it be?

UPDATE:

FIRST PROBLEM

I cannot navigate anymore.

Until 3.5.1 you can navigate like this:

  1. Being in Home ("/")
  2. click on "Go to Teams Page" ("/teams")
  3. click on "Go to Team 1 Page" ("team/1")

With 3.5.2 you can't.

You can do 1 and 2 but at the step 3 you see nothing.

If you are on the page "team/1" and reload using codesandbox's preview icon it works.

SECOND PROBLEM

In one of my projects the error is even more frightening:

TypeError: Cannot read property '$$' of undefined

Render something during route import().

Thanks to https://github.com/lukeed/svelte-demo I'm learning how to use the amazing navaid.

I have a doubt: how can I render something (eg. "Loading...") until import(ing)() a Route?

Example:

const router = Navaid('/')
  .on('/', () => import('../routes/Home.svelte').then(draw))
  .on('/about', () => import('../routes/About.svelte').then(draw))
  .on('/blog', () => import('../routes/Blog.svelte').then(draw))
  .on('/blog/:title', obj => import('../routes/Article.svelte').then(m => draw(m, obj)))
  .listen();

Let's say import('../routes/About.svelte')is slow, the browser is waiting but nothing is showed to the user.

Am I wrong?

Support for cancelling navigation?

Hi,
Is it possible to cancel navigation in navaid? In an editor scenario I would like to setup a dirty flag so that if the user wants to navigate off the page while the editor has changes, to prompt the user and give him the chance to cancel navigation.

In the svelte-demo repo I have seen a good example of route guards for the protected/admin area scenario. Following that sample code, I could conditionally prompt the user based on a dirty flag and do nothing at all if he or she chooses to cancel. But from navaids point of view, the navigation would have already happened and the history would reflect the new url.

I thinks a beforeNavigation hook would be a great addition to the the library.

Is it possible to solve this scenario in navaids current incarnation?

Thanks!!

const router = (
  Navaid('/')
  // support for cancelling navigation??
  .beforeNavigation((newPath, oldPath) => {
    if($dirtyFlag) {
      // prompt user  
      // return false to cancel navigation      
      return false;
    }
    return true;
  })
  .on('/', () => run(import('../routes/Home.svelte')))
  .on('/about', () => run(import('../routes/About.svelte')))
  .on('/login', () => run(import('../routes/Login.svelte')))
  .on('/private', () => onAdmin(import('../routes/Private.svelte')))
  .on('/forbidden', () => run(import('../routes/Forbidden.svelte')))
  .on('/blog', () => run(import('../routes/Blog.svelte')))
  .on('/blog/:postid', obj => run(import('../routes/Article.svelte'), obj))
  );
function onAdmin(thunk, obj) {
  if ($isAdmin) return run(thunk, obj);
  if ($isUser) return router.route('/forbidden', true);
  router.route('/login', true);
}

404 if direct access to specific route.

Hi! I am currently trying to duplicate what you had done in https://github.com/lukeed/svelte-demo using Navaid.

However, if I directly accessing my /about page, it returns me 404, it does work fine if I have a button to redirect from /.

May I know how should I set up so that it allows me to directly accessing /about without going through /?

Thanks a lot

Nested routes info and problem reproduction

I apologize in advance if I'm wrong to open the issue and I shouldn't have done it here.
But I think it really concerns the basics of Navaid.

I'm also ready to open and update a wiki page for all newcomers like me.

THE PROBLEM:

I started from svelte-demo (https://github.com/lukeed/svelte-demo) and I'm trying to use nested routes for a list/detail view.

REPRODUCTION: https://codesandbox.io/s/quizzical-dirac-2d0qy

To come to the problem you should:

  • open the console from the tab on the bottom
  • click on "Go to Teams Page"
  • click on "Go to Team 1 Page" (or others)
  • click on "Go to Team 1 Page" (or others) from the list
  • as you can see in console until now just four messages from "App draw()" and one "PageTeamRoute draw()"
  • now you can clear the console
  • click on "Go to Player 1 Page" (or others) from the list
  • you can see now the problem: 4 messages in console:

image

Why?

Am I correctly using Navaid with nested routes?

Is this correct in App.svelte?

.on("team/*", obj => draw(PageTeam, obj))

Hi, a small question

It's a tiny great lib, but i have a question.
Does browser native support
image
these two events? I can't find it
thank you !

Calling listen() with an optional URL to be used on its internal run() call

Hello, Luke!

I am implementing navaid as a long-lived router on an app that will be used both on web and smartphones (through Cordova).

Is there an option on navaid to avoid the automatic call of run() when calling listen() or a way to provide a specific URL to this first call, like / instead of the current URL?

This helps my app to run fine on smartphones as well, because otherwise the run() call will fail with the initial URL provided by Cordova, that is something like file:///home/user/app/directory/index.html.

I used Page.js to to this before, and on the smartphones I used its dispatch: false option to avoid this firs "run". But Navaid is so incredibly smaller that I prefer it over Page.js.

Thanks again!

commonjs build does not have same exports as esm build

Ran into this while writing tests.

The bundled script contains dist/navaid.mjs, while Jest (or more accurately node?) imports dist/navaid.js.

  • navaid.mjs exports Navaid as the default export
    • Rollup imports this file (can't speak for other bundlers, but I imagine the behaviour is the same)
  • navaid.js exports Navaid as a named export
    • when Jest comes across import navaid from "navaid" it imports this file
    • this results in navaid === undefined, since there is no default export

Thought I'd bring it up as the inconsistency doesn't seem like it would be intentional.


As a side note to anyone else coming across this, Jest can be configured to map the default navaid import to the .mjs file:

// jest.config.js

module.exports = {
	// ...

	moduleNameMapper: {
		// use the navaid.mjs file when Jest finds `import navaid from "navaid"`
		"^navaid$": "<rootDir>/node_modules/navaid/dist/navaid.mjs",
	},

	transform: {
		// transform .mjs files with babel
		"^.+\\.m?js$": "babel-jest",
	},

	// ...
}

catch urls like /team/:teamID and /team/:teamID* (wildcard)

Coming from this example: https://codesandbox.io/s/reverent-tesla-c0kq0 I would like to understnad if this is possible in Navaid now:

const router = Navaid()
    .on("/", () => draw(PageHome))
    .on("/teams", () => draw(PageTeamList))
    .on("/team/:teamID/*", obj => draw(Team, obj))
    .listen();
  • using .on("/team/:teamID/*", obj => draw(Team, obj)) doesn't catch urls like "/team/1"
  • using .on("/team/*", obj => draw(Team, obj)) catch urls like "/team/1" but as params in Team component I need the :teamID parameter which is wild in this case; the problem is if the URL is eg. like "/team/1/2/56/8" wild is 1/2/56/8 which is a wrong var in my Team component code.

UPDATE:

I need also something like this to catch: /team/1/shirt/5.

Is there a way to catch both using something like /team/:teamID* where * here is a wildcard after :teamID?

I hope I was clear. :)

Ignore and redirect external links

This library takes over external links so when you click on an external link:

<a href="http://google.com">link</a>

It instead redirects to:

http://localhost:5000/http://google.com

My current workaround is detect an external link and replace the location like:

if (u.indexOf('/http') === 0) {
    location.replace(u.substring(1))
    return;
}

But it still temporarily shows the invalid URL in the browser before redirecting and you can't use location.href = u.substring(1) because it adds the invalid URL to the browser history so when you go back it goes back to the URL.

IMO external links should not be caught and redirected immediately, or if you don't agree with that default behavior can you provide an option to not handle external links.

Alternatively it would be more flexible if there was a callback where we can intercept the location change so we can immediately redirect ourselves using location.href.

RFC: Multiple Navaid Instances?

There's currently not a good way to handle multiple routers within the same page.
But what are your thoughts about this for handling multiple routers in a page?

TBH I'm not even sure that this is a good idea... still on the fence

var items = (
  navaid()
    .on('/items/', () => console.log('Items Home'))
    .on('/items/:title', x => console.log('Item: ', x.title))
);

var books = (
  navaid()
    .on('/books/', () => console.log('Books Home'))
    .on('/books/:title', x => console.log('Book: ', x.title))
);

var main = (
  navaid()
    .on('/', () => console.log('Home'))
    .on('/about', () => console.log('About'))
    .use(items)
    .use(books)
    .on('/contact', () => console.log('Contact'))
);

main.listen();

I already have this built, and it seems to be the only sane way to juggle multiple routers on a page.

As for actual routing, there are two options:

  1. Look at the main routes first and then the sub-routers' routes, disregarding the order of the router composition:

    /   โ€“>   /about   โ€“>   /contact   โ€“>   /items   โ€“>   /items/:title   โ€“>   /books   โ€“>   /books/:title
    
  2. Treat all routers' routes equally, respecting the order of composition:

    /   โ€“>   /about   โ€“>   /items   โ€“>   /items/:title   โ€“>   /books   โ€“>   /books/:title   โ€“>   /contact
    

Of course, another alternative is to do something like this:

main.listen();
items.listen();
books.listen();

...which feels much simpler to the end-user, but it very likely adds a considerable amount of overhead to Navaid in order to facilitate that. IMO it may not be worth it, especially if/when the 90% use-case is to use a single instance per App.


No matter the choice, handling the on404 behavior needs answering, too. For example, if main, items, and books all have their own 404-handler, how will one know to fire vs wait for a sibling router? Similarly, what if only books had a 404-handler defined... how should it know that it's okay to run?

My hunch is that the 2nd option (3x .listen()) is not compatible with this at all.

Query params

Would you be open to a contribution that supported query params?

It would be removing the query string from the end of the uri sent to run before looking for matching routes, then splitting they query string into its key-value pairs and sending those as a second parameter to the route function. E.g.

router.on('/users', (params, query) => {
  // use query params to sort, filter, etc the user list.
  const { sort, filter, limit } = query;
  ...
}

before / after handlers

I see there's on. It's not clear to me from the docs whether that happens before or after, but it'd be cool if there were also a handler for the other one

This is low priority for me since I'm not actually using navaid for anything right now. Just something I noticed was missing from the API and thought I'd share as a suggestion

How to add go back action?

I would like to add an in-app back button that should go back in the history, but if it would leave my site it should call history.replaceState to a fallback instead.

For example, if I click on a link to example.com/products/123, the in-app back button would work as router.route('/products', true), but if I was already at any page in that site and navigated to that route, the button would work as history.back().

The falback uri would be different depending on the page and should be specified, but it could default to /.

[RFC] Prevent routing to current path

Assuming this setup:

router = (
  navaid()
    .on('/', () => console.log('doing something'))
    .listen()
);

You will be doing something every time the "/" path is routed. This is common sense & to be expected. However, what might not be obvious is that if you have a standard link in a global component (eg, a navbar) pointing to the home page:

<a href="/">Go Home</a>

... and click it repeatedly, you will be doing something for each of those clicks, even though you're already on the "/" route.


This is posted as a RFC because it might qualify as a breaking change in some apps.
Many might actually be relying on this behavior to reset page state, for example.

Please let me know below :)

Best approach to implement protect and public routes

what is this the best practice to implement a separation between public and protected routes?

I know that is possible to implement this kind of separation inside a protected components. For example, if the user is not authenticated I can call the route /login.

Are there another approach?

Support for shadow dom

Intercepting clicks made on anchors inside shadow roots looking for the anchor closest to the event target doesn't work since events are re-targeted to the shadow root host element.

The anchor can be found in the event path with the following code:

function findA(e){
  var i = 0, p = e.path || e.composedPath && e.composedPath(), a = p && p[i];
  while(a && a.tagName !== 'A') a = p[++i];
  return a;
}

Note that this will only work for shadow roots configured with mode set to open since elements inside closed ones don't appear in the event path.

Can submit a PR if you agree with the proposed change

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.