Shortcodes: Can a Named .Param be an Array? Can Positional .Params be Sliced?

I’m trying to create a shortcode to render images with a JS image gallery script I’ve got. The JS is pretty straightforward. It just looks for a <div class="gallery"> and builds a gallery from any images and their thumbnails inside that div. eg:

<!-- gallery box -->
<div class="gallery">
    <a href="grafix/test01.jpg" data-caption="test01"><img src="grafix/test01wee.jpg" alt="test01"></a>
    <a href="grafix/test02.jpg" data-caption="test02"><img src="grafix/test02wee.jpg" alt="test02"></a>
    <a href="grafix/test03.jpg" data-caption="test03"><img src="grafix/test03wee.jpg" alt="test02"></a>
</div>
<!-- end gallery box -->

I can make a simple shortcode like this:

{{< gallery "test01" "test02" "test03" >}}

and create the shortcode template thus:

<!-- gallery box -->
<div class="gallery">

{{range .Params}}
<a href="grafix/{{.}}.jpg" data-caption="{{.}}">
<img src="/grafix/{{.}}wee.jpg" alt="{{.}}"></img>
</a>
{{end}}

</div>
<!-- end gallery box -->

and that gets me the bare-bones of what I want. But there are some problems:

1: The .jpg and wee.jpg [for the thumbnails] filename endings are hard-wired in, which is exceedingly crap. What if the gallery comprised pngs or gifs?
2: If there is more than one instance of the gallery div on a page, I need to give each one an id to uniquely identify it, so the script keeps each gallery separate.

In an ideal world, my shortcode would look something like this:

{{< gallery galleryname="somegallery" imagetype="jpg" "image01" "image2" "image3"... >}}

but that would fall foul of not being able to mix named vs positional parameters in a shortcode.

I could have all the parameters positional and then extract the galleryname and imagetype as the first two items but then how do I range through the rest of them? It doesn’t seem possible using Golang template code to slice an array by doing something like {{ range .Params[2:] }} to loop through the rest of the parameters.

Alternately, using named parameters and putting the list of images into an array. Something like:

