Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Router queries: getting and passing query parameters #82

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ Scripts in `<body>` will run on every page change, but you can force scripts in
```

The fetch-progress event is a custom event, so usage will look something like this:

```js
window.addEventListener('flamethrower:router:fetch-progress', ({ detail }) => {
const progressBar = document.getElementById('progress-bar');
// progress & length will be 0 if there is no Content-Length header
const bytesReceived = detail.received; // number
const length = detail.length; // number
progressBar.style.width = detail.progress + '%';
const progressBar = document.getElementById('progress-bar');
// progress & length will be 0 if there is no Content-Length header
const bytesReceived = detail.received; // number
const length = detail.length; // number
progressBar.style.width = detail.progress + '%';
});
```

Expand All @@ -85,6 +86,40 @@ Prefecthing is disabled by default.
const router = flamethrower({ prefetch: 'visible' });
```

### Working with url queries

#### Getting queries

You can also get router query

```js
const router = flamethrower();

// getting query from '/query?foo=bar&id=4'
const myQueries = router.query;
console.log(myQueries); // outputs { foo: 'bar', id: 4 }
```

#### Passing queries

You can pass query to url to navigate to

```js
const router = flamethrower();

// navigating to '/query?foo=bar&id=4'
router.go('/query', {
foo: 'bar',
id: 4,
});
```

`NOTE` You don't need to call `router.go(...)` when you used anchor tags in html. Flamethrower will automatically detect url and queries

```html
<a href="/query?foo=bar&id=4">Query Page</a>
```

### Misc

**Supported in all browsers?** Yes. It will fallback to standard navigation if `window.history` does not exist.
Expand Down
3 changes: 1 addition & 2 deletions example/about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ <h1 id="heading">About</h1>
<div id="keep" flamethrower-preserve>
<article>
<p>This text should be preserved when starting from about</p>
</article>
</article>
</div>


<script type="module">
const router = window.flamethrower;
document.getElementById('go').onclick = () => {
Expand Down
4 changes: 2 additions & 2 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
<body>
<div id="load-bar"></div>
<h1 id="heading">Home</h1>
<a href="/">Home</a> | <a id="about" href="/about">About</a>
<a href="/">Home</a> | <a id="about" href="/about">About</a>| <a id="query" href="/query">Query example</a>

<div id="keep" flamethrower-preserve>
<article>
<p>This text should be preserved when starting from home</p>
</article>
</article>
</div>

<script>
Expand Down
102 changes: 102 additions & 0 deletions example/query/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="../favicon.ico" type="image/png" />
<meta name="description" content="The About Page" />
<meta name="extra" content="test" />
<title>Query</title>
<script defer type="module">
import flamethrower from '/flamethrower.js';
flamethrower( { prefetch: 'visible', log: true, pageTransitions: true } );
</script>

<style>
#heading {
color: blue;
}

#load-bar {
background-color: blue;
position: fixed;
top: 0;
left: 0;
width: 0;
height: 5px;
}

#contents {
width: 100%;
margin-top: 15px;
padding: 4px;
border: 2px solid blue;
}
</style>
</head>

<body>
<div id="load-bar"></div>
<a href="/">Home</a> | <button id="go">Go</button>
<button id="back">Back</button>
<h1 id="heading">Query Example</h1>

<h3>Popular Javascript frameworks and libraries</h3>
<div class="">
<li>
<button id="react">React</button>
<button id="angular">Angular</button>
<button id="vue">Vue</button>
</li>
<h3>WIth a tags</h3>
<li>
<a href="/query?lib=react">React</a>
<a href="/query?lib=angular">Angular</a>
<a href="/query?lib=vue">Vue</a>
</li>
</div>
<div id="contents">
<h1 id="cont"></h1>
</div>

<script type="module">
const router = window.flamethrower;
document.getElementById( 'go' ).onclick = () => {
router.go( '/' );
};

document.getElementById( 'back' ).onclick = () => router.back();
console.log( 'query', router.query );

document.getElementById( 'react' ).onclick = () =>
router.go( '/query', {
query: {
lib: 'react',
},
} );
document.getElementById( 'angular' ).onclick = () =>
router.go( '/query', {
query: {
lib: 'angular',
},
} );
document.getElementById( 'vue' ).onclick = () =>
router.go( '/query', {
query: {
lib: 'vue',
},
} );

document.getElementById( 'cont' ).textContent = router.query.lib;

// show loader
window.addEventListener( 'flamethrower:router:fetch-progress', ( { detail } ) => {
const loadBar = document.getElementById( 'load-bar' );
loadBar.style.width = detail.progress + '%';
} );
</script>
</body>

</html>
2 changes: 1 addition & 1 deletion example/script1.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
console.log('script 1');
document.getElementById('headCheck').innerText = '✔️ head script works';
document.getElementById('headCheck').innerText = '✔️ head script works';
2 changes: 1 addition & 1 deletion example/script2.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
console.log('script 2');
document.getElementById('headCheck2').innerText = '✔️ head script works (this should NOT appear of hot navigation)';
document.getElementById('headCheck2').innerText = '✔️ head script works (this should NOT appear of hot navigation)';
37 changes: 33 additions & 4 deletions lib/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RouteChangeData } from './interfaces';
import { RouteChangeData, RouterQuery } from './interfaces';

