Lesson Monday

In the previous lesson we set up our Blog project directory, and created its basic back-end logic using Behavior-Driven development. We should have a Post class with a constructor, and getContent() method. Now that our basic back-end logic is in place we can construct our front-end user interface with Spark.

Creating a Spark Application with Objects

Root Route

We'll begin by creating a root route in App.java file that will render our home page:

my-epicodus-blog/src/main/java/App.java
import java.util.Map;
import java.util.HashMap;
import spark.ModelAndView;
import spark.template.handlebars.HandlebarsTemplateEngine;
import static spark.Spark.*;

public class App {
  public static void main(String[] args) {
    staticFileLocation("/public");

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

index.hbs Template

You'll notice the route we added in the last step referred to a template named index.hbs. It's common naming practice to call the template for your root route index. Let's create our index template now. It will contain a form for users to input a post to add to their Blog.

If you haven’t yet created it, add a new directory called templates under resources and add layout.hbs and index.hbs to that directory. Add the following code to your layout.hbs

my-epicodus-blog/src/main/resources/templates/layout.hbs
{{#block "header"}}

   <!doctype html>
   <html lang="en">
   <head>
       <meta charset="UTF-8">
       <meta name="viewport"
             content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
       <meta http-equiv="X-UA-Compatible" content="ie=edge">
       <title>{{#block "title"}}Welcome to My Epicodus Blog{{/block}}</title>
       <link rel="stylesheet" href="/css/main.css">
   </head>
   <body>
{{/block}}
<div class="page">
{{#block "content"}}



{{/block}}

{{#block "footer"}}
   </div>
   </body>
   </html>
{{/block}}

Note: Be sure that you are adding your .hbs files to src/main/resources, NOT src/test/resources!

Because we are creating a new object and showing it, this form will have a method attribute of post:

my-epicodus-blog/src/main/resources/templates/index.hbs
{{#partial "content"}}

<h1>Post list!</h1>

<p>Add a new Post:</p>

<form action="/posts/new" method="POST">
  <label for="content">Post Description</label>
  <input id="content" name="content" type="text">

  <button type="submit" class="btn btn-default">Add post</button>
</form>
{{/partial}}

{{> layout.hbs}}

Creating Objects with a POST Request

Now, because the form's action attribute is "/posts, and its method attribute is post, Spark will send this form's data to a route with the post() action and "/posts/new". Let's create this route now:

my-epicodus-blog/src/main/java/App.java
…
post("/posts/new", (request, response) -> { //URL to make new post on POST route
   Map<String, Object> model = new HashMap<String, Object>();
   String content = request.queryParams("content");
   Post newPost = new Post(content);
   return new ModelAndView(model, "success.hbs");
}, new HandlebarsTemplateEngine());
...

Don’t be confused by the number of different things called “post” here! Let’s break it down:

<form action="/posts/new" method="POST"> Means: Send the form to the URL “posts/new” on the POST route. This is the other main route we use next to GET. It has nothing to do with our Post object, or even the URL. It is totally separate and fixed term we can not try and change. We could be sending something to the URL cat/mouse/game and the method would still either be POST or GET.

post("/posts/new", (request, response) -> {//do stuff} Means: When activity is detected on the URL /posts/new via the POST route, code is executed.

Post newPost = new Post(content); Means: Make a new Post object, call it newPost.

Also, in this code we're doing the following:

  • We create our HashMap named model.
  • We fetch the user-inputted post content from the form and save it into a String with the line String content = request.queryParams("content");.
  • After that, we use our Post constructor to create a new Post with the user's provided content.
  • Finally, we render a success page that informs our user they've successfully added a new post to their list.

Let's create our success.hbs template next:

my-epicodus-blog/src/main/resources/templates/success.hbs
{{#partial "content"}}

<h1>Success!</h1>

Your blog post has been saved.

<a href="/">Go Back</a>
{{/partial}}

{{> layout.hbs}}

At the moment, we are not handling showing Posts correctly. We’ll take care of that next.

Displaying Custom Objects

In order to display the posts in the index template we'll create a static method to return all Posts.

Before we do that we need to write a test! But our test will fail to even compile if we don’t at least have a dummy method with the right name, so let's move over to Post.java and add the following:

my-epicodus-blog/src/main/java/models/Post.java
public static ArrayList<Post> getAll(){
   return null;
}

public static void clearAllPosts(){
   instances.clear();
}

The second method we’ll use to clear our posts so that our tests run correctly. Now, let’s switch to our PostTest.java and add the following:

my-epicodus-blog/src/test/java/models/PostTest.java
@After
public void tearDown() {
  Post.clearAllPosts(); //clear out allll the posts before each test.
}

@Test
public void AllPostsAreCorrectlyReturned_true() {
   Post post = new Post("Day 1: Intro");
   Post otherPost = new Post ("How to pair successfully");
   assertEquals(2, Post.getAll().size());
}

@Test
public void AllPostsContainsAllPosts_true() {
   Post post = new Post("Day 1: Intro");
   Post otherPost = new Post ("How to pair successfully");
   assertTrue(Post.getAll().contains(post));
   assertTrue(Post.getAll().contains(otherPost));
}

There is NO WAY our app can malfunction if we can make these two tests pass - at least where retrieving Post’s is concerned, right? Read through those tests and make sure you understand them. Then run them and be oddly pleased they don’t pass. (If they do, you have a problem, Houston).

Return to Post.java, and implement the following property for your Post class fields and constructor:

my-epicodus-blog/src/main/java/Post.java
public class Post {

   private String content;
   private static ArrayList<Post> instances = new ArrayList<>(); // I’m new. When do I get created?

   public Post (String content){
       this.content = content;
       instances.add(this); //Also new. Can you figure out what I do and how I work?
   }

...

Okay, given the code above, can you figure out how to write our getAll() method that returns all Posts? Give it your best shot. If you get stuck, check the Cheat Sheet or the example repo linked at the end of this lesson..

Run your tests again and make sure they pass. Then go walk around the classroom and take a breath. We’re making progress!

Showing All Posts

You’re back? Okay.

Now here’s how we can grab all Posts in our index route handler:

my-epicodus-blog/src/main/java/App.java

[...]

get("/", (req, res) -> {
   Map<String, Object> model = new HashMap<>();
   ArrayList<Post> posts = Post.getAll();
   model.put("posts", posts);

   return new ModelAndView(model, "index.hbs");
}, new HandlebarsTemplateEngine());
...

Now we'll need to update index.hbs to display the Post we've just packed up in model:

my-epicodus-blog/src/main/resources/templates/index.hbs
{{#partial "content"}}

   <h1>Post list!</h1>

   {{#if posts}} <!--if posts exist, then -->
       {{#each posts }} <!--loop-->
        <p>Current post:</p>
        <p>{{ content }}</p> <!--show-->
       {{/each}}
   {{else}} <!--no posts!-->
    <p>No posts!</p>
   {{/if}}

   <p>Add a new Post:</p>

   <form action="/posts/new" method="post">
       <label for="content">Post Content</label>
       <input id="content" name="content" type="text">

       <button type="submit" class="btn btn-default">Add new blog post</button>
   </form>
{{/partial}}

{{> layout.hbs}}

Here, we use branching in Handlebars to display our post's content if a post is present. If a post is not present, the template will display No posts!. Check it and see! Add some posts! Feel good!

Make sure you walk through this dense and lengthy content before you move on. Well done!!


Example GitHub Repo for Epicodus Blog

Here is the complete Post POJO for your reference.

my-epicodus-blog/src/main/java/Post.java
package models;

import java.util.ArrayList;

public class Post {
   private String content;
   private static ArrayList<Post> instances = new ArrayList<>();

   public Post (String content){
       this.content = content;
       instances.add(this);
   }

   public String getContent() {
       return content;
   }


   public static ArrayList<Post> getAll(){
       return instances;
   }

   public static void clearAllPosts(){
       instances.clear();
   }
}