Create custom helpers to add functionality to your Ghost blog
I use the Ghost blogging platform to publish my content here on this blog. To be honest, I think that Ghost has the potential to be an awesome platform — but for now, it does lack some features.
Then why am I using it? Well, it's fun :) Fun to try to figure out how something works, how something could be extended and made better, sometimes without documentation or any help from Google. And since my blog is not a very important production site, I can do this without consequences.
Currently you have 3 options if you want to add new funtionality to Ghost:
- Create an app; apps are still in beta and not supported and unfortunately I couldn't make them work for anything more than the easiest, dummy sample described in the docs.
- Modify the source code directly; needless to say, this is way overkill and over my paygrade
- Create a custom helper; this is somewhat of a middle-ground. You do have to write code and hack into the Ghost core and it is not supported or documented, but works for complicated scenarios and the amount of hacking is not that much.
For a novice, but definitely not expert Javascript/Node.Js-guy like me, the third option is definitely the way to go. In this post I will show you how to create a custom block helper and use it in your themes.
Creating a simple block helper
Let's say you want to create a simple block helper. Block helpers are the ones that are used in your template like this:
<body>
<h1>Some page</h1>
{{#outputSomething}}
{{/outputSomething}}
</body>
And then, when the template is rendered into html, the rendering engine simply outputs the result of the helper in place of the helper itself.
So what is a helper and how can you create one? A helper is basically just a javascript function that returns a string. That is the result of the helper and that will be rendered into the html instead of the block helper (there are also non-block helpers, maybe I'll do a post on that later). To create a helper, simply create a function and export it. So let's say you are creating the helper above and you create a new file called outputSomething.js. And inside you create a function and export it:
module.exports = function outputSomething() {
return "Some value";
}
Of course the string can be anything; usually valid html that is inserted at the place of the block. Maybe something like this:
module.exports = function outputSomething() {
return "<h2>Some value</h2>";
}
Now your helper is done, but you cannot use it in your templates just yet. First, you have to register the helper with Ghost.
Registering the helper
So now that you have your helper, you have to register it with Ghost. Here's what you need to do, step by step:
- Navigate to the core/server/helpers folder. Copy the helper file into this folder.
- Navigate back to the core folder to install all the dependencies your custom helper might use. Ghost uses yarn, so you'll have to issue yarn add packagename command, while in this folder.
- Navigate back to the core/server/helpers folder and open the index.js file in this folder. This is where you have to register the helper. If you look at the file structure, you can probably guess the couple of lines you need to add yourself.
- First, you have to require the helper file in the index.js. Simply add it to the first section of the file like this: coreHelpers.outputSomething = require('./outputSomething'); Be sure to correctly reference the file that you have copied into this folder in step 1.
- In the next section of the file, register the helper inside the registerAllCoreHelpers() function. If the helper is a synchronous helper, use the registerThemeHelper() function. If the helper is an asynchronous helper, use the registerAsyncThemeHelper() function (more on async helpers later). The first parameter is the name of the helper, the second parameter is the object that you assigned the module in step 4.
When you're done, you can use the newly registered helper in your theme file. However, Ghost's template validating engine, gscan still doesn't recognize your helper as a valid helper when activating the theme. So you have to do one of the following:
- Edit your theme, but comment out the lines with your custom helper before uploading or activate the theme. Then inside the production environment, comment out the selected lines again.
- Edit gscan to recognize the helper. Go to your node_modules folder and find gscan. Inside the libs/specs/v1.js file you'll find the declaration of the knownHelpers array somewhere at the beginning of the file. Simply add your helper's name to the array and you're done.
Check out the referenced files for more information, I'm sure you'll get the idea.
Adding parameters to the helper
This is all good, but for a helper to be really useful and more than just a shortcut for a code snippet, it has to be parameterizable. So how can you add parameters to helpers?
Easy. Just add a parameter to your function:
module.exports = function outputSomething(inputValue) {
return "<h2>Some value "+inputValue+"</h2>";
}
And then use it like this in your template:
<body>
<h1>Some page</h1>
{{#outputSomething "and a parameter"}}
{{/outputSomething}}
</body>
As far as I can tell, you can pass in strings, numbers, booleans and arrays as parameter. The helper also operates in the current context, so if you have a property of the current context, you can pass that in to. So if you want to use it inside another helper, you can do it like this (of course you'd have to modify the helpers code, but you get the idea):
<body>
<h1>Some page</h1>
{{#get 'posts'}}
{{#outputSomething posts}}
{{/outputSomething}}
{{/get}}
</body>
Here, posts is an available property of the current context, which is populated by the get helper.
Now what if you want to have more parameters? As far as I can tell, this is not supported with this syntax. If you want to have more parameters, then you have to specify the parameters as name-value pairs when using the helper in your template:
<body>
<h1>Some page</h1>
{{#outputSomething "and a parameter" param1="and another" param2="and yet another"}}
{{/outputSomething}}
</body>
Then, you have to modify the helper function to take one more parameter (regardless of the number of parameters you passed to the helper in the template). This is usually named options. This parameter has a property called hash, and you can access the parameters by name as properties of this object:
module.exports = function outputSomething(inputValue, options) {
return "<h2>Some value "+inputValue+ " "+options.hash.param1+" "+options.hash.param2 +"</h2>";
}
Using other helpers inside the block helper
While parameterizability makes the helpers a lot more versatile, we still cannot use them to their full extent. Let's imagine a situation like this:
- You want to create a helper
- You pass in a parameter to the helper and the helper downloads a list of objects via an http request
- Every object has two properties: title and url, both strings
- And you want to be able to generate a list of url-s in the html for every object in the downloaded list
This is where the real use of block helpers can be demonstrated. Basically what we want is something like this:
<body>
<h1>Some page</h1>
<ul>
{{#outputSomething "www.downloadDataFromThis.com/dataSource"}}
<li> <a href="{{url}}">{{title}}</a> </li>
{{/outputSomething}}
</ul>
</body>
If there are 3 objects in the list downloaded by the helper, the output is expected to be something like this:
Some page
``` Here the inner template is also expanded and substituted in combination with the outer block helper. So how do you do this? Actually, this is quite easy. The inner template is compiled and then accessible as the _fn_ property of the _options_ parameter. So all you have to do is create your array, then iterate over the array and call this function with the currently iterated element of the array. And of course collect the results of the inner-template-function calls to a variable. Something like this:module.exports = function outputSomething(inputValue, options) {
// TODO: download data from the web; now just create dummy arrays
var data = [
{
url : "https://myUrl.com/res1",
title: "Resource1"
},
{
url : "https://myUrl.com/res2",
title: "Resource2"
},
{
url : "https://myUrl.com/res3",
title: "Resource3"
}
];
var result = '';
data.forEach( (item) => { result = result + options.fn(item) });
return result;
}
You call the options.fn function while iterating through the array. This function represents the inner template and the argument of the function becomes the context of the inner template. So when you specify something like {{title}} in the inner template, this will, at rendering time, actually refer to the title property of the object that is passed to the options.fn function. Easy enough, right?
Inside the block helper you can also create conditional templates with the {{#if}} and {{else}} block helpers. If you use this, the options.fn function refers to the if-part, and there is another function property, options.inverse that refers to the template in the else-part.
This is where block helpers really shine: you can combine the helpers and the inner template is accessible to you in your custom helper. Of course, this can go further down, not just one level.
Asynchronous helpers
Just a word about asynchronous helpers. So before I said that helpers are essentially just functions that return strings. This is almost true — they can return strings or promises that are resolved to strings. If a helper returns a promise, then it is an asynchronous helper. This is useful if the helper makes asynchronous calls: you can return the promise and the templating engine will resolve it and substitue the result. There is really nothing more to it: just be sure to return a promise (and register it with the correct registration method, as stated above).
Further info
For further information check out the general Handlerbars helper documentation. This is not Ghost specific, but is a good starting point. Also check out my Github repository containing my custom Ghost helpers that I use in this blog too.