There is 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, we make all these cards using the copy-paste method. It is not perfect, but it works. That is 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 situations are not as rare as they may seem. Therefore, developers need 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 to reuse it. It is a handy solution for creating components.
We will use a button component as an example. It is an uncomplicated construct that can consist of just one tag. We use the keyword mixin
to create a mixin, followed by whatever name you like. We store all the HTML we need inside the mixin. In this case, it is a <button>
tag:
mixin buttonOrder
button.btn.btn-order Place an order
Here we see a simple design with many features, which we will discuss later. Now you need to call this mixin to get its body into the resulting HTML code. We should understand that the mixin does not get into the final markup in any way. To call a mixin, we use the +
symbol and the mixin's name. After that, Pug inserts into the HTML code:
mixin buttonOrder
button.btn.btn-order Place an order
+buttonOrder
<button class="btn btn-order">Place an order</button>
We can call each mixin 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>
Mixins add existing markup, so would it make more sense to put it in a separate file and include
it? There is an ongoing debate about this, but it is worth keeping the semantics of these constructs in mind.
Mixins are needed to isolate a piece of the wrapped layout so we can reuse it 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.
Mixins as functions
In Pug, mixins act not only as HTML substitutes but also as functions that return HTML. In these mixins, there are arguments used when calling them.
Let us 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 is not the most convenient option because if you need to change the overall style of the buttons, you will need to edit each mixin.
Let us 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 up to you. The program interprets arguments inside mixins as an ordinary variable, so we work with it in a 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. It is useful when most elements have the same content. It is sufficient to assign the value needed 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 we use the argument default value, there is no need to add empty parentheses when calling, unlike in 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 have created a mixin, you can use it with all sorts of data that comes to Pug. You can go further and check fields and value types, validate and normalize data. We have given an empty array as the default value. It ensures that if data is missing or the variable does not exist, we insert an empty array 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>
Passing nested content to the mixin
We can use mixins as a skeleton of the structure of a component. It can be the design of an article 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 many article types on the site, we can make a template in a separate mixin. But how do you transfer the data? There are two ways:
- Define the arguments, and pass them to the mixin separately
- Somehow pass the whole article at once
The first method may be inconvenient because of the body of the article. After all, articles are not a one-sentence work we can convey as a mixin argument. In addition, the articles are often stored in a separate file, as we did in the lesson about include
. Therefore, the best option might be to pass the text directly to the mixin using nesting.
Let us call it the article
mixin. We pass the title and author as arguments and put 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. It will not go anywhere! The mixin does not know what to do with this data, even though it can accept it. To insert the nested data in the mixin, we use the block
construct, which stores what has been nested in the mixin. You can think of it as an invisible argument. It 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>
Additional assignment
Complete the mixin with the addition of an article to the page:
- Check that there is content to display. If there is not, print an error
- Add default values for the arguments. Use
Hexlet
as the article name andMcCoderson
as the author
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
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.