{{< gallery name="somegallery" imagetype="jpg" "images=["image01","image2","image3"...] >}}

would seem like a potential approach. But trying something like that throws up a multitude of errors.

So, can anyone think of a way this might be doable? Even just knowing whether or not it’s possible to pass an array as a named parameter, or slice an array of positional parameters would be a start.

Shortcode params are strings.

Yep. Obviously.

But Hugo must read them in as an array of strings, otherwise I wouldn’t be able to use {{ range ... }}. The question is, is it possible to perform actions such as slicing on that array? eg. to only pass ArrayOfParams[2:] to {{ range ... }}

…or apply any ‘logic’ to the item currently represented by the dot: {{.}} eg. [pseudocode] if {{.}} = something .... do something

Apologies if these are dumb questions, but it’s not always apparent what parts of Go are still usable within Go template code.

If you use positional params, you have a slice that you can range over (I guess you can also range over named params, which is a map).

v := range .Params or k, v := range .Params … or something.

But I suspect there are better ways to build a gallery. Remember that shortcodes can be nested, so:

{{< gallery >}}

{{ image src="foo.jpg" }}
{{ image src="bar.jpg" }}

{{< /gallery >}}



1 Like

Thanks.

I just wanted to know what was and wasn’t theoretically possible, before I got too wrapped up in trying to make something work.

I didn’t know about nested shortcodes though. So that looks like an easy[ish] way to solve the problem, albeit at the expense of slightly more complex syntax in the markdown files.

Time for another drink and a bit more random tinkering.

1 Like

Well, I think I might be making a bit of progress here but, as ever, the template code syntax is making my brain melt.

Here is the shortcode tag in my markdown file:

{{< gallery "galleryname" "jpg" "image01" "image02" "image03" "image04" >}}

[the first two parameters are the name of the gallery and the filetype for the images]

In my shortcode I have the following:

<!-- gallery box -->
<!-- store .Params in var -->
{{$allparams := .Params }}

<!-- get gallery name from $allparams[0] -->
<div class="gallery" id="{{index $allparams 0 }}">

<!-- store image filetype in $filetype var -->
{{$filetype := index $allparams 1 }}

<!-- loop through $allparams -->
{{ range $allparams }}

<!-- check if 'dot' = $allparams[0] -->
{{if eq . (index $allparams 0) }}
<p>DEBUG: dot => "{{.}}" and index $allparams 0 => "{{index $allparams 0}}"</p>
<!-- if it is, do nada -->
{{end}}

<!-- check if 'dot' = $allparams[1] -->
{{if eq . (index $allparams 1) }}
<p>DEBUG: dot => "{{.}}" and index $allparams 1 => "{{index $allparams 1}}"</p>
<!-- if it is, do nada -->
{{end}}

<!-- otherwise use 'dot' to populate gallery image tag -->
<a href="grafix/{{.}}.{{$filetype}}" data-caption="Caption for {{.}}"><img src="/grafix/{{.}}wee.{{$filetype}}" alt="{{.}}"></img></a>

{{end}}
<!-- end loop through $allparams -->
</div>
<!-- end gallery box -->

This correctly populates the div with id=<galleryname> and uses the jpg extension on the image filenames in the links. However although my equality tests seem to work, I still end up with an <img src="galleryname.jpg" ....> and <img src="jpg.jpg" ....> in my outputted HTML, when I thought I was excluding these:

<div class="gallery" id="galleryname">

<p>DEBUG: dot => "galleryname" and index $allparams 0 => "galleryname"</p>

<a href="grafix/galleryname.jpg" data-caption="Caption for galleryname"><img src="http://localhost:1313/grafix/gallerynamewee.jpg" alt="galleryname"></img></a>

<p>DEBUG: dot => "jpg" and index $allparams 1 => "jpg"</p>

<a href="grafix/jpg.jpg" data-caption="Caption for jpg"><img src="http://localhost:1313/grafix/jpgwee.jpg" alt="jpg"></img></a>

<a href="grafix/image01.jpg" data-caption="Caption for image01"><img src="http://localhost:1313/grafix/image01wee.jpg" alt="image01"></img></a>

<a href="grafix/image02.jpg" data-caption="Caption for image02"><img src="http://localhost:1313/grafix/image02wee.jpg" alt="image02"></img></a>

<a href="grafix/image03.jpg" data-caption="Caption for image03"><img src="http://localhost:1313/grafix/image03wee.jpg" alt="image03"></img></a>

<a href="grafix/image04.jpg" data-caption="Caption for image04"><img src="http://localhost:1313/grafix/image04wee.jpg" alt="image04"></img></a>
</div>

I’m sure the problem lies with my testing for equality between the ‘dot’ and the corresponding index of the $allparams array. The debug lines above seem to show equality, but I may not even be comparing like with like.

Also, if i throw an {{else}} into the mix:

....<snip>....

<!-- check if 'dot' = $allparams[1] -->
{{if eq . (index $allparams 1) }}
<p>DEBUG: dot => "{{.}}" and index $allparams 1 => "{{index $allparams 1}}"</p>
<!-- if it is, do nada -->
{{end}}

{{else}}
<!-- otherwise use 'dot' to pouplate gallery image tag -->
<a href="grafix/{{.}}.{{$filetype}}" data-caption="Caption for {{.}}"><img src="/grafix/{{.}}gallerythumb.{{$filetype}}" alt="{{.}}"></img></a>

{{end}}

I don’t get anything output at all, except for the two ‘DEBUG’ lines.

<div class="gallery" id="galleryname">

<p>DEBUG: dot => "galleryname" and index $allparams 0 => "galleryname"</p>

<p>DEBUG: dot => "jpg" and index $allparams 1 => "jpg"</p>
</div>

And [while my head is spinning, why does the {{else}} not need an {{end}}? If I put another {{end}} after the {{else}} clause, I get an error.

1 Like

It does. Which means something else is off.

Even if I strip the shortcode template down to the barest bones, I still get an error with an {{end}} on the {{else}}

<!-- store .Params in var -->
{{$allparams := .Params }}

<!-- loop through $allparams -->
{{ range $allparams }}

{{if eq . (index $allparams 0) }}
<!-- if it is, do nada -->
{{end}}

{{if eq . (index $allparams 1) }}
<!-- if it is, do nada -->
{{end}}

{{else}}
<!-- do nada -->
{{end}}

{{end}}
<!-- end loop through $allparams -->

Gives: ERROR: 2016/06/22 template: theme/shortcodes/gallery.html:22: unexpected {{end}}

Whereas the following works [at least as far as not generating a template error]:

<!-- store .Params in var -->
{{$allparams := .Params }}

<!-- loop through $allparams -->
{{ range $allparams }}

{{if eq . (index $allparams 0) }}
<!-- if it is, do nada -->
{{end}}

{{if eq . (index $allparams 1) }}
<!-- if it is, do nada -->
{{end}}

{{else}}
<!-- do nada -->
<!-- REQUIRED? END REMOVED --->

{{end}}
<!-- end loop through $allparams -->

I think I may have to throw the towel in on this one and look at doing it with nested shortcodes instead. Golang template code just seems to defy logic at times.

<!-- store .Params in var -->
{{$allparams := .Params }}

<!-- loop through $allparams -->
{{ range $allparams }}

{{if eq . (index $allparams 0) }}
<!-- if it is, do nada -->
{{end}}

{{if eq . (index $allparams 1) }}
<!-- if it is, do nada -->
REMOVE THIS:::: {{end}}

{{else}}
<!-- do nada -->
{{end}}

{{end}}
<!-- end loop through $allparams -->

Se my comment above: An if can be complemented with else, but needs only one end … at the end. You have 2.

1 Like

Ah. So the second one was the culprit.

Ta!

By God, I think the lad’s finally cracked it:

{{< gallery "galleryname" "jpg" "image01" "image02" "image03" "image04" >}}

and…

<!-- gallery box -->
<!-- store .Params in var -->
{{$allparams := .Params }}

<!-- get gallery name from $allparams[0] -->
<div class="gallery" id="{{index $allparams 0 }}">

<!-- store image filetype in $filetype var -->
{{$filetype := index $allparams 1 }}

<!-- loop through $allparams -->
{{range $allparams }}

<!-- check if 'dot' = $allparams[0] -->
{{if eq . (index $allparams 0) }}
<!-- if it is, do nada -->
{{else}}

<!-- check if 'dot' = $allparams[1] -->
{{if eq . (index $allparams 1) }}
<!-- if it is, do nada -->
{{else}}

<!-- otherwise use 'dot' to pouplate gallery image tag -->
<a href="grafix/{{.}}.{{$filetype}}" data-caption="Caption for {{.}}"><img src="/grafix/{{.}}gallerythumb.{{$filetype}}" alt="{{.}}"></img></a>
{{end}}
{{end}}

{{end}}
<!-- end loop through $allparams -->
</div>
<!-- end gallery box -->

Produces exactly what I want.

Now, if I could just remember where I was up to, sometime yesterday afternoon, when I foolishly thought “I know. Instead of writing all these image tags in HTML by hand, I’ll just knock up a quick shortcode to handle it. Should only take me half an hour!”

1 Like
{{$i,$e := range $allparams }}
{{ if gt $i 1 }}
<a href="grafix/{{$e}}.{{$filetype}}" data-caption="Caption for {{$e}}"><img src="/grafix/{{$e}}gallerythumb.{{$filetype}}" alt="{{.}}"></img></a>
{{ end }}
{{end}}

That looks neater and would avoid the flaw in my longwinded version where galleryname can’t be the same as the name of any of the images or that image doesn’t get shown. But when I try it:

<!-- loop through $allparams -->
{{$i,$e := range $allparams }}

I get this error:

ERROR: 2016/06/22 template: theme/shortcodes/gallery.html:14: too many declarations in command

This version works:

<!-- loop through $allparams -->
{{range $i, $e :=  $allparams }}
1 Like

As is my wont; after I finally got this working, following a day and a half of mental torture, I couldn’t leave well alone and had to continue hitting it with sticks to see what fell off.

Anyway, I happened to come across something in the Hugo docs that allowed me to do it the entire thing in a completely different way.

Turns out Hugo does add some ‘slicing’ functions to the .Params array, which aren’t in the standard Golang templates. So, availing of those, here is my latest version. The shortcode syntax is still not as flexible and elegant as I’d like. But, since this is for a site that only I will be maintaining, I can cope with that.

Shortcode syntax is now as follows:

{{< gallery
    "galleryname"
    "Caption for the Gallery"
    "jpg"
    "image01"
    "image02"
    "image03"
    ....etc...
>}}

Then I’m using Hugo’s extensions to standard Golang templates to effectively split my array of .Params into two separate variable arrays. The first, $meta contains the metadata for the gallery [name, caption, image type] and the second, $images contains the list of images in the gallery. That makes it easy to filter metadata from content.

{{"<!-- begin gallery shortcode //-->" | safeHTML}}

<!-- get metadata from 1st three params 0=galleryname, 1=caption, 2=filetype -->
{{ $meta :=  first 3 .Params }}
<!-- everything after 1st three params is an image -->
{{ $images :=  after 3 .Params }}

<div class="gallery" id="{{index $meta 0 }}">

{{range $images}}
<a href="/grafix/{{.}}.{{index $meta 2}}" data-caption="{{index $meta 1}}"><img src="/grafix/{{.}}thumb.{{index $meta 2}}" alt="{{.}}"></img></a>
<!-- end loop through $allparams -->
</div>
{{end}}
{{"<!-- end gallery shortcode //-->" | safeHTML}}

Not too shoddy. If I say so myself!

1 Like

I’m on a roll now!

I really didn’t like having to pass $filetype [ie. image extension] as a variable. I did it so that I could dynamically generate both the image name: image01.jpg and the thumbnail image name: image01thumb.jpg from the root image filename: image01 by using:

{{.}}.{{$filetype}}
and
{{.}}thumb.{{$filetype}}

but it was horribly clunky and would break if the gallery contained a mixture of image formats. Thankfully, my trawling of the docs page [linked to above], giving Hugo’s extensions to the standard Golang template turned up another gem, in that there is a nice string replace function:

So now, I can simplify my shortcode syntax, by not bothering to pass in a filetype parameter and just use the actual image filenames ‘as is’:

{{< gallery
    "galleryname"
    "Caption for the Gallery"
    "image01.jpg"
    "image02.jpg"
    "image02.jpg"
    ...etc...
>}}

and then in the shortcode itself, use the replace function to swap xxxx.jpg for xxxxthumb.jpg in the thumbnail filenames:

{{"<!-- begin gallery shortcode //-->" | safeHTML}}

<!-- get metadata from 1st two params 0=galleryname, 1=caption -->
{{ $meta :=  first 2 .Params }}
<!-- everything after 1st two params is an image -->
{{ $images :=  after 2 .Params }}

<div class="gallery" id="{{index $meta 0 }}">
<!-- begin loop through $images -->
{{range $images}}
<a href="/grafix/{{.}}" data-caption="{{index $meta 1}}"><img src="/grafix/{{ replace . "." "thumb." }}" alt="{{.}}"></img></a>
<!-- end loop through $images -->
{{end}}
</div>
{{"<!-- end gallery shortcode //-->" | safeHTML}}

I shall retire to the pavillion now. Bloodied but triumphant.

1 Like

Sorry to bring this up checks date from 7 years ago, but i have a question about this implementation.

I’ve been trying to to do something similar however

Things we like:

    {{ range .Params.likes }}
  • {{ . }}
  • {{ end }}
                </ul>
            </div>
            <div class="column">
                <h4>Things we don't like:</h4>
                <ul class="notdot xmark other">
                    {{ range .Params.dislikes }}
                    <li>{{ . }}</li>
                    {{ end }}
    
                </ul>
            </div>
        </div>
    

I want to populate the xmark ul class with an array of inputs and checkmark one with a different set of inputs. I can’t quite seem to get it to work since as we saw it can’t take an array. been scratching my head for a few days

I am so very sorry for replying to this thread again, but i solved my problem like this

<div class="columns">
    <div class="column is-half">
        <h4>Things we like:</h4>
        <ul class="notdot check other">
            {{ range $like := split .Params.likes "* " }}
                <li>{{ $like }}</li>
            {{ end }}
        </ul>
    </div>
    <div class="column">
        <h4>Things we don't like:</h4>
        <ul class="notdot xmark other">
            {{ range $dislike := split .Params.dislikes "* " }}
                <li>{{ $dislike }}</li>
            {{ end }}
        </ul>
    </div>
</div>

and just use likes=" item1* item2*" etc