No description, website, or topics provided.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
tc-flashcards-views-begin
tc-flashcards-views-final
tmpl_example
README.md
crud.md

README.md

Express Views 👀

Learning Objectives

  • Describe the concept of "templating" at a high level
  • Create templates and render views in an Express application
  • Apply EJS syntax to insert data into HTML
  • Render partials and iterate over data in views
  • Analyze pages to spot what elements could be abstracted into partials

What is a view, anyway?

We made a pretty cool app yesterday that serves flashcards in JSON format based on what route you visit.That's where express views come in.

The term "view" in the context of a full-stack application refers to what a visitor to the site sees when the page loads. Views are how the information in the database -- the model -- is represented in the browser.

Render Views (rather than responding to requests with JSON)

Adding the view engine is a fairly straightforward process.

  • Install the templating engine of choice. We're using EJS, so the command is npm install ejs --save.
  • Create the views directory right inside thundercats-tc-flashcards-begin -- not in any of the subfolders. Inside it, create a file called index.ejs. This will be blank for now.
  • In between the port setup and the static file setup in server.js, we're going to add two lines, one to tell the app where to look for our templates and the other telling it what kind of template to expect.
// where to look for the template:
//       | what we're setting
//       V           v where to look for the views
app.set('views', path.join(__dirname, 'views'));
// what kind of template:
//       | what we're setting
//       V               v what kind of view engine to expect
app.set('view engine', 'ejs');

Now what?

res.render

Yesterday, we used res.send, which is a method on the response object that allows us to send data back to the client. res.render is a similar concept, except it allows us to first put all that data into a template.

// what file to look for (`views/[whatever].ejs`,
// in this case `views/index.ejs`)
//            V        v an object that contains the data we're sending
res.render('index');

This can go in place of res.sendFile in the app.get function. (We also have to delete index.html from our public folder. Otherwise the app will serve it instead. Relevant stackoverflow answer here)

In server.js, for the root route:

app.get('/', (req, res) => {
  res.render('index');
});

This can go in place of res.sendFile in the app.get function. (We also have to delete index.html from our public folder. Otherwise the app will serve it instead. Relevant stackoverflow answer here)

In server.js, for the root route:

app.get('/', (req, res) => {
  res.render('index');
});

You Do: Make a simple Flashcards Index View

  • Set the views path and view engine
  • Create the views directory and add index.ejs and git it a short message
  • Update the root route to render the Index View
  • Delete public/index.html

View engines (a.k.a templating engines)

We're probably all familiar with the term "template" -- a document that already has some details in place, but needs to have the rest of them filled in. Templating engines in JavaScript allow us to fill in the blanks in our HTML with JavaScript without having to do a ton of string concatenation or DOM manipulation.

For example, consider the following two blocks of code, which have more or less the same end result:

// adding a paragraph with DOM methods
const myName = 'marty mcfly';
const myDiv = document.querySelector('#mydiv');
const newParagraph = document.createElement('p');
newParagraph.innerHTML = `Hello, my name is ${myName}.`;
const newLink = document.createElement('a');
newLink.setAttribute('href', `/${myName}`);
newLink.innerHTML = 'Learn more!';
newParagraph.appendChild(newLink);
myDiv.appendChild(newParagraph);
// adding a paragraph with string concatenation
const myName = 'marty mcfly';
const myDiv = document.querySelector('#myDiv');
let nameParagraph = '<p>';
nameParagraph +=       `Hello, my name is ${myName}.`;
nameParagraph +=       `<a href='/${myName}'>Learn More</a>`;
nameParagraph +=    '</p>';
myDiv.innerHTML = nameParagraph;

. . . And a third making use of ES6 templates strings:

const myName = 'marty mcfly';
const mydiv = document.querySelector('#myDiv');
let nameParagraph = `
    <p>
        Hello, my name is ${myName}.
        <a href='/${myName}'>Learn More</a>
    </p>`;
myDiv.innerHTML = nameParagraph;

None of these are particularly fun. The first is tedious, and the second -- while looking like HTML, more or less -- just feels bad. Having a Node backend gives us the ability to use a templating engine, which is one solution to this problem.

