The B4 application we’re developing is what you might call a single-page application. The behavior of the application is driven by user interaction, and the data is supplied by RESTful JSON APIs.
Rather than request from the server new pages that live at different URLs, the user requests only the first page and its dependent content (JavaScript, CSS, images, etc.) up front. This raises the question of how to implement and track changes to the application as the user navigates through it.
For this, we’ll use the URL hash: the part of the URL after the pound sign (#). We’ll refer to each page the user might encounter as a view. The welcome view will live at #welcome, which will also be the default if the URL lacks a hash or if it has an invalid hash. Other views will live at hashes that match their purposes.
To implement hashchange navigation between views, open your app/index.ts for editing. Start by removing everything after the page-setup part that uses querySelector to set the mainElement and alertsElement local variables. Next, add the following showView method:
| | /** |
| | * Use Window location hash to show the specified view. |
| | */ |
| | const showView = async () => { |
| | const [view, ...params] = window.location.hash.split('/'); |
| | |
| | switch (view) { |
| | case '#welcome': |
| | mainElement.innerHTML = templates.welcome(); |
| | break; |
| | default: |
| | // Unrecognized view. |
| | throw Error(`Unrecognized view: ${view}`); |
| | } |
| | }; |
This async function starts by reading the window.location.hash string and splitting it wherever there’s a forward slash (/). This will allow us to have parameterized views later, such as #view-bundle/BUNDLE_ID.
Next, we switch on the beginning portion of the hash (view) and take action appropriately. If the hash is #welcome, then we set the contents of the mainElement to contain the welcome-template HTML. If the hash is unrecognized, then we throw an exception.
Currently there’s no code that would invoke showView, but we want it to get called whenever the URL’s hash changes. To get that to work, add the following:
| | window.addEventListener('hashchange', showView); |
Whenever the URL’s hash changes, the window object emits a hashchange event. In answer to this, we want showView to be invoked.
Unfortunately, the initial page load does not trigger a hashchange event, so we still need to call showView explicitly to get things rolling. For that we need one more line of code:
| | showView().catch(err => window.location.hash = '#welcome'); |
Here we call showView directly. Since this is an async function, it returns a Promise when called. If that Promise gets rejected—which would happen if the view hash was unrecognized—then we want to explicitly set the hash to #welcome, which will trigger a hashchange event and load the welcome view.
To recap, at this point your app/index.ts should look like this:
| | import '../node_modules/bootstrap/dist/css/bootstrap.min.css'; |
| | import 'bootstrap'; |
| | import * as templates from './templates.ts'; |
| | |
| | // Page setup. |
| | document.body.innerHTML = templates.main(); |
| | const mainElement = document.body.querySelector('.b4-main'); |
| | const alertsElement = document.body.querySelector('.b4-alerts'); |
| | |
| | /** |
| | * Use Window location hash to show the specified view. |
| | */ |
| | const showView = async () => { |
| | const [view, ...params] = window.location.hash.split('/'); |
| | |
| | switch (view) { |
| | case '#welcome': |
| | mainElement.innerHTML = templates.welcome(); |
| | break; |
| | default: |
| | // Unrecognized view. |
| | throw Error(`Unrecognized view: ${view}`); |
| | } |
| | }; |
| | |
| | window.addEventListener('hashchange', showView); |
| | |
| | showView().catch(err => window.location.hash = '#welcome'); |
After you save this file, your webpack dev server should pick up the change. If you check out localhost:60800, it should look the same as before, with the welcome view showing, but without any alerts.
Now we have all the pieces in place to start implementing views that load data from the web services we developed in previous chapters.