Le contexte de Page d'Hugo n'est pas seulement la source d'information la plus importante pour vos pages, c'est aussi la source de données principale de tous vos templates. Plus souvent qu'il n'y paraît, vous aurez à ajouter vos propres variables personnalisées en plus de celles définies par défaut.
Avec la fonction .Scratch d'Hugo, n'importe quelle Page ou Shortcode peut être enrichie avec autant de variables que nécessaire en plus de celles par défaut.
C'est quoi Scratch ?
Scratch a été ajouté à l'origine pour contourner une limitation du langage de templating de Go, qui empêchait d'écraser des variables. Elle s'est rapidement enrichie d'autres méthodes et constitue désormais une fonctionnalité d'Hugo à part entière.
.Scratch.Set
Set
est utilisé pour mémoriser une valeur voire pour pouvoir surcharger simplement une valeur par la suite.
{{ .Scratch.Set "salutations" "Bonjour" }}
{{ if eq $ciel "sombre" }}
{{ .Scratch.Set "salutations" "Bonsoir" }}
{{ end }}
{{ .Scratch.Get "salutations"}}
.Scratch.Add
Cette méthode s'occupe d’ajouter ou de pousser des valeurs multiples dans une variable ou une clef.
// Pour les chaînes de caractères
{{ .Scratch.Add "salutations" "Bonjour" }}
{{ .Scratch.Add "salutations" "Bonsoir" }}
{{ .Scratch.Get "salutations" }}
// Affichera : BonjourBonsoir
Utilisée avec slice
, elle permet d’ajouter une ou plusieurs valeurs à un
tableau.
{{ .Scratch.Add "salutations" (slice "Bonjour") }}
{{ .Scratch.Add "salutations" (slice "Bonsoir") }}
{{ .Scratch.Add "salutations" (slice "Aloha" "Buenos dias") }}
.Scratch.Get
Maintenant récupérons tout ça.
// Avec la fonction range
{{ range where .Scratch.Get "salutations" }}
<ol>
<li>
{{ . }}
</li>
</ol>
{{ end }}
// ☝️ Affichera une liste ordonnée avec nos 4 salutations.
// Ou avec la fonction delimit
{{ delimit (.Scratch.Get "salutations"), ", " }}
// ☝️ Affichera Bonjour, Bonsoir, Aloha, Buenos dias
.Scratch.Delete1
Supprime la paire clé/valeur du contexte.
Lors de l'utilisation de .Scratch.Add
dans une boucle, .Scratch.Delete
est pratique pour réinitialiser une valeur.
{{ .Scratch.Delete "salutations" }}
newScratch2
Ce n'est pas une méthode issue de Scratch, mais une fonction qui permet la création d'une instance locale de Scratch dans un template.
{{ $headerScratch := newScratch }}
{{ $headerScratch.Add "brand_image" .Params.image }}
Manipuler des tableaux et des maps
.Scratch.SetInMap
Cette fonction-là permet de cibler la clef d’un tableau et de lui assigner une nouvelle valeur.
Elle prend comme premier paramètre votre clef .Scratch
, comme second paramètre
la clef issue du tableau ou de la map, le troisième étant la valeur que vous
définissez.
Si vous ne connaissez pas dict je vous explique tout ça dans cet article
{{ .Scratch.Add "salutations" (dict "english" "Hello" "french" "Bonjour") }}
{{ .Scratch.SetInMap "salutations" "english" "Howdy 🤠" }}
// Nous avons modifié la valeur de la clef en anglais de Hello à Howdy 🤠 !
Attention au périmètre et au contexte…
.Scratch
n'est disponible que pour l’objet page ou l’objet shortcode. Vous ne
pouvez pas l’utiliser sur un autre élément.
Souvenez-vous que si vous vous trouvez à l’intérieur d’une boucle range
dans
votre page d’index, alors le .Scratch
de votre page d’index sera $.Scratch
alors que la page courante que vous traitez dans votre boucle sera .Scratch
.
Retenez également que vous pouvez affecter une paire clef-valeur à .Scratch
depuis n'importe où, même dans un fichier partiel du moment que vous lui passez
le contexte. Heeeeein? Prenons un exemple concret pour illustrer les dangers qui
vous guettent avec l’utilisation de .Scratch
et du contexte.
Un exemple classe avec .Scratch
Je trouve ça bien pratique d’affecter des classes à mon élément body
(comme le
fait WordPress) pour pouvoir faire des ajustements CSS/JavaScript en fonction
de la page sur laquelle on se trouve.
Je trouvais ça très fastidieux à faire avec Hugo, jusqu'à ce que je comprenne
comment utiliser .Scratch
.
Je veux ajouter une classe CSS rp-body
à toutes mes pages ainsi que la valeur
de .Section
à mes classes.
Et seule la page d’accueil devrait hériter de la classe rp-home
.
Je pourrais écrire ça une bonne foi pour toute, dans un fichier partiel ou un
fichier de gabarit de page qui comprend l’ouverture de la balise body
mais… je
pourrais avoir besoin de cette liste de classes ailleurs dans mon code pour
réaliser des tours de magie avec ajax. Disons sous forme d’objet JavaScript.
Comme faire pour créer cette liste, la modifier si je suis sur la page d’accueil
et la stocker dans mon objet .Page
pour pouvoir la réutiliser par la suite ?
Pour bien faire, nous allons stocker nos classes dans un tableau.
// Avant la balise body, je peux stocker mon unique et première classe universelle.
{{ .Scratch.Add "classes" (slice "rp-body") }}
// Puis ma section. Ce printf me permet d’ajouter la valeur de .Section avec mon préfixe personnalisé.
{{ .Scratch.Add "classes" (slice (printf "rp-%s" .Section))) }}
// Et maintenant sommes nous sur la page d’accueil ?
{{ if .IsHome }}
{{ .Scratch.Add "classes" (slice "rp-home") }}
{{end}}
// Est-ce que ce sont les vacances ? 🎄
{{ if isset .Site.Params "season" }}
{{ .Scratch.Add "classes" (slice (printf "rp-body--%s" .Site.Params.season))) }}
{{ end }}
Nous pourrions faire bien plus de vérifications et de contorsions, mais en fin de compte, nous n'avons plus qu'à écrire dans notre fichier de gabarit ce joli :
<body class='{{ delimit (.Scratch.Get "classes") " " }}'>
Et pour JavaScript, nous pouvons créer notre objet à l’endroit où nous en avons besoin.
<script>
let bodyClasses = [{{ range .Scratch.Get "classes" }}"{{ . }}", {{end}}];
</script>
Très bon cas de figure, continuons notre chemin.
.Scratch
dans un fichier partiel
Comme je l’expliquais plus tôt, comme .Scratch
fait partie de l’objet page
généralement passé en tant que contexte (le fameux point) à l’appel de la fonction
partial
.Déplaçons le bout de code qui stocke nos classes dans un fichier
partiel pour gagner en lisibilité :
// partials/scratching/body_classes.html
{{ .Scratch.Add "classes" (slice "rp-body") }}
[… ici le code vu précédemment …]
Dans mon fichier de gabarit, je peux maintenant écrire :
{{ partial "scratching/body_classes.html" . }}
<body class='{{ delimit (.Scratch.Get "classes") " " }}'>
[…]
Le retour de la fonction .Scratch
de la page a été transmis au fichier partiel
via le contexte, de manière à pouvoir continuer de le modifier sans avoir à
toucher au code ailleurs. En plus ça permet d’avoir des fichiers de gabarits de
page plus propres !
.Scratch
dans un fichier partiel dans une boucle range
🤯
Quand vous utilisez la fonction range
pour boucler sur des éléments, vous ne
pouvez pas lui passer le contexte en paramètre comme on peut le faire avec la
fonction partial
, le contexte que vous manipulez est celui de la boucle, c'est
bien ce que vous souhaitez.
{{ .Scratch.Set "section_color" }}
{{ range where .Data.Pages}}
<h2>{{ .Title }}</h2>
<div class="Child Child--{{ $.Scratch.Get section_color}}">
[…]
<div>
{{ end }}
// Affichera le contenu de section_color.
// Alors que…
{{ range where .Data.Pages }}
{{ partial "enfant.html" . }}
{{ end }}
// Le fichier partiel enfant.html ne saura pas récupérer le contenu de la fonction .Scratch de la page, même si nous lui passons le contexte en paramètre…
C’est parce que le contexte que nous passons en paramètre de la fonction
partial
est celui de l’élément en cours parcouru grâce à la fonction range
,
pas celui de la page dont vous êtes en train de coder le gabarit.
Très bien me direz-vous, mais comment faire pour accéder au .Scratch
de la
page parente depuis notre fichier partiel ?
Eh bien, vous pouvez toujours stocker ce qui est retourné par la fonction
.Scratch
de la page dans une variable, pour la passer ensuite en paramètre de
votre fichier partiel :
{{ $indexScratch := .Scratch }}
{{ range where .Data.Pages }}
{{ partial "child.html" $indexScratch }}
{{ end }}
Dans le fichier partiel on écrira alors :
<div class="Child Child--{{ .Get "section_color" }}">
[…]
<div>
Si vous avez également besoin de l’ensemble du contexte de la page que vous êtes
en train de parcourir dans la boucle, utilisez alors la fonction dict
:
{{ $indexScratch := .Scratch }}
{{ range where .Data.Pages }}
{{ partial "child.html" (dict "indexScratch" $indexScratch "page" . }}
{{ end }}
Dans le fichier partiel vous pourrez alors écrire :
<div class="Child Child--{{ .indexScratch.Get section_color}}">
{{ .page.Content }}
<div>
.Scratch dans un fichier partiel sans contexte de page
Tout ce qui figure ci-dessus est important si vous devez accéder à une instance Scratch liée à votre contexte de page, mais avec l'ajout de newScratch
2, vous pouvez utiliser désormais utiliser Scratch n'importe où, y compris dans un fichier partiel sans contexte de Page.
Appelons un fichier partiel. Notez que nous ne passons aucun contexte de Page, juste une map issue du Front Matter qui contient class
, alt
et une potentielle image_src
pour remplacer celle par défaut.
{{ partial "brand" .Params.brand }}
Dans notre fichier partiel nous pouvons toujours faire appel à Scratch :
{{ $brandScratch := newScratch }}
{{ $brandScratch.Set "brand_image" "default.jpg" }}
{{ with .image_src }}
{{ $brandScratch.Set "brand_image" "." }}
{{ end }}
<div class="brand {{ .class }}">
<img src="{{ $brandScratch.Get "brand_image" }}" alt="{{ .alt }}" />
</div>
.Scratch
après Go 1.11
Oui, avec la version 11 de Golang nous pouvons maintenant nativement écraser les variables dans les templates Go mais …
Dans beaucoup de cas, je trouve que stocker une valeur dans le contexte de Page plus utile qu'autre chose. Par exemple, si un fichier partiel a besoin d'accéder à des variables de Page et à d'autres informations, si vous vous passiez de Scratch, vous vous retrouveriez avec un contexte sous la forme d'un long dict
…
{{ $humeur := "Joyeux" }}
{{ if $pluie }}
{{ $humeur = "Grincheux" }}
{{ end }}
{{ partial "blancheneige/nain.html" (dict "humeur" $humeur "page" . ) }}
Utiliser Scratch pour stocker vos variables dans l'objet de Page vous garantit un code propre et réutilisable.
Avec .Scratch
{{ .Scratch.Set "humeur" "Joyeux" }}
{{ if $pluie }}
{{ .Scratch.Set "humeur" "Grincheux" }}
{{ end }}
{{ partial "blancheneige/nain.html" . }}
En plus, je ne pense pas que s'amuser à dénouer des maps complexes soit aussi
pratique que ce que nous permet de faire actuellement .Scratch.SetInMap
!