This is what we’ll be building: (demo here)
A simple website to look for music podcasts, list their episodes and listen to them.
The main idea is to take a component based approach, so, with that in mind, this is the structure I’ll be using for the code:
*api (here is the code to access podcasts services)
*components (here are the views and partials for the UI)
*config (here is the routing configuration)
*plugins (support code)
You can get the final code here.
So, the HTML file would look like this:
The important part here is the type attribute for the script tag.
In the markup, we only set a root entry point where we will mount our application from the main file.
That main file, inserts the application layout (a header with a spinner we’ll use for loading state) and delegates main content rendering to the application router:
We just create a new html element with the layout and setup the client-side router with the routing configuration, the root element where it should render the main content (based on the URL) and a listener so we can react to loading events (transition across pages when data is not cached).
So, client-side routing. Many would automatically look for a third party library because it seems that this is a complex area, but the truth is, if you just need to handle simple navigations, building a router from the scratch is not that difficult. It’s just sync your URL with a component.
Regarding to data needed by the rendering component, you can setup the component to load it or you can have the router load it and just pass it to the component. I prefer the second way because I don’t want the new page to begin rendering until I have its data and I also don’t want to handle a loading state for every page (if you create helper component for this, I think its better just to use the router).
Here is the application router:
It is a simple one, but it’s enough to begin with.
I used this approach in some other projects with a slightly more complex router and I usually create another file for popstate proper handling, and to export some navTo and redirectTo helper functions.
So this is what we do:
And when we need to load a new page:
There’s also the execution of the callback to show the app loading indicator.
Ok, so next thing we need to learn about are the pages (home, podcast-detail and episode-detail).
We’ve just seen that pages might have a constructor which accepts initial data and a render function which must return an HTMLElement with the expected content.
As all of the pages will have some common behavior, and we will need several instances of them, we’ll be creating a base class for them which every page will inherit from.
In the constructor of the page we receive the data from the router and call parent to store instance state. We will be using that state to re/render the view.
We also have a getter which returns a map of the DOM events the page must handle. The syntax is inspired by Backbone.
The key for every entry has to parts: the first one is the name of the DOM event we want to listen to and the second part, the selector for “the element we want to have the listener attached to”; the second part is the function we want to associate with the event.
Actually, the event listeners are not bound to the elements represented by the selector, they are all bound to the root element for a view instance and there’s a delegation management in the BasePage.
The function used in the render process is the html one, which must return a string representing the HTML to visualize the page with its provided state.
In the router we saw that we were generating the contents for the page calling a render function which returns an HTMLElement. That function is implemented in the BasePage class (we’re about to get there).
The last important thing to note is the static dataLoader function, which is used by the router to know how to load (asynchronously) the data this view requires.
So far so good. Let’s take a look at the base class for every application page:
In the constructor we store the data received from the page class, create the root HTMLElement for this view and register the possible DOM event handlers the subclass might define.
Then, we only have a function to update instance state (which re-renders the page instance) and the render function for setting HTML string produced by the subclass in the root instance HTMLElement.
If you get back to the HomePage render code, you will notice that every podcast info is rendered using the PodcastSummary partial.
This kind of component is a simpler one because it does not need to handle navigation and don’t need dynamic state.
These ones are like stateless functional components in React. They just receive the data to render and return the corresponding HTML string.
The code for the other pages are quite similar of what you’ve just seen in the HomePage class.
There are some other relevant files:
You can browse/download the finished code for this pet project here to get their details.
As you can see, with the latest browsers, you can implement a basic solution with no third party dependencies in a quite easy way.
I’m sure that, a year or two ahead, when all the browsers have this APIs implemented, there will be newer outstanding ones that will be only available in one or two of them, so we will still waiting for all the browsers to implement them, just as we are now. But, you know? That’s what make our lives entertaining.
If you got this far, I want to THANK you for giving me some of your time and I hope you found something useful in this article.
Extra Bonus: I’ve written a follow-up article to show how easy can be to add code splitting to this basic application.
Check it out! JS: Let’s try the future today (extra bonus)