Add items to an array in Nunjucks
To add items in Nunjucks, use the.push()
function.
{%set arr=[1,2]%}
{%set arr=(arr.push(3), arr)%}
Final array:
arr = [1,2,3]
Unfortunately, I did not found any references in the officialNunjucks documentation for this useful function 🤷🏻♀️
{%set animals=['cat 🐱','dog 🐶','lion 🦁']%}
{%set domesticAnimals=[]%}
{%for animalin animals%}
{%if animal!=='lion'%}
{%set domesticAnimals=(domesticAnimals.push(animal), domesticAnimals)%}
{%endif%}
{%endfor%}
Final array:
domesticAnimals = ['cat 🐱', 'dog 🐶']
🧨!important
If you use
{% set .... %}
inside a for-loop block, pay attention to have defined itoutside before entering the loop.
I wrote a post about it:📒 Nunjucks scoped variable declarations
📚More info
Docs aboutTwig 'push' filter. Note that this filter is not present into the officialTwig documentation 🤷🏻♀️
Git flow initialize
To inizializegit flow with default branches configuration, run
git flow init-d
It will create a git flow structure to your project
Initialized empty Git repositoryin /Users/giulia/Sites/giulia/test/.git/
Using default branch names.
No branches exist yet. Base branches must be created now.
Branch namefor production releases:[main]
Branch namefor"next release" development:[develop]
.git/hooks/post-commit: line8: git-stats:command not found
How to name your supporting branch prefixes?
Feature branches?[feature/]
Bugfix branches?[bugfix/]
Release branches?[release/]
Hotfix branches?[hotfix/]
Support branches?[support/]
Version tag prefix?[]
Hooks and filters directory?[/Users/giulia/Sites/giulia/test/.git/hooks]
📚 More info
Add multiple classes in pug
Pug, formerly known asJade, is a template engine thas uses a JavaScript render engine. Sometimes we have to add a class conditionally based on a variable, here I go with some tricky solutions I found.
One condition to check#
The easiest way to check a class inpug is using theternary operator
-var cond1=true
.c-component(class=cond1?'cond1-TRUE':'cond1-FALSE')
HTML output
<divclass="c-component cond1-true"></div>
But there are other two ways to write the same exact condition in pug:
-var cond1=true
//- (1)
.c-component(class={'cond1-true': cond1===true})
//- (2)
.c-component(class={cond1True: cond1===true})
Important#
🧨!important
(1) kebab-case classcond1-true
must be wrapped in quotes
(2) camelCase classcond1True
can skip wrapper quotes
HTML output
<divclass="c-component cond1-true"></div>
<divclass="c-component cond1True"></div>
More than one condition to check#
If we have to checktwo condintions, we can go also in this case with the ternary operator to choose which classes we want.
We can write the conditionals in three ways:
- using two
class
attributes separated by space
(class=cond1 ? 'cond1-TRUE' : 'cond1-FALSE' class=cond2 ? 'cond2-TRUE' : 'cond2-FALSE')
- using two
class
attributes separated by comma
(class=cond1 ? 'cond1-TRUE' : 'cond1-FALSE', class=cond2 ? 'cond2-TRUE' : 'cond2-FALSE')
- using two
class
attributes, one for each parentesis
(class=cond1 ? 'cond1-TRUE' : 'cond1-FALSE')(class=cond2 ? 'cond2-TRUE' : 'cond2-FALSE')
All three:
pug
-var cond1=true
-var cond2=false
.c-component(class=cond1?'cond1-TRUE':'cond1-FALSE'class=cond2?'cond2-TRUE':'cond2-FALSE')
.c-component(class=cond1?'cond1-TRUE':'cond1-FALSE',class=cond2?'cond2-TRUE':'cond2-FALSE')
.c-component(class=cond1?'cond1-TRUE':'cond1-FALSE')(class=cond2?'cond2-TRUE':'cond2-FALSE')
HTML output
<divclass="c-component cond1-TRUE cond2-FALSE"></div>
<divclass="c-component cond1-TRUE cond2-FALSE"></div>
<divclass="c-component cond1-TRUE cond2-FALSE"></div>
If we have a ternary option with the second operandempty, we could simplify the pug syntax:
-var cond1=true
-var cond2=false
.c-component(class=cond1&&'cond1-TRUE'class=cond2&&'cond2-TRUE')
//- or more explicit
.c-component(class=cond1?'cond1-TRUE':''class=cond2?'cond2-TRUE':'')
HTML output
<divclass="c-component cond1-TRUE"></div>
📚 More info
Remove duplicates in object arrays
We have an object with multiple arrays inside it, and we need to remove duplicates between these arrays.
const obj={
arr1:['a','b','c'],
arr2:['a','b','d','e','f'],
}
First of all, we have to get the two arrays and merge their items with theArray.prototype.concat() method. As the documentation says, we can use this function on arrays only, not on objects!
We use this trick: we call an empty array
[]
and then we apply theconcat()
method to it
const allItems=[].concat(obj.arr1, obj.arr2)
that will return
console.log(allItems)
// (8) ["a", "b", "c", "a", "b", "d", "e", "f"]
Remove duplicates from an array#
Method 1: filter()#
Let's filter our array with all items inside, just to be without duplicates.
Array.prototype.filter() method use this condition: "for every item I loop through, I will check if the current index (pos
) is the same as theindexOf(item)
, and it this condition is true I will return the item.".
Array.prototype.indexOf() methodreturns the first index at which a given element can be found in the array, or -1 if it is not present, so the filter condition is satisfied only for the first time the item pass the loop because loopindex
andindexOf
are the same.
To be more clear let's make a table of the loop:
Item | Loop index | indexOf* | Condition | Saved into unique array |
---|---|---|---|---|
'a' | 0 | 0 | ok, 0 == 0 so this will returntrue | yes |
'b' | 1 | 1 | ok, 1 == 1 so this will returntrue | yes |
'c' | 2 | 2 | ok, 2 == 2 so this will returntrue | yes |
'a' | 3 | 0 | whoa, 3 != 0 so this will returnfalse | nope! |
'b' | 4 | 1 | whoa, 4 != 1 so this will returnfalse | nope! |
'd' | 5 | 5 | ok, 5 == 5 so this will returntrue | yes |
'e' | 6 | 6 | ok, 6 == 6 so this will returntrue | yes |
'f' | 7 | 7 | ok, 7 == 7 so this will returntrue | yes |
*indexOf = first position the item is present
const unique= allItems.filter((item, pos)=> allItems.indexOf(item)=== pos)
console.log(unique)
// (6) ["a", "b", "c", "d", "e", "f"]
Method 2: Set()#
Set() is an object lets you store unique values of any type.
- create a new
Set()
to return the unique values - spread
...
its items inside theSet
object - wrap all in square brackets to return an array
[object]
const unique=[...newSet(allItems)]
or we can use this syntax (I prefer this one! It seems more clear to me that we are manipulating something that will become an array using theArray.from() method)
- create a new
Set()
to return the unique values - convert the Set object to an array using
Array.from()
const unique= Array.from(newSet(allItems))
and the result is exactly the same as above usingfilter()
.
console.log(unique)
// (6) ["a", "b", "c", "d", "e", "f"]
To sum up#
/* ==========================================================================
OBJECT ARRAYS, REMOVE DUPLICATES
========================================================================== */
const obj={
arr1:['a','b','c'],
arr2:['a','b','d','e','f'],
}
const allItems=[].concat(obj.arr1, obj.arr2)
// using filter()
const unique_filter= allItems.filter((item, pos)=> allItems.indexOf(item)=== pos)
// using Set()
const unique_set1=[...newSet(allItems)]
const unique_set2= Array.from(newSet(allItems))
console.log(allItems)// (8) ["a", "b", "c", "a", "b", "d", "e", "f"]
console.log(unique_filter)// (6) ["a", "b", "c", "d", "e", "f"]
console.log(unique_set1)// (6) ["a", "b", "c", "d", "e", "f"]
console.log(unique_set2)// (6) ["a", "b", "c", "d", "e", "f"]
📚 More info
Add HTML classes to 11ty markdown content
11ty comes with some useful plugins for markdown manipulation, one of these ismarkdown-it-attrs.
This plugin should be used combined with its big brother, the markdown parsermarkdown-it, which is already added in 11ty basic installation.
markdown-it-attrs usesmarkdown-it
and adds the possibility to add attributes to HTML nodes generated from markdown.
To use it, add this plugin to the.eleventy
configuration file:
- require
markdown-it
const markdownIt=require('markdown-it')
- require
markdown-it-attrs
const markdownItAttrs=require('markdown-it-attrs')
- define basic
markdown-it
configuration options
const markdownItOptions={
html:true,
breaks:true,
linkify:true
}
- set
markdown-it-attrs
asmarkdown-it
usage options
const markdownLib=markdownIt(markdownItOptions).use(markdownItAttrs)
- set as eleventy configuration the new markdown configuration
eleventyConfig.setLibrary('md', markdownLib)
To sum up:
// .eleventy.js
const markdownIt=require('markdown-it')
const markdownItAttrs=require('markdown-it-attrs')
const markdownItOptions={
html:true,
breaks:true,
linkify:true
}
const markdownLib=markdownIt(markdownItOptions).use(markdownItAttrs)
eleventyConfig.setLibrary('md', markdownLib)
Example of usage#
# This is a title {.c-article-section__title}
This is a paragraph with data-state {data-state=important}
Another text with attributes {.c-article-section__disclaimer #articleId attr=value attr2="spaced value"}
{.u-shadow}
[Link in a new tab](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a){target="_blank" rel="noopener"}
will output
<h1class="c-article-section__title">This is a title</h1>
<pdata-state=important>This is a paragraph with data-state</p>
<pclass="c-article-section__disclaimer"id="articleId"attr=valueattr2="spaced value">Another text with attributes</p>
<imgclass="u-shadow"src="image.jpg"alt="Alt text">
<ahref="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"target="_blank"rel="noopener">Link in a new tab</a>
🧨!important
Note the last line where I added the
target="_blank"
attribute to the link to open it in a new browser tab. It's ok open a link in a new tab, but for security reasons it has to have also therel="noopener"
attribute.
Side note: unfortunately, I did not find a way to add attributes to markdowntables andblockquotes 😢
📚 More info
Start an npm project
To start an npm project, you have just to runnpm init
and npm will start asking you a lot of questions about how to call the project, who's the author and so on. If you are like me and have complete trust on your tools and its choices, just add-y
at the end. It stands foryes and npm will complete the boring burocrazy for you. 😏
npm init-y
Force include classes in critical CSS
Critical CSS build byAddy Osmani is a useful library that extracts and inlines critical-path CSS in HTML pages.
In the documentation page, there are a lot of configurations availablebut they are not the only ones! 😏
Critical CSS uses as its enginepenthouse which has in turn a lot of configuration options. One of them, isforceInclude
.
forceInclude: [...]
description from docs 📚:
Array of css selectors to keep in critical css, even if not appearing in critical viewport. Strings or regex (f.e. ['.keepMeEvenIfNotSeenInDom', /^.button/])
For instance, if we want to add a cta class injected via JS and not available in DOM nodes when the critical path is generated, we have to configure our critical CSS options like this:
critical.generate({
base:'./',
src:'template-homepage.html',
css:['production/css/style-1.css','production/css/style-2.css'],
....
penthouse:{
forceInclude:['OPTIONAL-CLASS-HERE'],
},
})
Nunjucks scoped variable declarations
We have to pay attention where we set Nunjucks variables because they arescoped
{%set animals=['🐱','🐶','🐺']%}
{%for itemin animals%}
{%set animal= item%}
{%endfor%}
{{ animal}}
{# animal -> ERROR #}
{# animal declared INSIDE the loop is NOT available #}
{%set animals=['🐱','🐶','🐺']%}
{# note this declaration #}
{%set animal=''%}
{%for itemin animals%}
{%set animal= item%}
{%endfor%}
{{ animal}}
{# animal declared OUTSIDE the loop is available #}
{# animal -> 🐺 (last array item) #}
📚 More info
Nunjucks advanced loops
Nunjucks is a powerful template engine that allows to loop through arrays and also objects 😏
Loop though an array#
{%set animals=['🐱','🐶','🐺']%}
{%for itemin animals%}
Value:{{ item}}
{%endfor%}
Value: 🐱
Value: 🐶
Value: 🐺
Loop though an object#
{%set animal={
name:'cat',
emoji:'🐱'
}%}
{%for key, valuein animal%}
{{ key}}:{{ value}}
{%endfor%}
Note that we have to declare the two parameters of the loopkey, value
.
name: cat
emoji: 🐱
Theiterable
property#
InTwig exists an intresting property,iterable
that checks if a variable can be iterable in a for loop:
Loop through an array:
{%set animals=['🐱','🐶','🐺']%}
{%if animalsis iterable%}
{%for itemin animals%}
Value:{{ item}}
{%endfor%}
{%else%}
Not iterable:{{ animal}}
{%endif%}
Value: 🐱
Value: 🐶
Value: 🐺
Loop through an object:
{%set animals={
name:'cat',
emoji:'🐱'
}%}
{%if animalsis iterable%}
{%for itemin animals%}
Value:{{ item}}
{%endfor%}
{%else%}
Not iterable:{{ animal}}
{%endif%}
Value: cat
Value: 🐱
🧨!important
Please note that
iterable
is aTwig property and can have unexpected results in Nunjucks template engine.
InTwig astring isnot iterable:
{%set animal='cat'%}
{%if animalis iterable%}
Iterable!
{%for itemin animal%}
Value:{{ item}}
{%endfor%}
{%else%}
Not iterable!
{{ animal}}
{%endif%}
Twig output
Not iterable!
cat
but if we run the same code inNunjucks, we discover that astring is iterable 🤯
Nunjucks output
Iterable!
Value: c
Value: a
Value: t
Accessing the parent loop#
Nunjucks provides in its loops theloop
property.
From thedocs theloop.index
is
the current iteration of the loop (1 indexed)
But what if we have two nested loops and we want to access to the parent loop?
Workaround: save the loop index as row number! 😏
In this example we have a matrix content: two rows and each row has one ore more cells. If we want to print all cells content and position, we have to:
loop (parent loop) through the rows
loop (child loop) through the columns
get the content inside each cell
{%set animals=[
['🐱','🐶','🐺'],
['🐍']
]%}
<table>
{%for rowin animals%}
{# new row #}
<tr>
{%set rowloop= loop%}
{%for cellin row%}
<td>
row (rowloop.index):{{ rowloop.index}}
column (loop.index):{{ loop.index}}
cell:{{ cell}}
</td>
{%endfor%}
</tr>
{%endfor%}
</table>
HTML output
<table>
{# new row #}
<tr>
<td>
row (rowloop.index):1
column (loop.index): 1
cell: 🐱
</td>
<td>
row (rowloop.index):1
column (loop.index): 2
cell: 🐶
</td>
<td>
row (rowloop.index):1
column (loop.index): 3
cell: 🐺
</td>
</tr>
{# new row #}
<tr>
<td>
row (rowloop.index):2
column (loop.index): 1
cell: 🐍
</td>
</tr>
</table>
📚 More info
Shell cheatsheet
Few commands I found very useful during development.
Command | Description |
---|---|
man ls | show manual for command 'ls' |
wc <file> | words count |
rm <file> | remove/delete file |
rm -i <file> | remove/delete file (interactive, ask confirm) |
rmdir <directory> | remove/delete directory |
rm -R <directory> | remove/delete directory and subdirectory |
rm -iR <directory> | remove/delete directory (interactive) |
cp <current location> <destination> | copy files |
chmod -R 755 <folder> | add writing permission to folder |
pwd | present working directory / print working directory |
cd | change directory |
mkdir | make directory |
ls | list files |
ls -l | list files (long form) |
ls -lah | list files (long form, all also hidden, human readable) |
touch [filename] | create file |
chown | change owner |
cat <file> | show file |
<cmd> > <file> | direct the output of "cmd" into "file" |
grep -rl "<text>" <dir> | search for all files containing<text> inside<dir> |
ln | symbolic link |
alias | show available alias on shell |
cd - | go to the previous current directory |
ctrl +r | advanced search (search any word in bash history) |