Layouts and Partials
Learning objectives
- You know what layouts and partials are and you know how to use them in your application.
In web development, the term layout refers to how the elements of a web page are arranged on the screen. In web development, layouts are defined using both HTML and CSS (Cascading Style Sheets). For now, we refrain from using CSS, as we look into it towards the end of the course. Here, we use the term layout in the context of view templates.
Layouts
When using view templates, we can define a generic layout file that outlines the structure of the web page, and more specific content files, which contain the actual content of individual web pages. The content files are rendered to the layout file, and the result is a complete web page.
Let's start with a new project that has the following project structure. We have a file app.js
in the root folder with a folder templates
. The folder templates
contains a file index.eta
and a folder layouts
. The folder layouts
contains a file layout.eta
.
tree --dirsfirst
.
├── templates
│ ├── layouts
│ │ └── layout.eta
│ └── index.eta
└── app.js
The file layout.eta
will be used as a layout file. A layout file is a normal Eta document with HTML and an Eta tag <%~ it.body %>
. When the layout file is used, the <%~ it.body %>
tag will be replaced with content from the file that uses the layout file.
The following example -- layout.eta
-- defines a layout with boilerplate HTML (i.e. common HTML content). The Eta tag <%~ it.body %>
is within the body
element, which means that content from the file that uses the layout will be placed there.
<!DOCTYPE html>
<html>
<head>
<title>Hello layouts!</title>
</head>
<body>
<%~ it.body %>
</body>
</html>
Layout files are used from view templates using Eta's layout
function. The function call receives the path to the layout file as a parameter, and it is called from the top of the view template file.
As an example, if the index.eta
contains the following content, rendering the index.eta
file will lead to a situation where the content in index.eta
below the <% layout("/layouts/layout.eta") %>
will be rendered to the place in layout.eta
specified with the tag <%~ it.body %>
.
The location where Eta looks for the files start from the folder given as the
views
parameter to Eta configuration, given that the paths are given as absolute paths (starting with/
). If the paths are relative (e.g. starting with./
,), the locations are relative to the file in which the path is defined. We use the absolute locations.
<% layout("/layouts/layout.eta") %>
<h1>The heading!</h1>
<p>The content!</p>
Let's assume that the contents of the app.js
is as follows. That is, the app.js
is a normal web application that renders a view template (in this case, index.eta
).
import { Eta } from "https://deno.land/x/eta@v3.4.0/src/index.ts";
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
const eta = new Eta({ views: `${Deno.cwd()}/templates/` });
const app = new Hono();
app.get("/", (c) => c.html(eta.render("index.eta")));
Deno.serve(app.fetch);
When we run the application (using deno run --allow-net --allow-read --watch app.js
) and access the application, we see that the content in index.eta
has been included to the layout file layout.eta
, and the resulting content is received as a response to the request.
And, when we access the application, we see the following output.
curl localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>Hello layouts!</title>
</head>
<body>
<h1>The heading!</h1>
<p>The content!</p>
</body>
</html>%
As we are still working with view templates -- layouts are just a functionality of view templates -- we can add data from the server. Let's change both the layout and the view template so that they will contain data from the server. In the layout, we use a title from the server if it exists, while in the view template we use a heading from the server, similarly if it exists.
<!DOCTYPE html>
<html>
<head>
<title><%= it.title ?? "Hello layouts!" %></title>
</head>
<body>
<%~ it.body %>
</body>
</html>
<% layout("/layouts/layout.eta") %>
<h1><%= it.heading ?? "The heading!" %></h1>
<p>The content!</p>
The title from the server is My Title
and the heading from the server is My Heading
.
import { Eta } from "https://deno.land/x/eta@v3.4.0/src/index.ts";
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
const eta = new Eta({ views: `${Deno.cwd()}/templates/` });
const app = new Hono();
const data = {
title: "My Title",
heading: "My Heading",
};
app.get("/", (c) => c.html(eta.render("index.eta", data)));
Deno.serve(app.fetch);
Now, when when we run the application and access it, the output will contain the data from the server.
curl localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>My Title</title>
</head>
<body>
<h1>My Heading</h1>
<p>The content!</p> </body>
</html>%
Partials
Partials are used for dividing view template content and functionality into logical entities. They are useful, for example, for easier content management, allowing the developer to work on a single logical part of the HTML code at a time.
Let's continue with the previous example with a layout file and a view template, and create a partial for a menu. Let's add a folder called partials
to the folder templates
. Let's further add a file called menu.eta
to the partials
-folder.
The structure of the project is now as follows.
tree --dirsfirst
.
├── templates
│ ├── layouts
│ │ └── layout.eta
│ ├── partials
│ │ └── menu.eta
│ └── index.eta
└── app.js
Let's modify the menu so that it will contain three links. The links (and the content of the menu.eta
) will be as follows.
<nav>
<ul>
<li><a href="/">Index</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact us</a></li>
</ul>
</nav>
Partial files are included to view templates using the include
function, which takes a path to a partial file as a parameter. The syntax for using the function is as follows; <%~ include("path-to-partial"); %>
-- note that when using the function layout
, we do not use a tilde ~
, but with include
we do use one.
Let's add the menu partial to the layout.
<!DOCTYPE html>
<html>
<head>
<title><%= it.title %></title>
</head>
<body>
<%~ include("/partials/menu.eta") %>
<%~ it.body %>
</body>
</html>
Now, when we make a request to our application, the response is as follows.
curl localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>My Title</title>
</head>
<body>
<nav>
<ul>
<li><a href="/">Index</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact us</a></li>
</ul>
</nav>
<h1>My Heading</h1>
<p>The content!</p> </body>
</html>%
The application can also, based on the requested path, change the template that is being used. This is demonstrated in the next programming assignment.