There's quite a large number of similarly structured blocks when designing a site. Imagine a catalog for an online store. It can consist of hundreds of cards, which differ only in terms of their data. When developing the old-fashioned way, without templating, all these cards will be made using the “copy-paste” method. It's not perfect, but it works. That's what it feels like until the cards change. Any addition/removal of elements will result in hours or even days of work to change the layout.
It seems unbelievable, but these sorts of situations aren't as rare as they may seem. Therefore, it's important for developers to learn how to work with repeating portions of code.
Pug uses mixins, a construct that allows you to define and insert code in any part of a layout, in order to reuse the layout. This is a great solution for creating components.
We'll use a button component as an example. This is an uncomplicated construct that can consist of just one tag. We use the keyword mixin
, naturally, to create a mixin, followed by whatever name you like for the mixin itself. While it's up to you to choose the name, it's important to give it a good name. All the HTML we need is stored inside the mixin. In this case, it's one <button>
tag:
mixin buttonOrder
button.btn.btn-order Place an order
That's it. A simple design, but with a lot of features, which we'll talk about a little later. Now you need to call this mixin to get its body into the resulting HTML code. Here, it's important to understand that the mixin itself doesn't get into the final markup in any way. To call a mixin, we use the +
symbol and the name of the mixin. After that, it'll be inserted into the HTML code.
mixin buttonOrder
button.btn.btn-order Place an order
+buttonOrder
<button class="btn btn-order">Place an order</button>
Each mixin can be called an unlimited number of times:
mixin buttonOrder
button.btn.btn-order Place an order
+buttonOrder
+buttonOrder
+buttonOrder
<button class="btn btn-order">Place an order</button>
<button class="btn btn-order">Place an order</button>
<button class="btn btn-order">Place an order</button>
If mixins just add existing markup, wouldn't it make more sense to put it in a separate file and include
it? There's an ongoing debate about this, but it's worth keeping the semantics of these constructs in mind: mixins are needed to isolate a certain piece of wrapped layout so it can be reused quickly. Connecting a file is required for adding large logical constructs, such as templates, scripts, objects, or arrays.
If you need a wrapper for a logical component, you use a mixin
, and if you want to include a template in a specific Pug file, you use the include
construct.
In Pug, mixins act not only as HTML substitutes, but also as functions that return HTML. The distinguishing feature of these mixins is that there are certain arguments used when calling them. Let's go back to the button system from the last example. There are often several different kinds of buttons on app pages. They may differ in terms of text, classes, and so on.
You can create the necessary number of mixins, but if the elements are subject to a single design system, it's not the most convenient option because if you need to change the overall style of the buttons, you'll need to edit each mixin.
Let's add the ability to pass some text to the mixin with the button. To do this, when we define a mixin, we put the arguments we need in brackets The name of the argument is completely up to you. Arguments inside mixins are interpreted as an ordinary variable, so we work with it in a fairly standard way.
mixin button(title)
button.btn.btn-order= title
Here's how you call mixins:
mixin button(title)
button.btn.btn-order= title
+button('Order')
<button class="btn btn-order">Order</button>
Mixins can also take default values. This is useful when most elements have the same content. It's sufficient to assign the value you need in the mixin definition:
mixin button(title = 'Order')
button.btn.btn-order= title
+button
+button('Place an orderч')
<button class="btn btn-order">Order</button>
<button class="btn btn-order">Place an orderч</button>
If the default value of the argument is used, there's no need to add empty parentheses when calling, unlike Sass.
Mixins are also convenient to use for templating, combining the features learned earlier. For example, outputting of users in a table. If there are several different user tables on the site, the most convenient option is to create a mixin and pass the necessary user data into it.
-
const users = [
{
name: 'Hexlet',
surname: 'McCoderson',
login: 'hexlet-code',
score: 1271
},
{
name: 'Layout',
surname: 'ODesign',
login: 'king-of-layout',
score: 1100
},
]
mixin createUserTable(usersData = [])
table
thead
tr
th Name
th Surname
th Login
th Score
tbody
each user in usersData
tr
td= user.name
td= user.surname
td= user.login
td= user.score
else
tr
td(colspan='4') No users
section
h2 The best of the best
+createUserTable(users)
h2 The worst of the worst
+createUserTable(badUsers)
Once you've created a mixin, you can use it with all sorts of different data that comes to Pug. You can go even further and check fields and value types, and validate and normalize data. We've given an empty array as the default value. This ensures that if data is missing or the variable doesn't exist, an empty array will be inserted instead.
<section>
<h2>The best of the best</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
<th>Login</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr>
<td>Hexlet</td>
<td>McCoderson</td>
<td>hexlet-code</td>
<td>1271</td>
</tr>
<tr>
<td>Layout</td>
<td>ODesign</td>
<td>king-of-layout</td>
<td>1100</td>
</tr>
</tbody>
</table>
<h2>The worst of the worst</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
<th>Login</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4">No users</td>
</tr>
</tbody>
</table>
</section>
Mixins can be used as a skeleton of the structure of a component. This can be the design of an article that'll be displayed on a page in the form of the following template:
<article class="post">
<h2>Article title</h2>
<div class="post-body">
<p>The text of the article</p>
</div>
<div class="post-author">
<p>Author of the article</p>
</div>
</article>
If there are lots of these sorts of articles on the site, it makes sense to make a template in a separate mixin. But how do you transfer the data? There are two ways:
The first method may be inconvenient because of the body of the article. After all, an article is not a one-sentence work that can be conveyed as a mixin argument. In addition, articles are often stored in a separate file, as was the case in the lesson about include
. Therefore, the best option in this situation might be to pass the text directly to the mixin using nesting. Let's call the article
mixin by passing the title and author of the article as arguments, and putting the inclusion of the file in the body.
+article('Article title', 'Article author')
include:markdown-it main.md
The main question is where the result of the include:markdown-it main.md
will be placed. Actually, it won't go anywhere! The mixin doesn't know what to do with this data, even though it can accept it. To insert the nested data in the mixin, we the block
construct, which stores what has been nested in the mixin. You can think of it as an “invisible argument,” which is always there but not explicitly declared. Now you can implement the logic of the mixin:
mixin article(name, author)
.post
h2= name
.post-body
block
.post-author= author
+article('Article title', 'Article author')
include:markdown-it main.md
<div class="post">
<h2>Article title</h2>
<div class="post-body">
<p>The article text from the main.md file</p>
</div>
<div class="post-author">Article author</div>
</div>
Complete the mixin with the addition of an article to the page:
The Hexlet support team or other students will answer you.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.
Programming courses for beginners and experienced developers. Start training for free
Our graduates work in companies:
Sign up or sign in
Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.