We'll be using a templating engine called EJS.

EJS stands for Effective JavaScript (or Embedded JavaScript), and it lets us inject JavaScript directly into our HTML by surrounding it with special marker tags.

Here's what the above blocks of code would look like in EJS:

<% const myName = 'marty mcfly'; %>
<div id='mydiv'>
  <p>
    Hello, my name is <%= myName %>.
    <a href='/<%= myName %>'>Learn more</a>
  </p>
</div>

Short and sweet, right? Notice the <% %> and <%= %> tags in the above block. These are what allow us to inject JavaScript into our HTML.

  • <% %> allows us to declare variables, do loops, do conditionals, etc. Normal JavaScript-y things.
  • <%= %> allows us to output the values of variables.
  • There are a few other clown tag variations. You can check them out in the EJS docs.
  • NOTE: <% %> and <%= %> are only evaluated server-side, and therefore can't be used for updating the DOM after page load

Files that use EJS have to have the extension .ejs. For example: index.ejs.

Now update the index view with a message from the controller

app.get('/', (req, res) => {
  res.render('index', { message: 'How did this get here?'});
});

And include the message in the view like so:

<p>        
    <%= message %>
</p>

Putting it all together with the Show route

flashcardsController.show = (req, res) => {
  Flashcard.findById(req.params.id)
    .then(flashcard => {
      res.render('show', { flashcard: flashcard });                                                                                                                                     
    }).catch(err => {
      console.log(err);
      res.status(500).json(err);
    });
};

Now add a Show view that displays the flashcard

<div class="flashcard-single-container">
    <div class="flashcard">
        <h3><%= flashcard.question %></h3>
        <p><%= flashcard.answer %></p>
        <div class="meta">
            <div class="category">
                Category: <%= flashcard.category %>
            </div>
            <div class="difficulty">
                Difficulty: <%= flashcard.difficulty %>
            </div>
        </div>
    </div>
</div>

You Do: Add the Show View

  • Update the show route handler in the flashcardController to render a view and pass the flashcard object into the view
  • Create a show.ejs view that displays some data about the flashcard
  • Profit!

Templating with loops

One of the powerful things about EJS is that it allows us to adjust the page layout based on what data is passed to it. Let's create a view for all of our movies, the /movies route.

Let's create the index view for Flashcards using a loop in the template:

<div class="flashcard-container">
    <% flashcards.forEach(flashcard => { %>
        <div class="flashcard">
            <div class="meta">
                <span class="category">Category: <%= flashcard.category %></span>
                <span class="difficulty">Difficulty: <%= flashcard.difficulty %></span>
            </div>
            <h3><%= flashcard.question %></h3>
            <a href="/flashcards/<%= flashcard.id %>">See the answer!</a>
        </div>
    <% }) %>
</div>

And update the Flashcards controller:

flashcardsController.index = (req, res) => {
  Flashcard.findAll()                                                                                                                                                                   
    .then(flashcards => {
      res.render('index', {
          flashcards: flashcards,
      });
    }).catch(err => {
      console.log(err);
      res.status(500).json(err);
    });
};

Modularizing our EJS

Let's take a look at the views files we have so far. What do they all have in common? We can abstract some of those out using partials.

Partials are pretty much what the name sounds like -- parts of your HTML that can be inserted into any document. For example, if we were to abstract the HTML5 boilerplate to its own boilerplate.ejs file, we could put it at the top of any file by saying:

<% include ./partials/boilerplate %>

OR

<% include ../partials/boilerplate %>

(Depending on where in the views directory you are.)

We can also abstract the </body></html> tags to their own partial, for neatness: end.ejs.

You can even nest partials inside of other partials. For example, if we decided to make a navigation.ejs partial and add a menu bar, we could include it in the boilerplate.ejs partial.

🚀 Rounding out our views with Index.js using a loop (or iterator)

Get caught up with where we're at so far! By the end of this lab, you should have:

  • views/index.ejs
  • views/partials/boilerplate.ejs and views/partials/end.ejs
  • BONUS: Try creating a navigation partial or a footer partial. Include them in the boilerplate or end partials, respectively.