Eta View Templates
Learning objectives
- Knows what view templates are and knows the basics of Eta.
- Knows how templates are used and can use a template when responding to a request.
Starting to use view templates
Let's start with a simple example that adds content from the server to a view that is then sent to the user. First, create a project with a file app.js
. Then, add a directory called views
in the same folder with the app.js
and add a file called index.eta
to the views
-folder.
The directory structure should be as follows.
tree --dirsfirst
.
├── views
│ └── index.eta
└── app.js
1 directory, 2 files
Now, copy the following content to the index.eta
file that is in the views
-folder. The file index.eta
is a view template that will be used for showing content from the server to the user.
<h1>
<%= it.title %>
</h1>
Copy the following content to the app.js
file.
import { serve } from "https://deno.land/std@0.222.1/http/server.ts";
import { configure, renderFile } from "https://deno.land/x/eta@v2.2.0/mod.ts";
configure({
views: `${Deno.cwd()}/views/`,
});
const responseDetails = {
headers: { "Content-Type": "text/html;charset=UTF-8" },
};
const data = {
title: "Hello world!",
};
const handleRequest = async (request) => {
return new Response(await renderFile("index.eta", data), responseDetails);
};
serve(handleRequest, { port: 7777 });
The configure
function -- used in the lines 4 to 6 -- is used to set the directory from which the view templates are retrieved. In the above example, view templates will be retrieved from the views
folder under the current working directory (retrieved using Deno's function call Deno.cwd()
).
The function renderFile
is given the name of the view template file that should be processed and the data that should be added to the view template during processing. Effectively, the function renderFile
loads a view template and injects given data to the template, returning the content as string. The function is asynchronous, so it is called with the await
keyword -- to accommodate for this, we also have to set the handleRequest
function as asynchronous using the async
keyword.
Now, run the app.js
using the command deno run --allow-net --allow-read app.js
.
deno run --allow-net --allow-read app.js
When we access the site, the response is as follows.
curl localhost:7777
<h1>
Hello world!
</h1>%
In a browser, if we access the address http://localhost:7777
, we see a page with a h1
-element that contains the text Hello world!
.
In more detail, when we access the site, the server processes the request. The server calls the function renderFile
, which loads a file called index.eta
from the folder views
, adding the data in the given parameter data
to the loaded file. In this case, the <%= it.title %>
in the view template is replaced with Hello world!
. Finally, the server returns a document where the template values have been changed to match the data.
In Eta, the prefix it
is used to denote variables passed from the server to the .eta
-files.
Note that this processing -- i.e. replacing variable values -- happens on the server. The browser (or the client) is sent a document in which the template data has been already replaced with actual values.
Changing content
In the previous example, the data added to the view template was always the same.
Next, we create an application that keeps track of visits to the page. Now, instead of defining the data-object that is passed to the renderFile
-function outside the function that handles requests, we define the object within the function.
In the example below, the data object contains both a count variable that is incremented for each request and a string representing the title of the page.
import { serve } from "https://deno.land/std@0.222.1/http/server.ts";
import { configure, renderFile } from "https://deno.land/x/eta@v2.2.0/mod.ts";
configure({
views: `${Deno.cwd()}/views/`,
});
const responseDetails = {
headers: { "Content-Type": "text/html;charset=UTF-8" },
};
let visitCount = 0;
const handleRequest = async (request) => {
const url = new URL(request.url);
if (url.pathname === "/") {
visitCount++;
const data = {
count: visitCount,
title: "Counter!",
};
return new Response(await renderFile("index.eta", data), responseDetails);
} else {
return new Response("Not found", { status: 404 });
}
};
serve(handleRequest, { port: 7777 });
Now, when we visit the page, we see the following content.
curl localhost:7777
<h1>
Counter!
</h1>%
Unsurprisingly, the count
variable is not shown on the page as the index.eta
file does not yet contain a place into which the value of the variable should be added to.
Let's change the template so that it contains a place for the count
-variable.
<h1>
<%= it.title %>
</h1>
<p>
The current count is <%= it.count %>.
</p>
Now, when we access the site, we see that the count is added to the page and incremented between each request.
curl localhost:7777
<h1>
Counter!
</h1>
<p>
The current count is 1.
</p>%
curl localhost:7777
<h1>
Counter!
</h1>
<p>
The current count is 2.
</p>%
The previous example could as well have been implemented so that the data object would still reside outside the function responsible for handling the requests. In such a case, we would adjust the count property of the object on each request. This would function as follows.
import { serve } from "https://deno.land/std@0.222.1/http/server.ts";
import { configure, renderFile } from "https://deno.land/x/eta@v2.2.0/mod.ts";
configure({
views: `${Deno.cwd()}/views/`,
});
const responseDetails = {
headers: { "Content-Type": "text/html;charset=UTF-8" },
};
const data = {
count: 0,
title: "Counter!",
};
const handleRequest = async (request) => {
const url = new URL(request.url);
if (url.pathname === "/") {
data.count++;
return new Response(await renderFile("index.eta", data), responseDetails);
} else {
return new Response("Not found", { status: 404 });
}
};
serve(handleRequest, { port: 7777 });