What's the difference between shortcodes and render hooks?

Hi everyone,
I’m trying to implement similar effects with a shortcode and a render hook. I wrote two blog posts like this:

+++
date = '2025-01-12T02:38:06-05:00'
draft = true
title = 'Test1'
+++
1. Test with shortcode
{{< testlink "/posts/test2" >}}

2. Test with render hook  
[click](/posts/test2)

and,

+++
date = '2025-01-12T02:38:08-05:00'
draft = true
title = 'Test2'
+++
**I'm here!**

So my idea was, when I click no matter which link, the content of page ‘Test2’ will show up under the link.
Here’s how I implemented the shortcode:

{{ with .Get 0 }}

{{ $page := $.Page.GetPage . }}
<p>
    <a href="/" onclick="handleClick('{{ $page.Content }}'); return false;">click</a>
</p>
<div id="shortcode"></div>

<script type="text/javascript">
    function handleClick(content) {
        var shortcodeElement = document.getElementById("shortcode");
        shortcodeElement.innerHTML = content;
    }
</script>

{{ end }}

It seems to work fine. But when I tried the similar code in a render hook:

{{- $u := urls.Parse .Destination -}}
{{- $href := $u.String -}}

{{ $page := $.Page.GetPage $href }}

<a href="{{ $href }}" onclick="call('{{ $page.Content }}'); return false;">
  {{ .Text | safeHTML }}
</a>
<div id="render"></div>

<script type="text/javascript">
 function call(content) {
     var div = document.getElementById('render');
     div.innerHTML = content;
 }
</script>

It seems the render hook doen’t know how to compile the page content and falls into an infinite building process. Is it not the correct way to call $page.Content in a render hook? How should I find the correct way?

Try call('{{ $page.Content | | safeHTMLAttr }}') or safe.JSStr and see if that works.

Let’s assume this serves the purpose of your test, but it’s very unlikely that you want an entire page’s content on the onclick of a link. The ways that can go bad because of unexpected characters are too many to count.

1 Like

If A points to B points to C points to A…

:chicken: :egg: :chicken: :egg: :chicken: :egg:

Exactly, it’s recursion by definition, so of course it’s going to loop infinitely.
Instead of keeping each target pages data inside each link, you may want to create a JSON file with the respective data, and query that file from your script with the url key.

For example that JSON Feed template would do it:

{{/* layouts/index.json */}}
{
"version": "https://jsonfeed.org/version/1.1",
"title": "{{ if eq  .Title  .Site.Title }}{{ .Site.Title | plainify }}{{ else }}{{ with .Title }}{{ . | plainify }} • {{ end }}{{ .Site.Title | plainify }}{{ end }}",
"home_page_url": "{{- with .Site.BaseURL }}{{ . | absLangURL  }}{{ end -}}",
"feed_url": "{{ .Permalink | absLangURL }}",
{{ with .Site.Params.Author -}}
	"authors": [{
		"name": "{{ .name }}"{{ if .email }},
		"url": "mailto:{{ .email }}"{{ end }}{{ if .image }},
		"avatar": "{{ .image | absLangURL }}" {{ end }}
	}],
{{- end }}
"items":
{{- $index := slice -}}
{{- $pages := where .Site.RegularPages.ByDate.Reverse "Type" "not in"  (slice "page" "section") -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
	{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- range $pages -}}
	{{- if .Params.dateCreated -}}
			{{ $.Store.Set "date_created" (.Params.dateCreated) }}
	{{- else -}}
			{{- if isset site.Params "date_format" -}}
				{{- $.Store.Set "date_created" (.Date.Format site.Params.date_format) -}}
			{{- else -}}
				{{- $.Store.Set "date_created" (.Date.Format "2006-01-02") -}}
			{{- end -}}
	{{- end -}}
	{{- if .Description -}}
		{{ $.Store.Set "content" (.Description | $.RenderString) }}
	{{- else if .Summary -}}
		{{ $.Store.Set "content" (.Summary | $.RenderString) }}
	{{- else if .Content -}}
		{{ $.Store.Set "content" (.Content | $.RenderString) }}
	{{- else -}}
		{{ $.Store.Set "content" "no content" }}
	{{- end -}}
	{{- $index = $index | append (dict
		"title" ( .Title | plainify )
		"id" (md5 .Permalink)
		"url" .Permalink
		"types" (i18n .Page.Type )
		"tags" (apply .Params.tags "i18n" "." )
		"content_text" ($.Store.Get "content" | $.RenderString | plainify)
		"date_created" ($.Store.Get "date_created")
		"date_published" (.Date.Format "2006-01-02T15:04:05Z07:00")
	) -}}
{{- end -}}
{{- $index | jsonify -}}
}

Interesting, I haven’t tried this way. Thanks for your advice!

How about using a details HTML element? Hide everything except the summary and then take the InnerHTML from the content to inject into your container. Advantage: Search engines see your content. Advantage 2: You can have as much other HTML and scripts in that content as you wish. It has click and toggle events and can be styled by status (open/closed).

3 Likes