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.
Important: only elements that are in the root of the document can be permanent. Children are never considered permanent.
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.
- Where the response says:
{__ref: "xxx"}
the ‘xxx’ part is the slug, look for theslugs
property in the root object to find ‘xxx’ to check further. - If the response says something like
x_cache_timestamp_ok: true
it means the browser cache has the most recent version and the Element is not fetched nor sent. - Force the Element to be fetched anew by clicking the ‘view’ icon
⊙
in theEDIT
column.
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.
It is advised to use the {%__row__%}
tag always, for its behaviour is more consistent and uses less resources than the automated rendering of the rows.
For example 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:
{{version}}
- the current Bloembraaden version.{{version_timestamp}}
- the timestamp of the publishing of the templates, use it for cache-busting.{{nonce}}
- the nonce you need to add to any javascripts for them to function.{{csrf_token}}
- post this with a form for validation. Automatically posted when you let Bloembraaden handle your form.{{root}}
- the root of your website, e.g. https://how-to.bloembraaden.io/.{{dark_mode}}
- see below.{{session}}
- the entire session object, but you should probably only access the session through its javascript methods.{{is_account}}
- a boolean value indicating whether the visitor has an account.{%__user__%}
- the user and their properties, gets dynamically updated of course, like everything else.{%__messages__%}
- the messages received from the server. Generally these are handled by Bloembraaden. You can, of course, format the look of the messages with css.
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 or not present at all, 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:
plus_one
- adds one to an integer.plus_two
- adds two to an integer.minus_one
- subtracts one from an integer.as_float
- displays number as float for the locale.format_money
- formats number as money for the locale.clean
- removes html tags and line endings from a string, returns double quotes enquoted.format_order_number
- superseded, use{{order_number_human}}
for a readable version of any order number.enquote
- enquotes double quotes.no_render
(internal function) - blocks template tags from being rendered.encode_for_template
(internal function) - usesno_render
to return clean html that will not be rendered further.
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):
{{slug}}
- the url part after your domain name that summons this page{{title}}
{{excerpt_parsed}}
- a short version of the contents{{content_parsed}}
- the contents of the page{{template_id}}
- the template assigned to it{{date_published}}
- the date the page was published (user editable){{css_class}}
- classnames you can use to target in your designs
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:
{{slug}}
- this summons the image template{{title}}
- use this as alt text{{excerpt_parsed}}
- a short version of the description{{description_parsed}}
- long description of the image{{css_class}}
- classnames you can use to target in your designs{{src_tiny}}
,{{src_small}}
,{{src_medium}}
,{{src_large}}
,{{src_huge}}
- direct link to the specific size of the image{{width_tiny}}
and{{height_tiny}}
- numeric, the size in pixels of the tiny version of the image, also available for all other sizes (e.g.{{width_large}}
).
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
{{slug}}
{{title}}
{{content_type}}
- (not editable){{extension}}
{{excerpt_parsed}}
- a short version of the contents{{description_parsed}}
- the (long) description of the 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: Brand → Serie → Product → Variant. 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.
subject
is optional.to
is also optional, but when you specify it, the e-mailaddress must be whitelisted in Bloembraaden admin environment.- The messages are also optional, several solutions can be used to provide feedback, via javascript.
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:
download
- expects the slug of the file as third part of the url, like this:/__action__/download/{{slug}}
account_delete_session
- allows a visitor to delete their session, useaccount_delete_sessions
to delete all session of a user except the current one.properties
- gets an object with all properties, optionally POSTfor=Page
to get properties for Pages, rather than Variants.properties_valid_values_for_path
- you must POST the path you want this for:path=/my-path
. Returns an object with all the properties and values that are relevant to that path. Quirk: if none is relevant, it returns all.instagram
- supplyfeed
and the name of the feed, as present in Bloembraaden admin, as well, like this:{%__action__/instagram/feed/name%}
, to get the feed. Otherinstagram
sub-actions are internal (authorize
,confirm
,delete
).suggest
post_comment
get_template_by_name
- POST the required template name, and initialize aPEATCMS_template
object with the returned data. With this object, you can render Elements of the type this template is made for. Example: Progressive loading example
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.
order
- order the shoppinglist. You need to POST a lot of info, including which shoppinglist to order.pay
- create a payment link for a certain order, needs order number and optionally slug of the payment page.reorder
- put the contents of an existing order in a shoppinglist, needs order number and shoppinglist name.detail
- will retrieve the last order from session, or nothing when no order was placed with the session.
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
account_create
account_login
account_password_forgotten
account_password_update
account_update
account_delete_sessions
- delete sessions for this account, except the current session, useaccount_delete_session
to delete current session.create_address
delete_address
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
add_to_list
remove_from_list
update_quantity_in_list
Always POST the following info to the action:
- shoppinglist (the name of the list to use, e.g. ‘cart’).
- variant_id (only variants can be in a shoppinglist).
- quantity (optional, defaults to 1).
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{{online::not: hidden}} class="{{css_class}}"> <img 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.