7 Layouts for our templates

Now that we have a handful of templates - list.html, detail.html, cities.html, and maybe some more if you did the zipcodes thing - we’re going to want to get a little more organized.

I’ve been thinking about adding some links to the navigation up at the top of the page so visitors can browse between different pages. One link for the cities page, one for the ZIP codes page, one for the home page, etc etc etc.

The problem is that I’m lazy, and there’s at least three different pages to edit. As our site grows and grows, we’re going to have more templates and mote things to edit and I don’t want to edit seventeen HTML pages every time I want to make one change to the header or footer.

7.1 Layouts

Layouts are separate HTML pages that might have a header and a footer, but nothing in the middle. When we use our template, we say “hey! go find that layout, and wrap me in its warm embrace.” And thus our template automatically gets the header and footer!

Let’s try a test with one of our schools. First, edit detail.html to remove everything outside of the bits that are about the school (or will eventually be about the school). It should look like this:

<div class="jumbotron">
  <div class="container">
    <h1 class="display-4">{{ school.SCHOOLNAME.title() }}</h1>
    <p class="lead">{{ school.ADDRESS.title() }}, {{ school.city.title() }}</p>
  </div>
</div>
<div class="container">
    <p></p>
    <p></p>
    <p></p>
</div>

If you visit http://localhost:5000/schools/X068 now, you’ll be horrified. All of our hard work!

But let’s go and make a new file in the same templates folder called layout.html.

<!doctype html>
<html>
  <head>
    <title>My incredible web site.</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  </head>
  <body>
      <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="#">Schools</a>
      </nav>
      {% block content %}{% endblock %}
    </div>
  </body>
</html>

It’s everything from our previous page we got rid of, but with the addition of {% block content %}{% endblock %}. That little piece means “if you have a chunk of code you call content, I’ll put it here.”

Refresh http://localhost:5000/schools/X068, note that nothing’s changed, and realize we need to

  1. Tell our detail.html to call the layout
  2. Identify the parts of detail.html that should be the content

To tell the template to use a layout, you put a {% extends "layout.html" %} up top. Then you use that weird {% block... thing again to identify where the content is.

Our updated detail.html will look like this:

{% extends 'layout.html' %}
{% block content %}
<div class="jumbotron">
  <div class="container">
    <h1 class="display-4">{{ school.SCHOOLNAME.title() }}</h1>
    <p class="lead">{{ school.ADDRESS.title() }}, {{ school.city.title() }}</p>
  </div>
</div>
<div class="container">
    <p></p>
    <p></p>
    <p></p>
</div>
{% endblock %}

Pretty fun, right? Amazingly fun? Incredibly fun? And it gets even better!

Every single one of our pages has that “jumbotron” thing in it, with the big text and the small text. Sometimes it’s a title like “Cities” or sometimes “P.S. 068 Bronx” or sometimes the count of the schools in a zip code.

What happens if we wanted to change the style of our big headers, and replace the jumbotron with something a little more elegant? Well, we’d have to go through and edit every one of those pages with the new style, which wouldn’t be fun at all.

With our current template, we called everything on the page content, which then filled in the content section of layout.html. But it doesn’t have to be that way! What if we had more sections, things like headline and subhead and anything else we could think of?

Your detail.html might look like this, with headline and subhead sections:

{% extends 'layout.html' %}
{% block headline %}
  {{ school.SCHOOLNAME.title() }}
{% endblock %}
{% block subhead %}
  {{ school.ADDRESS.title() }}, {{ school.city.title() }}
{% endblock %}
{% block content %}
<div class="container">
    <p><strong>School type:</strong> {{ school.SCH_TYPE }}</p>
    <p></p>
    <p></p>
</div>
{% endblock %}

Which then get imported into our layout.html:

<!doctype html>
<html>
  <head>
    <title>My incredible web site.</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
  </head>
  <body>
      <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="#">Schools</a>
      </nav>
      <div class="jumbotron">
        <div class="container">
          <h1 class="display-4">{% block headline %}{% endblock %}</h1>
          <p class="lead">{% block subhead %}{% endblock %}</p>
        </div>
      </div>
      {% block content %}{% endblock %}
    </div>
  </body>
</html>

I added the school type just so we could be sure that content was filled in.

Now we can go through each of our other pages and do the same thing, using the layout and breaking the content into headline, subhead and content sections.

Our list.html file gets chunked up like this:

{% extends 'layout.html' %}
{% block headline %}
  There are {{ "{:,}".format(count) }} schools in {{ location.title() }}
{% endblock %}
{% block subhead %}
  It's pretty cool.
{% endblock %}
{% block content %}
<div class="container">
    <p>I'm amazed and frankly you should be, too.</p>
    {% for school in schools %}
      <p><a href="/schools/{{ school.LOC_CODE }}">{{ school.SCHOOLNAME }}</a></p>
    {% endfor %}
</div>
{% endblock %}

And in city.html we don’t have a subhead, so we can go ahead and completely ignore it:

{% extends 'layout.html' %}
{% block headline %}
  Cities
{% endblock %}
{% block content %}
<div class="container">
    {% for city in cities %}
    <p><a href="/city/{{ city.lower().replace(' ', '-') }}">{{ city.title() }}</a></p>
  {% endfor %}
</div>
{% endblock %}

7.1.1 And a final fun trick

If you pay attention to the top of your browser, the title of the page, you’ll notice it’s always the same - “My incredible web site.” That’s both useless and narcissistic, so it’d be great to change it.

A benefit of having moved to a template with variables for things like the header, subhead and content, is that we can re-use the headline in both the jumbotron and* the title of the page.

Unfortunately, Jinja2 is really weird, so we can’t just use the {% block... syntax again. Instead, we do this:

<head>
  <title>{{ self.headline() }}</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
</head>

If self.headline() gets the headline, why don’t we use that all the time instead of the werid {% block... syntax? I have no idea, I just follow directions.

Visit a few different pages and rejoice! Titles galore.

7.2 Adding in our navigation

I almost forgot why we were doing this! Navigation, navigation, navigation.

Let’s go ahead and edit our layout to add a new link to the header, following the instructions from Bootstrap. Edit the <nav> section of layout.html like so:

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <a class="navbar-brand" href="/">Schools</a>
  <ul class="navbar-nav">
    <li class="nav-item active">
      <a class="nav-link" href="/">Home</span></a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="/city">Cities</a>
    </li>
  </ul>
</nav>

Navs in Bootstrap are pretty fancy beasts, you can read the docs for more info.

7.2.1 A dropdown for cities

We don’t have that many cities, right? Why can’t we just have a dropdown that lists them all?

If you want to go that route, I recommend checking out context processors on the FLask end and navigation dropdowns on Bootstrap. You’ll also need to add the Bootstrap JavaScript to make them work, which you can find on their homepage under the BootstrapCDN heading.