Template engine

The template is your design in html. As a designer you will augment this with a stylesheet and javascript. In the template you put specific tags that control where the elements and their properties will be filled in, as well as parents and children, when appropriate. You can add as many different templates (blocks) as you want and re-use templates in other templates as well. Everything is combined and compressed once you publish the templates.

Quirk ahead: Bloembraaden used to be called Peatcms, hence you will find some references to ‘Peat’ and ‘Peatcms’ in the code.

Progressive as standard

Standard Bloembraaden only loads and renders the requested element. You, the designer, can load more if necessary. You can also define ‘permanent’ regions, those are parts in the document that are static (like header and footer, use data-peatcms-keep with a unique identifier) as far as the html goes. This further speeds up your website and makes it possible to play video and / or audio without interruption throughout the entire website.

Tags

Use simple tags, like {{title}} to display a current value, use complex tags, like {%__variants__%} to display children and their properties. With {{product:title}} you may render a property (title in this case) of a parent (Product in this case).

By defining a page with slug blurb you can summon it with the complex tag {%blurb%} and use all its properties there. If you summon this in a permanent region (designated by data-peatcms-keep="unique_id", it will be preloaded and cached, and kept in the dom, for your visitor.

Example

{%blurb%}
<section data-peatcms-keep="blurb">
    <h2>{{title_parsed}}</h2>
    {{excerpt_parsed}}
    <p><a href="/{{slug}}" title="{{title|clean}}">More info</a></p>
</section>
{%blurb%}

This will summon the Element with slug blurb and give you access to all its properties in the template, of which you are using the css class, the title, the excerpt and the slug. When blurb does not exist, the entire section will not be rendered.

Note: permanent (data-peatcms-keep) only works with root elements, because children, obviously, are not guaranteed permanence in the DOM. It means the specific section will not be re-rendered, ever.

Available properties for templates

To check the properties available to your template, just check the object returned by Bloembraaden in your developer tools. Look for the request of the Element you want to check out, and see the response.

Rendering the properties in a template

Properties that start with __ must be summoned by a complex tag, e.g. {%__variants__%}. All other properties will be summoned by simple tags, like {{css_class}}, or {{image:src_huge}}.

Complex tags will only be rendered when present. Complex tags usually consist of an array of elements. You can summon the array separately by using {%__row__%}. That way you can easily render a section conditionally, with the items separately inside, like a carousel with products:

{%__variants__%}
<section class="variants carousel">
    <h2>Suggestions</h2>
    <div class="multi-ripple"><div></div><div></div></div>
    <div class="carousel-left button previous">←</div>
    <div class="carousel-right button next">→</div>
    <div class="strip">
        {%__row__%}
        {{> Variant thumb }}
        {%__row__%}
    </div>
</section>
{%__variants__%}

When there are no __variants__ the section will not be rendered at all. As you have seen by now, all complex tags must be opened and closed. Bloembraaden will warn you if open tags are not closed.

The tag {{> Variant thumb }} will summon a partial template named ‘Variant thumb’, it is inserted directly inline. In that partial template, you have access to all the properties of the Variant, in this case. When published these templates will be combined and compressed, so feel free to break up your site in as many partial templates as necessary, this will not slow down the site in any way.

Some properties have a ‘parsed’ version, this means the contents is rendered as valid html. Notably excerpt, title and content. You should use excerpt_parsed, title_parsed and content_parsed in your templates where html is displayed.

Multi-ripple is simply a css animation class in the website design (not Bloembraaden itself) that is present until the carousel is loaded.

Global

For globally available properties inspect the window.PEATCMS_globals object in your javascript console. Especially the ‘session’, ‘user’ and ‘messages’ are interesting, and will be covered in further detail in the javascript section of this howto.

Simple tags that are useful:

Loading bar

If you put an element with id bloembraaden-loading-bar in your template, it will act as a horizontal progress indicator for the Element the visitor navigated to.

Dark mode automated

dark_mode is a session variable, but a special one. dark_mode is always set on the initial output object, so you can capture it in your templates before the session is even loaded. It is also initially updated with the user preference (by OS or browser) if not already present from a previous visit.

If you want to support it in your design, start with setting it on the html element in the dom, and go from there. <html lang="en_US"{{dark_mode: class="dark-mode"}}>

This will set the (css)class ‘dark-mode’ on the html element whenever it is set. That way you can initiate it directly without flickering in your css. You can also see a new structure:

If statements

Simple if-statements are done by adding a : after the property, which will execute what follows if the property is truthy.

{{is_account:<strong>User has an account</strong>}}

If you want to display something when it is falsy, you can use :not::

{{in_stock::not:<span class="warning">Out of stock</span>}}

If you want to display the actual (truthy) value, if there is any, you can use ::value:: inside the statement:

{{excerpt_parsed:<h2>The excerpt</h2>::value::}}

You may also use other simple properties inside the condition, as long as they are present.

Lastly, you can, but I hope you don’t need it, compare two values to get a true / false reading. A client wanted their standard ‘add to cart’ quantity to be 6 for a specific product category (here: XXX), and 1 for all others:

<input type="number" name="quantity" value="{{serie:title:==XXX:6:not:1}}" autocomplete="off"/>

Nested if-statements are also possible.

Filters / functions

Like other templating languages, Bloembraaden supports certain functions using a pipe, e.g. {{title|clean}} cleans the title from tags and whitespaces that are not a regular space. As this slows down the templating engine it is recommended to use sparingly.

In javascript you can add your own filters, just create a function that accepts a string (the content) and returns the string with some manipulation done to it and call it after the pipe character.

Supported functions out of the box:

At the moment you cannot string functions together with the pipe characters!

Elements

Remember you can inspect the Element by simply surfing to it and watching the response in the network tab of your browser. Below are some examples to get you started.

Page

The Page element has the following properties (simple tags):

In the areas where there are multiple pages (you can link multiple pages to a page) you use the complex tag {%__pages__%}. Unlike simple tags complex tags have to be closed, so they always come in pairs.

{%__pages__%}
<h3>{{title}}</h3>
{{content_parsed}}
{%__pages__%}

Within that tag all the simple tags are available for each page. For more fine grained control you have the optional {%__row__%} tag to define the part that has to be repeated for each page.

{%__pages__%}
<h2>Interesting pages</h2>
<section>
    {%__row__%}
    <h3>{{title}}</h3>
    {{excerpt_parsed}}
    <div class="more"><a href="/{{slug}}">More information</a></div>
    {%__row__%}
</section>
{%__pages__%}

All elements work like this, you can link them using drag and drop.

Image

For a single image you can assign a template to display it. Otherwise, use the complex tag {%__images__%}. For each image the following properties are available:

Regardless of what you upload, images are served as WebP with transparency and fallback to jpg.

Note that Instagram images do not feature the tiny and huge sizes.

Embed

The Embed element supports embedding video’s from e.g. Youtube and Vimeo. But also other stuff that is around the web. It features all the standard Element fields. You can link Images to represent the embedded content, and / or as a placeholder, depending on the design.

In addition, it has the {{embed_code}} field. Your website owner can simply put in the ‘embed’ code from a video sharing site in there and it should work. But as a designer you want to format the view a bit better probably, maybe like this, for video’s:

{%__embeds__%}
<div class="embed-wrapper cool-video" data-slug="{{slug}}">
    <div class="embed-iframe">
        <iframe height="2" width="2" frameborder="0" allowfullscreen title="{{title}}" src="{{embed_code}}"></iframe>
    </div>
</div>
{%__embeds__%}

Combined with some css to format the view for your design. Your website owner or editor can then just put in the share link to the video in the {{embed_code}} property.

File

You can link several Images to a File to represent it.

Collections of Elements

While looping through a collection of Elements (e.g. the Images of a Page) an __index__ and __count__ Property are also available. Use them for your styling when appropriate, or have javascript pick up e.g. total number of items directly in stead of having to count html elements.

Note that __index__es are 0 based, like javascript arrays. So the highest index is always one below the (total) __count__.

In the below example you can easily style a single image differently (by targeting .images.total-1 in css) than a section with more.

{%__images__%}
<section class="images total-{{__count__}}" data-total-images="{{__count__}}">
    {%__row__%}
    <img src="{{src_medium}}" data-index="{{__index__}}"/>
    {%__row__%}
</section>
{%__images__%}

Quirk: {{__index__}} and {{__count__}} are simple tags, even though they start with __. These are currently the only exceptions.

More Elements:

E-commerce products are available in a single chain of Elements: BrandSerieProductVariant. Of course these can be linked to Pages, Images, Files and Embeds any way you wish (depending on the template).

The special Property Element can be used as a category or tag and linked to (interchangeable) Pages and Variants, allowing more flexible navigation as well as sophisticated filtering.

Default children and parent Elements

The first Element in a collection is also available directly by using the name and a colon. For instance the first image in a carousel on a page is available, for that page, as {{image:src_medium}}, {{image:css_class}} etc.

This makes it easy to put a sharing image in the <head> of your html page, for instance like this:

<meta property="og:type" content="website"/>
<meta property="og:description" content="{{excerpt_parsed|clean}}"/>
<meta property="og:image" content="{{image:src_large}}"/>
<meta property="og:image:width" content="{{image:width_large}}"/>
<meta property="og:image:height" content="{{image:height_large}}"/>
<meta property="og:image:secure_url" content="{{image:src_large}}"/>

But you can access any property like that, for instance to link to the Brand of a Variant:

<a href="/{{brand:slug}}" class="brand-link">{{brand:title}}</a>

Interact with the backend

Mostly the designer will use javascript to interact with the Bloembraaden backend. However, using forms is sometimes handy, preferable or necessary.

Simply create a html form in your template with the action you need and the corresponding inputs.

Subscribe to newsletter

For instance this simple subscribe to newsletter form:

<form action="/thank-you-for-subscribing">
    <input type="hidden" name="action" value="sendmail"/>
    <input type="hidden" name="subject" value="Newsletter subscription from website"/>
    <input type="hidden" name="to" value="your@email.address"/>
    <input type="email" name="from_email" placeholder="E-mail"/><br/>
    <input type="text" name="from_name" placeholder="Name"/><br/>
    <input type="hidden" name="template" value="Newsletter subscription"/>
    <input type="hidden" name="success_message" value="Thank you for subscribing!"/>
    <input type="hidden" name="failure_message" value="Subscribing failed."/>
    <input type="submit" class="button" value="Subscribe"/>
</form>

The bland action is sendmail, you can use that as form action. But since everything is asynchronous, you can also set the action to a specific page to be shown, and use the hidden input action to point to the actual action.

The hidden input template instructs Bloembraaden to use a specific mail template, otherwise the form data is sent unformatted. The mail template could look like this:

<p>{{from_name}}</p>
<p>{{from_email}}</p>
<p>subscribed to the newsletter. Yay!</p>

Available actions

Directly access an action by posting a hidden form with the name of the action, or link directly to /__action__/name_of_action (for actions that do not require the {{csrf_token}}). Useful actions for the website designer / builder are:

E-commerce

For an e-commerce website there are some special functionalities, that can be handy for other sites as well.

Order related actions

For e-commerce orders there are a bunch of special actions, with an example below.

Example

Show the last order placed by this session, for example on the ‘thank you’ page or directly after confirming. Allow the visitor to pay for the order (if not already paid) and re-order the order as well. Note: {{html}} is simply the html version of the order once placed, this is a fixed value so when your order design changes, existing orders are not affected and will appear as they were when placed.

{%__action__/__order__/detail%}
<header>
    <span class="order-number nowrap">{{order_number|format_order_number}}.</span>
    <span class="nowrap">€ {{amount_grand_total|format_money}}.</span>
    <span class="nowrap">({{date_created}}).</span>
</header>
<section class="order">
    <a href="/__action__/reorder/shoppinglist:cart/redirect_uri:order/order_number:{{order_number}}">ORDER AGAIN</a>
    {{payment_confirmed_bool::not: <a href="/__action__/pay/order_number:{{order_number}}">PAY FOR THIS ORDER</a>}}
    {{html}}
</section>
{%__action__/__order__/detail%}

Account related actions

Shoppinglist

Both a shoppinglist / cart and a wishlist. Just a list of Variants, basically. You can automatically generate such a list for your visitor simply by requesting /__shoppinglist__/your_name in the template, where your_name is the name you wish to give this list. For example: /__shoppinglist__/cart. When not yet present, the list will be initialised for your visitor.

Shoppinglist related actions

Always POST the following info to the action:

A typical ‘add to cart’ form could look like this:

<div class="product-to-cart" data-in-stock="{{in_stock}}">
    {{price_from:<span class="price-from">€::value::</span>}}
    <strong class="price">€{{price}}</strong>
    <form method="post" action="/__shoppinglist__/cart/">
        <input type="hidden" name="action" value="add_to_list"/>
        <input type="hidden" name="variant_id" value="{{variant_id}}"/>
        <input type="number" name="quantity" value="1" autocomplete="off"/>
        <input type="submit" value="Add to cart"/>
    </form>
    {{message:<em>::value::</em>}}
</div>

Enhanced html

Bloembraaden offers some fancy html enhancing out of the box, for commonly used functionality. When present in the DOM these will be handled automatically.

Post form by ajax

Bloembraaden posts your forms by ajax including the required {{csrf_token}} and some context data. To override this set data-peatcms_ajaxified="1" on your form. Bloembraaden will then skip enhancing it.

Image with sourceset for optimum size

Because the native html sourceset / srcset did not perform adequately for my clients, I made this simpler one. Just supply the sources you want to use like in the examples below, and Bloembraaden will load the right one for you and gently fade it in (fast).

<header id="hero" data-srcset='
{%__images__%}
{{__index__:=={{__count__|minus_one}}:
[   {"width":"{{width_medium}}","height":"{{height_medium}}","src":"{{src_medium}}"},
    {"width":"{{width_large}}","height":"{{height_large}}","src":"{{src_large}}"},
    {"width":"{{width_huge}}","height":"{{height_huge}}","src":"{{src_huge}}"}]
}}
{%__images__%}
'></header>

The above will output the srcset for the last image of a collection (the highest __index__ is one below __count__ because indexes are zero based). Use this for example when the first image is used for something else (e.g. a sharing image).

The next example is more common, a more complete handler of an image that will output a figure element with a proportional svg placeholder in the img to prevent CLS (Cumulative Layout Shift).

<figure class="{{css_class}}">
    <img{{online::not: hidden}} data-srcset='[
        {"width":"{{width_medium}}","height":"{{height_medium}}","src":"{{src_medium}}"},
        {"width":"{{width_large}}","height":"{{height_large}}","src":"{{src_large}}"},
        {"width":"{{width_huge}}","height":"{{height_huge}}","src":"{{src_huge}}"}
    ]' src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 {{width_tiny}}
        {{height_tiny}}'%3E%3C/svg%3E" alt="{{excerpt|clean}}"/>
    {{description_parsed:
    <figcaption>::value::</figcaption>
    }}
