Easy apps with hyperHTML — 9, routing
Ivan
Posted on June 6, 2019
Part 9 written by
- Introduction, wire/bind
- Events and components
- Moar about components and simple state management
- Wire types and custom definitions (intents)
- Custom elements with hyper
- Customizing my custom elements
- Testing!
- Async loading, placeholder and a Typeahead with hyper
- Handling routes
- 3rd party libraries
If you missed any of the previous parts, now’s a good time to catch up on the ones you missed. Specifically, part 9 builds upon part 8. Recall that we built a table in part 8 and populated it with real data from the cryptocompare api. Now that we are retrieving real data, we can enhance our application even more. This application is a good candidate for routes. By using routes, we can turn it into a single page application or SPA. SPAs don’t need to reload the whole page every time we want to display different parts of the application.
This is our application so far:
We are going to add a new tab to display the historical data for the selected coin in the dropdown. To do this, we are going to use state management, our elements, and route management.
Reorganization
So far we have only used a few elements on the same page. While this is easy to manage, we now need better organization to handle routes and different elements working together.
We are going to create a folder called elements and move the ones we’ve already created into it. Same with intents. Don’t forget: since we changed the paths we need to update our import statements for all of them.
Also, let’s create a components folder. We’ll use it soon.
This is our code now
Routing
There are a ton of different options for routing and most likely your favorite framework has one… and hyper does as well. And yes it’s YAAT — Yet Another Andrea Tool. https://github.com/WebReflection/hyperhtml-app. You don’t need to use hyper to use this library, and it is similar to the Express framework in how you define the routes.
Route types
- get(“/path/:param”, cb): GET requests to the specified path will be caught. Inside the callback cb, the param will be available as a property on the first argument. e.g: ctx.param
- use(paths, cb): Adds middleware to the specified paths. This is similar to get, but it accepts an array of paths
- delete(path, cb): Removes a callback from a path
- param(name, cb): Allows you to specify a function to use once when a parameter name is passed
- navigate(path): Navigate to the path programatically
For our current project, we are will mostly use get, but as you can see, hyperhtml-app has many options for you to use.
To get started, let’s import the library and create a couple of routes. Because we’ll be adding an index route, we can get rid of the code inside our index.html and index.js files. We’ll replace it with this:
New index.html
New index.js
When we spin up the server, clicking on the links shows our messages in the console.
Let’s do a quick recap. First, we created an application then we used get (or post or others) to define our paths and a callback. In a real app, the callback will return the view for that path, but for this simple demo, we used it to console.log some text.
Components
That was pretty easy! But how about we use some components?
By the way, if you need help with components check out part 2.
We are going to create a coins.js file that loads a couple of components.
Let’s start by making two similar components, compare and info. The only difference between them will be that they display something different. Each of these component will be in their own file, Compare.js and Info.js. Take a look at the Compare component:
Our Info component will be the same, but inside the render() function it will show “Information” and a link to “/”.
Now coins.js is going to get a bit more complex. First we are going to load both Compare and Info components
import Compare from "./compare";
import Info from "./info";
Then in the constructor for the app we are going to create references to our components.
constructor(app) {
super();
this.app = app;
this.compare = new Compare();
this.info = new Info();
We also going to create a wire to render them.
this.tabc = hyper.wire(this, ":tabcontent")`<div>`;
this.tabb = hyper.bind(this.tabc);
}
After adding the default state with coin and path as properties, add an update function that only updates the path for the state. After this we are going to add a renderComponent
function that will render the component based on the route
renderComponent() {
this.tabb`<div>`; //needed for later
if (this.state.path === "/") {
this.tabb`${this.compare.update(this.state)}`;
} else {
this.tabb`${this.info.update(this.state)}`;
}
return this.tabc;
}
And render
render() {
return this.html`
<div id="app" class="container-fluid">
${this.renderComponent()}
</div>
`;
}
We also need to update index.js to render coins.js, and index.html to just have the body element. Take a look at the current state of the code:
Layout
Now that we have our routes, we need to add the rest of the elements to our page. We’ll make a Search component that holds an input for selecting the coin, and some tabs to change between the views of the table and the coin information.
Search
For the search we are going to show a “select coin” label along with the input. We won’t start with a coin selected, so at load this is the only thing we are going to show. Let’s take a look at the Search component:
We have to load this new component in coins.js, and bring back our polyfill and elements to index.js
index.js:
import "document-register-element";
import './intents/intents.js';
import "./elements/Table.js";
import "./elements/Typeahead.js";
coins.js:
import Search from "./search";
Then update coins to render search component
${Search.for(this, ":search").update(this.state)}
… and that uncovered a bug. Having an empty filter its creating an error in typeahead. Do you think you can find and fix this bug? Take a shot at it, but if you get stuck, see the latest code below
Tabs
For the Tabs component, we will show a couple of tabs that will serve as our app navigation:
And in the same way as before, import it and render it in coins.js. Now we can see the two views when clicking on the tabs. Check out the code so far:
Complete application
We are almost there! We need to bring our previous elements back to make them work all together.
Compare
We have to load the data whenever the user selects a coin from our input. We’ll use the fetch api just like before. We also added a check before render so that we only render the compare table if we have an icon selected.
Back in coins.js, we now have to listen for the user selecting a coin from the input. Let’s add an onchange
to the base div
<div id="app" onchange="${this}" class="container-fluid">
Don’t forget about the event handler itself:
onchange(ev) {
this.setState({coin: ev.target.value});
}
This also uncovers a different bug in the table — columns are not defined when it tries to render. This is a more complex bug, so feel free to check the finished code for the fix.
Info
The info tab will also fetch the details for the selected coin from cryptocompare, and then show the icon and the name/fullname with a link.
Since we are not showing the tab view until we select something, let’s also hide the tabs themselves by adding the following conditional in render:
if (!this.state.coin) {
return;
}
And done! For an exercise, try to follow how we are passing the properties down to each component with the update() function. To pass data back up, we are listening for the events on the top components :D
See the complete code and application working:
This post took a while to create, but you know life happens. Hopefully you are still reading and as always, clap away and leave your comments and requests below. We are almost done with the series. We hope you like it so far!
Posted on June 6, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.