How to access variables defined in content of page in corresponding layout


#1

Hi,
I have pages that use default layout. Now, I have <script> tag specific to each page. But they need to be accessed in layout, as scripts are dependent on jQuery which is specified at last of <body> tag in default layout and scripts won’t run unless loaded after jQuery has been loaded.

So, if I define a variable say pageScript in content of page as follows:

    {% capture pageScript %}
    <script>
    ...
    </script>
    {% endcapture %}

then is there any way in which I can access this variable in default layout after occurrence of {{ content }}?

Thanks in advance


#2

One option is to define the pageScript in the page’s front-matter. For example:

---
title: a page
pageScript: |
  $(function () {
    ...javascript... 
  })
---

Then position the pageScript where needed in the layout:

{{ content }}
<script ... load jQuery ...>
<script>
{{ page.pageScript }}
</script>

#3

The issue isn’t entirely clear to me. How exactly is the <script> specific to each page?
Will you be able to provide a minimal and simple example?

Nevertheless, please understand the following technical aspects:

  • Jekyll process Liquid Templates only at the build time.
  • JavaScript executes scripts on each page load.

Consider the following scenario.
I have the following script in my layout:

<script>
  $(document).ready(function(){
    $("button").click(function(){
      $("#test").text({{ page.banner_text | default: 'Hello World' }});
    });
  });
</script>

{{ page.banner_text | default: 'Hello World' }} above is the placeholder that allows every page using the layout to provide a custom text for the script via the page’s front matter. If a page doesn’t provide a custom text, the default string 'Hello World' is used.


#4

How exactly is the <script> specific to each page?

There is search page which has scripts certain to itself, there is another page where there is script to make a div have its position: fixed after it attains a certain offset from top.

The issue isn’t entirely clear to me.

Allow me to explain.

Expected Behavior

When we define a variable in content area of a page/post using say capture tag, then that variable should be accessible in the layout file the page/post uses, after insertion of {{ content }}. See Code Sample

Current Behavior

A variable defined in the content area of a page/post is not accessible in the layout file the page/post utilizes.

Example & Code Sample

page.html

---
layout: default
---

...code...

{% capture pageScript %}
<script>
...
</script>
{% endcapture %}

_layouts/default.html

---
---

{{ content }}

<script src="link-to/jquery.js"></script>

{% if pageScript %}
  {{ pageScript }}
{% endif %}

As we are trying to access {{ pageScript }} after insertion of {{ content }} in layout file, it should have been accessible.

Thanks for your generous help @ashmaroli, @chuckhoupt.


#5

What you’re expecting, is not technically possible even with the latest version of Jekyll.
A “layout” only has access to three “context data”:

  • Data from the layout’s own front matter
  • Data from a “page’s front matter”
  • Data from the “site’s configuration”

Any variable defined inside the page.content is not propagated beyond the content itself. There is no “data-leaking” here.

The only way available is to follow @chuckhoupt’s suggestion.


#6

I see.

@chuckhoupt’s solution poses a problem, that we have to keep an eye on YAML’s syntax too. So, in case a line in script tag does not have enough indentation to form part of pageScript in front-matter, the code breaks because of invalid YAML markup. This gives rise to look for syntax of both the script and YAML, simultaneously. Other than that, it is good.

Anyway, does anyone know if there is any possibility in future that makes variables defined in a page/post’s content area accessible in layout used for corresponding page/post?

On a side-note:
As I am facing problem primarily for scripts written in jQuery, I found a quick-fix that allows to make scripts depending on jQuery executable even though they come before script tag of jQuery. This answer on Stack Overflow stated that if you can convert text to base64 format, then by using script tag with src attribute and reading base64 encoded string, one can defer the script thus making it execute after jQuery. There are a few plugins available as also plenty of online base64 encoders. A dirty but intelligent way IMO.


#7

Another option would be to allow pages to toggle the standard JS libraries on/off via a boolean. For example, a custom_js variable in combination with a standard_libs.html include (which loads jQuery, etc).

page.html:

---
title: a page
custom_js: true
---
...

{% include standard_libs.html %}
<script>
// page custom script
$(....)
</script>

_layouts/default.html:

---
---
{{ contents }}
{% unless page.custom_js %}
{% include standard_libs.html %}
{% endunless %}