</figure>

Lazy load images

When you use the Bloembraaden source set as described above, your image will be lazy loaded with a nice fade in. Of course you can simply use loading="lazy" on any image. If you want only the first image to be visible instantly, simply use something like this:

{%__images__%}
<img src="{{src_large}}" alt="{{title}}"{{__index__: loading="lazy"}}/>
{%__images__%}

When the index is truthy, it will append the lazy loading directive. So it will not for the first __index__ (indexes are 0 based).

Bloembraaden has its own lazy loader as well that has a bit more options. Use data-src on your element and the corresponding source will be lazy loaded. In the case of an image, it will be set as the source, on other elements the image will be loaded as background.

<aside class="portfolio-image" data-src="{{src_large}}"><div>{{title}}</div></aside>

Interactive carousel

The carousel is a horizontally scrolling area in your design that can hold anything that is available as a collection (Images, Variants, most Elements really).

The following example will summon the page ‘art’ which is a brand. Then it will display its __series__ in a carousel, using the first (default) image of each, and displaying a link with the excerpt (if available).

<div class="carousel"> is mandatory for a carousel to work, the inner <div class="strip"> is highly encouraged to decouple the scrolling behaviour from the carousel element. Class slide is mandatory on each individual slide.

{%art%}
    {%__series__%}
    <div class="carousel" aria-role="region" aria-roledescription="carousel">
        <div class="strip">
        {%__row__%}
        <div class="slide {{css_class}}">
            <img src="{{image:src_medium}}" alt="{{image:title}}"/>
            <h3><a href="/{{slug}}" class="cta on-page">{{title}}</a></h3>
            {{excerpt_parsed:::value::}}
        </div>
        {%__row__%}
        </div>
        {{> Carousel Navigation }}
    </div>
    {%__series__%}
{%art%}

