Leaf template extension issues

Hi there!

I am just starting my first project with vapor and leaf. All sound nice but I am running into some early issues when rendering embedded templates.

It just does not load the content of my child templates, tell me please if I am doing something wrong here:

layout.leaf (this is the main template)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>#(title)</title>

  <!-- Styles -->
  <link href="/styles/parallax.css" rel="stylesheet" type="text/css">
  <link href="/styles/card.css" rel="stylesheet" type="text/css">
  <link href="/styles/menu.css" rel="stylesheet" type="text/css">
</head>

<body>
  #import("head")
  #import("content")
</body>

</html>

head.leaf

#extend("layout"):
    #export("head"):
        <div class="header">
        <div class="logo-head">
            <a>
            <div class="text-logo">
                <p>A</p>
            </div>
            </a>
        </div>
        <div class="topnav">
            <a href="/">Menu1</a>
            <a>Menu2</a>
            <a href="/about">About</a>
        </div>
    #endexport
#endextend

main.leaf

#extend("layout"):
    #export("content"):
        </div>
            <div class="parallax-container parallax1">
            <div class="card-container">
            <div class="presentation-card" id="gradient-start">
                <p>Lorem ipsum dolor sit amet</p>
            </div>
            </div>
            </div>

            <div class="parallax-container parallax1 parallax2"></div>

            <div class="presentation-card" id="gradient-end">
                <p>Lorem ipsum dolor sit amet</p>
                <p>consectetur adipiscing elit</p>
                <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
            </div>

            <div class="parallax-container parallax-align-content-top parallax1 parallax3">
            <div class="card-container">
            <div class="presentation-card" id="gradient-end">
                <p>Lorem ipsum dolor sit amet</p>
                <p>consectetur adipiscing elit</p>
                <p>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
            </div>
            </div>
            </div>
    #endexport
#endextend

Hello!
It’s interesting that both head and main extend layout. Which template are you returning in your route handler? What is the rendered html on the page?

Hi!

I understand that the extension of these templates as modules, right? so I can contribute to several sections from different children templates. Do I understand this wrong?

Answering your questions:

  1. I am returning the layout.leaf template. This is my route file
import Fluent
import Vapor

func routes(_ app: Application) throws {
    app.get { req async throws in
        try await req.view.render("layout", ["title": "CoatchingApp Prototype"])
    }

    try app.register(collection: TodoController())
}
  1. This is the html rendered:
<html lang="en"><head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Title</title>

  <!-- Styles -->
  <link href="/styles/parallax.css" rel="stylesheet" type="text/css">
  <link href="/styles/card.css" rel="stylesheet" type="text/css">
  <link href="/styles/menu.css" rel="stylesheet" type="text/css">
</head>

<body>
 </body>
</html>

I have another example with just one import and one extension, and the result is the same, whenever the import is no html is included.

about.leaf

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>#(title)</title>

  <!-- Styles -->
  <link href="/styles/parallax.css" rel="stylesheet" type="text/css">
  <link href="/styles/card.css" rel="stylesheet" type="text/css">
  <link href="/styles/menu.css" rel="stylesheet" type="text/css">
</head>
<body>
    <div class="header">
        <div class="logo-head">
            <a>
            <div class="text-logo">
                <p>A</p>
            </div>
            </a>
        </div>
        <div class="topnav">
            <a href="/">Home</a>
            <a>Coaching</a>
            <a href="/about">About</a>
        </div>
    </div>
    <div class="content">
        <h1>Who we are</h1>
        #import("team")
    </div>
</body>
</html>

This is the children template, team.leaf

#extend("about"):
    #export("team"):
        <div class="team">
            <h1>Team</h1>
        </div>
    #endexport
#endextend

This is the rendering

<html lang="en"><head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title></title>

  <!-- Styles -->
  <link href="/styles/parallax.css" rel="stylesheet" type="text/css">
  <link href="/styles/card.css" rel="stylesheet" type="text/css">
  <link href="/styles/menu.css" rel="stylesheet" type="text/css">
</head>

<body style="background-color: black; display: block; color: white;">
    <div class="header">
        <div class="logo-head">
            <a>
            <div class="text-logo">
                <p>A</p>
            </div>
            </a>
        </div>
        <div class="topnav">
            <a href="/">Home</a>
            <a>Coaching</a>
            <a href="/about">About</a>
        </div>
    </div>
    <div class="content">
        <h1>Who we are</h1>
        
    </div>
</body>
</html>

As you can see where the imports are there is no html from the child template that's extending.

Think about in similar terms as subclassing a class. With this analogy in mind, layout.leaf is the "superclass" while head.leaf and main.leaf are each "subclasses".

So I believe you are thinking about backwards. Try returning main.leaf from your handler and see what happens.

1 Like

I got you!

Thanks very much, that worked like a charm.

I must say then either the documentation is misleading or I read it wrong at vapor.docs

Anyhow, much appreciated.

I was thinking on this hierarchy and I have the next question:

What if I want to make a modular template? My idea was the next (when I understood this backwards):

layout.leaf (main template)

<!DOCTYPE html>
<html lang="en">

<head>
  <title>#(title)</title>

  <!-- Styles -->
  #import("styles")
  <!-- Scripts -->
  #import("scripts")
</head>

<body>
  #import("header")
  #import("content")
  #import("footer")
</body>

</html>

So other view (children) contribute layout.leaf:

header.leaf

#extend("layout"):
    #export("header"):
        <div class="header">
            ...
        </div>
    #endexport
#endextend

home.leaf

#extend("layout"):
    #export("content"):
        <div class="main">
            ...
        </div>
    #endexport
#endextend

footer.leaf

#extend("layout"):
    #export("footer"):
        <div class="footer">
            ...
        </div>
    #endexport
#endextend

How I can do this now?
I understand if I render home.leaf I won't see the content of either header.leaf or footer.leaf.

What you might be looking for is #extend(<template>), note this does not have a closing #endextend This will copy the content of the template inline.

layout.leaf

<!DOCTYPE html>
<html lang="en">

<head>
  <title>#(title)</title>

  <!-- Styles -->
  #extend("styles")
  <!-- Scripts -->
  #extend("scripts")
</head>

<body>
  #extend("header")
  #import("content")
  #extend("footer")
</body>

</html>

Then, for example in header.leaf, you would simply define your html (no extends, exports, etc)

<div class="header">
    ...
</div>

The thing to remember is #export and #import store and retrieve content from the context whereas #extend incorporates content from another template.

1 Like

Thank @jagreenwood!

You helped me a lot, I couldn't see this way through the documentation.

I will play with it a see if the structure in my mind works.

Much appreciated.