Lesson Weekend

After integrating Handlebars into our project, our App.java file is much cleaner and easier to read. However, you may have noticed some redundancy between templates: They all have the same opening <DOCTYPE>, <html>, and other basic required tags at the tops and bottoms of their files.

Similar to our back-end logic, we want to keep our view files DRY too. Let's refactor our templates by creating a reusable layout that will be rendered on all pages. This layout will contain the HTML that all templates have in common. Then, each individual page will render the HTML unique to it through the layout. Don't worry if this sounds confusing at first; you'll see what we're talking about in just a moment. Reusable layouts and creating small page chunks is where templating really begins to shine!

Creating Reusable Layouts

First, we'll create a template called layout.hbs and move our basic HTML document structure into it:

friend-letter/src/main/resources/templates/layout.hbs

{{#block "header"}}

<!DOCTYPE html>
<html>
 <head>
   <title>{{#block "title"}}Hello Friend!{{/block}}
</title>
   <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'>
 </head>
 <body>
{{/block}}

   <!-- begin inner template-->
   <div class="container">
     {{#block "content"}}
{{/block}}

   </div>
   <!-- end inner template-->
{{#block "footer"}}

 </body>
</html>
{{/block}}

You'll notice a couple of lines that look pretty foreign:

{{#block "title"}}
{{#block "content"}}

And

{{#block "footer"}}

Along with corresponding {{/block}} tags. What’s all this? Well the cool thing about Handlebars isn’t just that we don't have to concatenate lengthy strings together to show HTML, but also that we can break our page into different segments, or, as Handlebars calls them, Blocks and Partials. Let’s look at how the content block works first, then we’ll discuss the rest.

Streamlining the Layout

Remove the content we've just placed into layout.hbs from hello.hbs. We should be left with this:

friend-letter/src/main/resources/templates/hello.hbs
{{#partial "content"}}

<h1>Hello From Afar</h1>
<p>Dear Friend,</p>
<p>How are you? I hope that you are having a nice weekend. I'm vacationing in Iceland while I learn programming! </p>
<p>Friend, you would not believe how cold it is here. I should have gone to Hawaii instead.</p>
<p>But I like programming a lot, so I've got that going for me. </p>
<p>Looking forward to seeing you soon. I'll bring you back a souvenir. </p>
<p>Cheers,</p>
<p>Travel Enthusiast Jane</p>
<p><a href='/favorite_photos' >P.S. Check out my favorite travel photos here.</a></p>
{{/partial}}

{{> layout.hbs}}


We'll do the same for favorite_photos.hbs:

src/main/resources/templates/favorite_photos.hbs
{{#partial "content"}}
<h1>Favorite Traveling Photos</h1>
<ul>
 <li><img src='/images/foggymountain.jpeg' alt='A photo of a mountain.'/></li>
 <li><img src='/images/rockycoast.jpeg' alt='A photo of a a rocky beach.'/></li>
</ul>
{{/partial}}

{{> layout.hbs}}

Since all the basic HTML structure now resides in our layout.hbs file, we no longer need it here in hello.hbs and favorite_photos.hbs. This really dries up our views!

How layout.hbs Works

So how does this work anyway? layout.hbs contains the basic HTML tags that each page will require. Instead of including these in all templates, we consolidate them here in this single file. Then, when we load a specific page, we'll instruct our app to render both layout.hbs and the page-specific content (in our case, hello.hbs or favorite_photos.hbs).

The HTML for the page-specific content (like hello.hbs or favorite_photos.hbs) will load wherever we place the {{#block "content"}}{{/block}} line in layout.hbs. Let’s walk through how all this works.

  • layout.hbs is not chosen at random - this is the default name Handlebars always uses to render the outer “frame”. Always give your global layout files this name.
  • When the server notices that a request for a route is being made, it reads through App.java until it reaches the correct Route Handler - namely a piece of code that indicates what is supposed to happen when this route is accessed.
  • Instead of rendering a concatenated String as HTML, or rendering a single .hbs template, we create a HashMap, and then store information on which template to render in the content area of Layout in that HashMap. Imagine the HashMap (It can be called model, or it can be called something different - as long as it's consistent) as a grocery bag in which you are placing all the ingredients to make a sandwich.
  • Next, we hand all of our miscellaneous bits and pieces to the HandlebarsTemplateEngine, which stitches together one cohesive HTML page and serves it out of the different pieces we provide.
friend-letter/src/main/java/App.java
import java.util.Map;
…

get("/", (request, response) -> { //request for route happens at this location
   Map<String, Object> model = new HashMap<String, Object>(); // new model is made to store information
   return new ModelAndView(model, "hello.hbs"); // assemble individual pieces and render
}, new HandlebarsTemplateEngine()); //

get("/favorite_photos", (request, response) -> {
   Map<String, Object> model = new HashMap<String, Object>();
   return new ModelAndView(model, "favorite_photos.hbs");
}, new HandlebarsTemplateEngine());

...

Routing Recap - One more time

So, let's walk through exactly what will happen when we make an HTTP GET request to access the root route of our application.

  1. We launch our application by running it in IntelliJ, making it accessible in the browser.

  2. We enter the following in the browser's URL bar: http://localhost:4567/. This triggers an HTTP GET request.

  3. Spark maps this GET request to the following route in App.java. (Note, if we entered http://localhost:4567/favoritephotos, it would map it to our _other route. The route name specified as the first argument to get() refers to the URL location of a specific area of our Spark app).

friend-letter/src/main/java/App.java
...
get("/", (request, response) -> { //request for route happens at this location
   Map<String, Object> model = new HashMap<String, Object>(); // new model is made to store information
   return new ModelAndView(model, "hello.hbs"); // assemble individual pieces and render
}, new HandlebarsTemplateEngine()); //

...
  1. This block of code is executed by Spark. It creates a new HashMap, placing our hello.hbs template in it with the corresponding key "template". Spark then returns a new ModelAndView object (this is a type of class built into Spark) created with the model HashMap and our layout.hbs HTML layout.

  2. This prompts spark to load our layout.hbs file.

Understanding Blocks and Partials

Now that we understand more about how we can break our app up into separate .hbs templates, let’s take a closer look at blocks and partials and how they influence each other. This is the same code mentioned above:

friend-letter/src/main/resources/templates/layout.hbs

{{#block "header"}}

<!DOCTYPE html>
<html>
 <head>
   <title>{{#block "title"}}Hello Friend!{{/block}}
</title>
   <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'>
 </head>
 <body>
{{/block}}

   <!-- begin inner template-->
   <div class="container">
     {{#block "content"}}
{{/block}}

   </div>
   <!-- end inner template-->
{{#block "footer"}}

 </body>
</html>
{{/block}}

Blocks and partials allow more fine-tuned control than simply loading one whole page into our layout.

What if, for example, we built our page and added 99 extra pages to our website. But we were fine having the title “Hello Friend” on many of these pages, but wanted the title "Check out my travel photos!" specifically on our favorite_photos page? How can we accomplish this? With a block override! We can define a partial load into a block defined in the frame from inside another partial. This allows us to selectively override properties defined in our layout page from a page that is actually being rendered inside of that “frame”. Cool! Let’s walk through how this works.

Let’s make a change to favorite_photos:

src/main/resources/templates/favorite_photos.hbs
{{#partial "title" }}
   Check out my travel photos! <!-- I've changed!-->
{{/partial}}

{{#partial "content"}}
<h1>Favorite Traveling Photos</h1>
<ul>
  <li><img src='/images/foggymountain.jpeg' alt='A photo of a mountain.'/></li>
  <li><img src='/images/rockycoast.jpeg' alt='A photo of a a rocky beach.'/></li>
</ul>
{{/partial}}

{{> layout.hbs}}

Re-run the server, and you’ll see the page title show up as “Check out my travel photos!” in the page we just changed, without needing to do anything complicated with layout.hbs. Great!


Example GitHub Repo for Friend Letter Code