The ‘Carousel Navigation’ partial template is just a small reusable bit with next and previous arrows:

<ul class="carousel-nav">
    <li class="carousel-left" title="previous"></li>
    <li class="carousel-right" title="next"></li>
</ul>

The css classes from the examples will provide standard behaviour for the arrows, but you are free to do it your own way.

Interactive slideshow

A simple slideshow with optional next and previous buttons, starts sliding automatically. The sliding interval is a time in milliseconds defined by the data-interval property on the slideshow element.

In the following example the slideshow has a small version of the image as background image, which acts as a placeholder until the higher res image is loaded from the data-srcset.

<section class="peatcms-slideshow" data-interval="9999">
    {%__images__%}
    <div class="peatcms-slide-entry {{css_class}} slide-on-landing-page" data-total_items="{{__count__}}" style="background-image:url({{src_small}})" data-srcset='[
    {"width":"{{width_medium}}","height":"{{height_medium}}","src":"{{src_medium}}"},
    {"width":"{{width_large}}","height":"{{height_large}}","src":"{{src_large}}"},
    {"width":"{{width_huge}}","height":"{{height_huge}}","src":"{{src_huge}}"}
]'>
        <div class="peatcms-slide-content">
            {{description_parsed}}
        </div>
    </div>
    {%__images__%}
    <nav class="peatcms-slide-nav">
        <span class="button previous">←</span>
        <span class="button next">→</span>
    </nav>
</section>

Quirk: the mandatory classes are prefixed by peatcms, the old name for Bloembraaden.

Useless code

The plan is to not have the code included if the templates do not offer the functionality. But we are not there yet.