/**
* @param {string} type
Expand Down Expand Up @@ -37,9 +37,7 @@ export function addToPushState(url: string): void {

// Smooth scroll to anchor link
export function scrollToAnchor(anchor) {
document
.querySelector(anchor)
.scrollIntoView({ behavior: 'smooth', block: 'start' });
document.querySelector(anchor).scrollIntoView({ behavior: 'smooth', block: 'start' });
}

/**
Expand Down Expand Up @@ -109,3 +107,34 @@ export function handleLinkClick(e: MouseEvent): RouteChangeData {
return { type: 'noop' };
}
}

export function parseQueryToString(query: RouterQuery): string {
let queryString = '';
const queryContainer: string[] = [];

// get the names of queries
const queries: string[] = Object.getOwnPropertyNames(query);

// add equal to every query and add in the array
queries.forEach((qname) => {
const fullQuery = `${qname}=${query[qname]}`;
queryContainer.push(fullQuery);
});

// add & to join queries
queryString = queryContainer.join('&');

return queryString;
}

export function getParsedQuery(): RouterQuery {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const queryObject: RouterQuery = {};

urlParams.forEach((value, key) => {
if (!queryObject[key]) queryObject[key] = value.replace('/', '');
});

return queryObject;
}
8 changes: 8 additions & 0 deletions lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ export type FetchProgressEvent = {
/** Number of bytes total (Content-Length header) */
length: number;
};

export type RouterQuery = {
[x: string]: string;
};

export type NavigationOptions = {
query: RouterQuery;
};
26 changes: 20 additions & 6 deletions lib/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FetchProgressEvent, FlamethrowerOptions, RouteChangeData } from './interfaces';
import { addToPushState, handleLinkClick, handlePopState, scrollTo } from './handlers';
import { FetchProgressEvent, FlamethrowerOptions, NavigationOptions, RouteChangeData, RouterQuery } from './interfaces';
import { addToPushState, getParsedQuery, handleLinkClick, handlePopState, parseQueryToString, scrollTo } from './handlers';
import { mergeHead, formatNextDocument, replaceBody, runScripts } from './dom';

const defaultOpts = {
Expand All @@ -11,6 +11,7 @@ export class Router {
public enabled = true;
private prefetched = new Set<string>();
private observer: IntersectionObserver;
public query: RouterQuery;

constructor(public opts?: FlamethrowerOptions) {
this.opts = { ...defaultOpts, ...(opts ?? {}) };
Expand All @@ -23,15 +24,21 @@ export class Router {
console.warn('flamethrower router not supported in this browser or environment');
this.enabled = false;
}

this.getQueryParams();
}

/**
* @param {string} path
* Navigate to a url
* @param {NavigationOptions} options
*/
public go(path: string): Promise<boolean> {
public go(path: string, options?: NavigationOptions): Promise<boolean> {
const prev = window.location.href;
const next = new URL(path, location.origin).href;
const keys = Object.keys(options?.query ?? {});
const query = keys.length !== 0 ? `?${parseQueryToString(options.query)}` : '';
const newPath = path + query;
const next = new URL(newPath, location.origin).href;
return this.reconstructDOM({ type: 'go', next, prev });
}

Expand Down Expand Up @@ -131,7 +138,7 @@ export class Router {
linkEl.as = 'document';

linkEl.onload = () => this.log('🌩️ prefetched', url);
linkEl.onerror = (err) => this.log('🤕 can\'t prefetch', url, err);
linkEl.onerror = (err) => this.log("🤕 can't prefetch", url, err);

document.head.appendChild(linkEl);

Expand All @@ -154,6 +161,13 @@ export class Router {
private onPop(e: PopStateEvent): void {
this.reconstructDOM(handlePopState(e));
}
/**
* Get query paramaters
*/
private getQueryParams(): void {
const query = getParsedQuery();
this.query = query;
}
/**
* @param {RouteChangeData} routeChangeData
* Main process for reconstructing the DOM
Expand Down Expand Up @@ -182,6 +196,7 @@ export class Router {
// Fetch next document
const res = await fetch(next, { headers: { 'X-Flamethrower': '1' } })
.then((res) => {
this.getQueryParams();
const reader = res.body.getReader();
const length = parseInt(res.headers.get('Content-Length'));
let bytesReceived = 0;
Expand Down Expand Up @@ -244,7 +259,6 @@ export class Router {
scrollTo(type, scrollId);
}


window.dispatchEvent(new CustomEvent('flamethrower:router:end'));

// delay for any js rendered links
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"build": "vite build && npx tsc --emitDeclarationOnly",
"serve": "serve ./example",
"test": "playwright test",
"deploy": "node ./deploy-vercel.mjs && vercel deploy --prebuilt"
"deploy": "node ./deploy-vercel.mjs && vercel deploy --prebuilt",
"format": "prettier --plugin-search-dir . --write ."
},
"keywords": [],
"author": "Jeff Delaney",
Expand Down
Loading