Access shortcode parameter in JavaScript and refer to current "a"-link inside shortcode file

Hello everyone,
yes, the title is very confusing, I’ll clear that up. There are in fact a few things I’m not too sure about and reading through the entire documentation still left a few questions.
I don’t want to bring up an XY-question where I ask about Y because I think it helps me to do X, instead of just asking about X right away. I’ll explain what I want to achieve, add my thoughts about what I tried already and I’d be happy if you could suggest a better, shorter and cleaner way to achieve this if there is one and I’m sure there might be one.

So I want to run a certain JavaScript function, when you click a certain link, which is wrapped in a shortcode, but this should only be done if the shortcode declaration contains a third given parameter. For that I’d simply use {{ with .Get 2 }}.
In this JavaScript code, I want to refer to the link, this link is linking to. Additionally, I want to remove the hash (if an ID is currently mentioned in the URL) and use this to remove something from the localStorage. The JavaScript code itself shouldn’t be a problem.
I’d theoretically want to use something like this inside an inline style-tag in the shortcode HTML file (quick pseudo-code):

const hashIndex = "{{ .Get 0 }}".indexOf('#') || url.Length;
const newUrl = "{{ .Get 0 }}".substring(0, hashIndex);

localStorage.removeItem("${newUrl}-something");

but that isn’t possible due to the .Get 0 not being valid (while the first parameter is always the URL it’s referring to).
I thought about saving the URL in a meta-tag, but I’d have to loop through all occurences of this meta-tag anyway and somehow differentiate which belongs to which. I could technically give the “a”-link a special ID, but I’m not sure how, since that all is taking place inside a shortcode HTML file.

I tried a lot of things, combining a lot of different things, but I couldn’t come up with a reasonable solution.
I mean for the click function, if the “a”-link doesn’t have a unique ID, I could have an ugly workaround doing something like this:

{{ with .Get 2 }}
<a href="{{ .Get 0 }}" class="link">{{.Inner}}</a>
{{ else }}
<a href="{{ .Get 0 }}" class="link" onClick="someFunction()">{{.Inner}}</a>
{{ end }}

but that is pretty ugly and inconvenient for several reasons. Using an onClick over an addEventListener in JS might not be the preferred way anyway and this whole thing looks a little bit hacky. There would still be the problem with the link though, because I can’t include the .Get 0 as a parameter either.

So whatever I tried, I ended up encountering an obstacle somewhere.

Check the sample on that page. You need to mark those attributes as safe. How you use them with JavaScript in the end is your thing :slight_smile:

I’m afraid I didn’t quite understand this. I tried something according to the documentation, but unfortunately that still leads to a dead end.

I tried using this HTML in my link.html file (the shortcode I’m using) and I’m afraid it’s horribly wrong:

{{ with .Get 2 }}
<a href="{{ .Get 0 }}" target="{{ .Get 1 }}" class="shortcode-link" {{ printf "onClick=`deleteScrollPos(%q)`" .Get 0 | safeHTMLAttr }}>{{.Inner}}</a>

<script>
    function deleteScrollPos(url) {
        const hashIndex = url.indexOf('#') || url.Length;
        const newUrl = url.substring(0, hashIndex);
        localStorage.removeItem("${newUrl}-scrollPos");
    }
</script>
{{ else }}
<a href="{{ .Get 0 }}" target="{{ .Get 1 }}" class="shortcode-link">{{.Inner}}</a>
{{ end }}

I’m getting this error:

execute of template failed: template: shortcodes/link.html:18:12: executing "shortcodes/link.html" at <.Get>: can't evaluate field Get in type string

Besides, this hacky way really doesn’t satisfy me, but I tried to get it working with the simplest way I could think of.

That is a mess.

  • inside of the with .Get 2 the dot (.) is not the parent context, but the content of the third parameter, so .Get 0 inside there won’t work. you need to get your parameters outside of the with loop.
  • the attribute onclick looks ok (if the parameter would be right). you are saying "run deleteScrollPos(Parameter 0)" but I would use %s instead of %q.
  • in the function itself you do some weird stuff that makes me think you have an url as parameter 0. If that is the case, then you add too much as a parameter because you cut everything away until the hash?
  • the function will be defined multiple times if you re-use the shortcode on the same page. It should have it’s own name, maybe with a random string, to avoid confusion.
  • you could take the URL in Hugo, parse it and add only the hash content as data-hash attribute to the link, then define that function one single time and let it check for the data-attribute and do it’s stuff.

