How to Improve the Flexibility of Widgets in Bolt 3

Phillipp
Bolt CMS
Published in
4 min readJun 20, 2016

Bolt 3 comes with a new feature called “Widgets”. With them, you can easily extend the frontend and backend of a Bolt application. The concept is not new but it wasn’t a thing in Bolt before.

How They Work

It’s pretty simple. There are different named widget locations. Then a Bolt extension can push HTML into these locations to show some sort of content. While there are widget locations all over the Bolt backend, it’s up to the theme developer to add them to the frontend.

The Problem

The backend has a known HTML structure, but the frontend don’t. That’s where it gets tricky.

Meaningful locations for widgets in the frontend are the sidebar, the footer and maybe on top and below the content area. The sidebar and the footer are very likely to have some sort of wrapper around each item that handles some spacing, background, etc.

But the extension cannot know about the need for such a wrapper.

Let me show you an example:

The template of a sidebar with two widget locations could look like the following.

<div class="sidebar">
{{ widgets('aside_top') }}
<div class="sidebar_item">some content ...</div>
<div class="sidebar_item">some content ...</div>
<div class="sidebar_item">some content ...</div>
{{ widgets('aside_bottom') }}
</div>

And now we have an extension that wants to display the following list in the sidebar.

<ul>
<li>Very</li>
<li>Useful</li>
<li>List</li>
</ul>

But what’s with the wrapper? We will end with a sidebar with broken spacing and other missing styles that are applied via the wrapper.

The Solution

Update: Scroll to the end to see the built in way to handle it all via the theme.

While we can’t expect the extension to know about the wrapper, we could tell it about the wrapper if both, the extension and the theme, implemented the technique I will describe below.

What is needed:

  • The extension has to accept a wrapper template
  • The theme has to provide a wrapper template

The idea is simple. We don’t just tell the extension about the widget location to use, we also tell it if there is a wrapper template to use.

An extension config could look like this:

widget:
location: aside_top
wrapper_template: widget_aside_wrapper.twig

The template that will be displayed by the extension could look like this:

{% extends wrapper %}
{% block content %}
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
{% endblock content %}

The important part here is that it extends a template that will be passed via the wrapper variable.

The wrapper template must include the content block so our content will be inserted when we extend the template.

<div class="sidebar_item">
{% block content %}{% endblock content %}
</div>

To get this wrapper, I wrote a small and helpful function:

protected function getWidgetWrapper($template)
{
$wrapper = $this->getContainer()['twig']
->createTemplate(
"{% block content %}{% endblock content %}"
);
if($template){
$wrapper = $this->getContainer()['twig']
->loadTemplate($template);
}
return $wrapper;
}

It receives the template name defined in the extension config and returns a twig instance so you can pass it to your own template. If the user didn’t define a wrapper template, it will create an empty one that we can extend.

And that’s how we use it in our widget callback function:

public function callbackWidget()
{
$wrapper = $this->getWidgetWrapper(
$this->getConfig()['widget']['wrapper_template']
);
return $this->renderTemplate(
'widget.twig',
['wrapper' => $wrapper]
);
}

This approach work really well, even if it’s only a workaround for a problem that nobody thought about when the widget functionality was implemented.

Maybe Bolt will implement a solution for wrappers in a future version, but until that, you should definitely consider this approach for your next extension.

Tip: As you can see above, you can create a twig instance from a string. That means we don’t even need themes that provide a wrapper template. The user could just define the wrapper template as string in the extension config.

Or Use the F#!cking Built in Functionality

I was about to create an RFC to suggest that we can pass a wrapper template via the widgets() function so the theme handles everything. Of course, the functionality is already there and works a bit different though.

So, in our theme, we can do the following:

{{ widgets('aside_top', 'frontend', 'sidebar_widget_wrapper.twig') }}

The provided sidebar_widget_wrapper.twig doesn’t look like the wrapper template from my solution above. Here we have to iterate over the widgets to wrap them. It’s not very difficult though:

{% for widget in widgets %}
<div class="sidebar_item">
{{ widget.html | raw }}
</div>
{% endfor %}

That’s all it needs to use the built in functionality which might also be the better solution in the long run. However, if the used theme doesn’t provide this, my initial idea in combination with a wrapper template defined as string in the config could be a valid fallback.

--

--