One feature of Bootstrap that I like is the
tabbable content
,
which allows the user to create tabs of local content. One common use-case is to
show multiple syntax highlighted code blocks that showcase the same problem, and
how to achieve it in different languages.
While this can be achieved by writing HTML directly in the content, it is a lot
of extra boilerplate content that could be automated, and makes it much easier
on the author to focus on the code content. The syntax I use in this blog allows
me to create nested shortcodes, and optionally, detect the language based on
the title of the tab.
Credit is due to Andreas Deininger, who helped rework the original version of
this shortcode, and came up with some improvements.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{{< tabs hello.c Python >}}
{{< codetab lang="C" highlight="linenos=table" >}}
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World\n");
return 0;
}
{{< /codetab >}}
{{< codetab >}}
print "Hello World"
{{< /codetab >}}
{{< /tabs >}}
|
The tabs
shortcode generates a top-level navigation list, and sets up the tabs
based on the parameters. The code for that is shown below, along with some
comments to help you follow along.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<!-- Unique ID for the tabs within the page -->
{{- $guid := printf "tabs-%d" .Ordinal -}}
<!-- Scratchpad register to specially handle the first element in the list -->
{{- .Scratch.Set "first" true -}}
<ul class="nav nav-tabs" id="{{- $guid -}}" role="tablist">
{{- range $index, $element := .Params -}}
<li class="nav-item">
<!-- Generate the IDs for the <a> and the <div> elements -->
{{- $tabid := printf "%v-%v-tab" $guid $index | anchorize -}}
{{- $entryid := printf "%v-%v" $guid $index | anchorize -}}
<a class="nav-link{{ if eq ($.Scratch.Get "first") true }} active{{ end }}"
id="{{ $tabid }}" data-toggle="tab" href="#{{ $entryid }}" role="tab"
aria-controls="{{ $entryid }}" aria-selected="{{ $.Scratch.Get "first" }}">
{{- $element -}}
</a>
<!-- Reset the scratchpad register, since we're done with the first parameter -->
{{- if eq ($.Scratch.Get "first") true -}}{{- $.Scratch.Set "first" false -}}{{- end -}}
</li>
{{- end -}}
</ul>
<!-- Inner content - generated by codetab shortcode -->
<div class="tab-content" id="{{- $guid -}}-content">
{{- .Inner -}}
</div>
|
The usage of a unique ID allows the user to add more than one tabs
list on a
page. While ideally one would use a random number generator and pass that
through some transformation to get a unique ID, Hugo doesn’t have any facilities
for generating random numbers in the templates. The best that we can use is to
pass a slice of characters to the shuffle function, get the first few characters
and join the resulting slice to get a string back. However, we only need to find
a unique ID within the page. This is where the .Ordinal
function comes in
handy. It returns the zero-based index of the shortcode within the parent
entity, whether it is a page, or an enclosing shortcode itself. Using that, and
the .Parent
function lets us create a codetab
shortcode that automatically
detects the language based on its position within the enclosing tabs
shortcode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
{{- $index := .Ordinal -}}
<!-- Make sure that we are enclosed within a tabs shortcode block -->
{{- if ne .Parent.Name "tabs" -}}
{{- errorf "codetab must be used within a tabs block" -}}
{{- end -}}
<!-- Generate the unique ID based on the enclosing tabs .Ordinal -->
{{- $guid := printf "tabs-%d" .Parent.Ordinal -}}
<!-- Trim any leading and trailing newlines from .Inner, this avoids
spurious lines during syntax highlighting -->
{{- $code := trim .Inner "\n" -}}
{{- $entry := .Parent.Get $index -}}
{{- $entry := lower $entry -}}
{{- $lang := default $entry (.Get "lang") -}}
{{- $hloptions := default "" (.Get "highlight") -}}
{{- $tabid := printf "%v-%v-tab" $guid $index | anchorize -}}
{{- $entryid := printf "%v-%v" $guid $index | anchorize -}}
<div class="tab-pane fade{{ if eq $index 0 }} show active{{ end }}"
id="{{ $entryid }}" role="tabpanel" aria-labelled-by="{{ $tabid }}">
{{- highlight $code $lang $hloptions -}}
</div>
|
The codetab
shortcode also allows the author to override the language of the
enclosed code, and add higlight options as necessary. Now that we have the
necessary shortcodes, we can see the result of our example listed above.
1
2
3
4
5
6
7
|
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World\n");
return 0;
}
|