And Hugo’s safe functions aren’t hacky. They are the safe way to add attributes that you parsed and checked.

{{ $parameter0 := .Get 0 }}
{{ $parameter1 := .Get 1 }}
{{ with $parameter1 }}
<a href="{{ $parameter0 }}">some link this is</a>
{{ end }}

Thank you very much for your answer. I understand your points.
To clear things up: I don’t want to remove everything but the hash, I want to remove the hash and keep everything else. I also didn’t mean Hugo’s safe functions are hacky, I meant the way of creating one link with and one link without an onClick, depending whether there is a third parameter or not, just doesn’t feel clean. But I’d be happy to get it working first.

I guess I understood all your points and changed my code. There is one little issue however. It adds the onClick attribute like that in the final rendered HTML:

<a href="something" onClick="`deleteScrollPos(/general/the-page/)`">

The problem about that: It adds the `` as well, but I couldn’t get it to work without them, because it doesn’t accept %s as a parameter for the JavaScript function.
Simply using:

<a href="{{ $p0 }}" target="{{ $p1 }}" class="shortcode-link" {{ printf "onClick=`deleteScrollPos(%s)`" $p0 | safeHTMLAttr }}>{{$inner}}</a>

doesn’t work. JavaScript of course doesn’t want to accept this.

The problem is “not enough information” :wink: Your sample line looks like the link has the same href as the thing that you want to give to the onclick function. Why not use an empty onclick function call and check this.href (or something that is valid javascript) inside of the function then?

The very specific call to the printf imho is this (not connected to the idea above, just in case you want to go further down the road you are already on):

{{ printf "onClick=\"deleteScrollPos('%s')\"" ($p0 | safeHTMLAttr) }}
  • strings in Go need to be encased in "
  • " that you want to print in your HTML need to be slashed out
  • in JS you can use single quotes
  • then a nice safe bracket around the parameter, just in case Hugo has a bad day. probably not required.

Thank you very much for your reply. I see what you mean, but I’m quite confused about all that. It seems like JavaScript doesn’t like the slashing out. The first \ seems to be an “Invalid character.” (that’s what the error says). It simply doesn’t like the slashes. Excuse me, if I’m confused about simple things, but I am quite confused right now so even simpler things I’d know would confuse me in that context.

If everything goes right the backslash shouldn’t be there when Javascript looks at it. It’s supposed to tell printf to output a ". You are doing something else wrong probably. The backslash is there so that printf prints the " into the parameter.

Please put a sample repo on Github that produces the problem you are encountering and from there we go.

I am pretty much using the exact same code you sent above while it does make sense to me, JS doesn’t want to accept it.

{{ printf "onClick=\"deleteScrollPos('%s')\"" ($p0 | safeHTMLAttr) }}

And this would be the entire shortcode except the CSS:

{{ $p0 := .Get 0 }}
{{ $p1 := .Get 1 }}
{{ $p2 := .Get 2 }}
{{ $inner := .Inner }}

{{ with $p2 }}
<a href="{{ $p0 }}" target="{{ $p1 }}" class="shortcode-link" {{ printf "onClick=\"deleteScrollPos('%s')\"" ($p0 | safeHTMLAttr) }}>{{$inner}}</a>

<script>
    function deleteScrollPos(url) {
        const hashIndex = url.indexOf('#') || url.Length;
        const newUrl = url.substring(0, hashIndex);

        localStorage.removeItem("${newUrl}-scrollPos");

        console.log("${newUrl}");
    }
</script>

{{ else }}
<a href="{{ $p0 }}" target="{{ $p1 }}" class="shortcode-link">{{$inner}}</a
{{ end }}

I agree, this topic is a little bit cluttered with messages. I am pretty confused why it doesn’t accept it though. There is one other thing I’m not sure about though.
Will {{ with $p2 }} evaluate false if there is no third parameter?

A sample repo has content and a config that sets up something where we can load it and type hugo to see what is happening :wink: Knowing only the layout part is not enough, sorry. I can’t see for instance one single sample call to your shortcode in this topic. You are presenting a blackbox and want a solution… Come one.

2 Likes

After a quick break, I managed to pretty much immediately come to the idea to use backquotes ` instead of quotation marks " and it worked just fine. Putting parenthesis around $p0 | safeHTMLAttr breaks the entire system by the way, so I decided to do it without them.

This worked out in the end:

{{ printf `onClick="deleteScrollPos('%s')"` $p0 | safeHTMLAttr }}

Thank you very much for leading me to the right path. I appreciate your help.