Skip to content

Instantly share code, notes, and snippets.

@ben-rogerson
Last active October 6, 2018 14:14
Show Gist options
  • Save ben-rogerson/d36d725f3bbd1fa392dd5a9d6928caf0 to your computer and use it in GitHub Desktop.
Save ben-rogerson/d36d725f3bbd1fa392dd5a9d6928caf0 to your computer and use it in GitHub Desktop.
Craft 3 Templates: Experimenting with Twig Objects as HTML building blocks
{#
# Craft 3 Templates: Experimenting with Twig Objects as HTML building blocks
Ben Rogerson <[email protected]> 2018
This Gist explores a way to create HTML from a formatted TWIG object.
I found it encourages a different way of thinking when writing HTML.
Here's an example that uses the emmet shorthand to create a `<button>` with a `<main>` parent.
In TWIG Templates:
{{ self.render(
[{
abbr: 'main.item.item--hasbutton#anchor',
inner: [{
abbr: 'button.item__button',
inner: 'Send',
}]
}]
) }}
Output HTML:
<main class="item item--hasbutton" id="anchor">
<button class="item__button">Send</button>
</main>
## Features
- Emmet style for element creation (includes class or id)
- Some Twig macro weirdness to get the job done
## How to use this
Paste this whole Gist into a .twig file within your Craft 3 installation.
You'll see a few examples that show up for easy tweaking.
## This work is experimental
You're likely to find bugs here and there.
#}
{#
================================================================================
Add Attributes Macro
================================================================================
Converts an object to a list of attributes. Great for keeping things clean when
working with a bunch of values. If value is falsy then it won't output the
attribute.
Usage:
{% set attributes = {
class: 'link'
href: '#',
target: link.blank ? "_blank",
} %}
<a {{ utils.addAttributes(attributes) }}>Text</a>
#}
{% macro addAttributes(attributes) %}{{' '}}{% spaceless %}
{% set output %}
{% for key, value in attributes %}{{' '}}{% spaceless %}
{% set keyValue = "=\"#{value|join(' ')}\"" %}
{{ key }}{{ keyValue|raw }}
{% endspaceless %}{% endfor %}
{% endset %}
{{ output|trim|raw }}{% endspaceless %}
{% endmacro %}
{#
================================================================================
Render Items Macro
================================================================================
Creates html elements from a structured twig object.
Usage:
{{ macro.render([
{
abbr: 'div.container',
inner: 'Inside content'
}
]) }}
{{ macro.render(
[{
abbr: 'main.item.item--hasbutton#anchor',
inner: [{
abbr: 'button.item__button',
inner: 'Send',
}]
}]
) }}
#}
{% macro render(items) %}
{% import _self as self %}
{% for item in items %}
{#
match words starting with .# and include --
match words starting with .# and include -
match words starting with .#
match [anything]
TODO: improve regex
#}
{% set itemElement = (item.abbr|default
? item.abbr|replace('/([.#]\\w+--?\\w+)|([.#]\\w+-?\\w+)|(\\[[^\[\\]]+\\])/', '')
: (item.element ?? 'div')
) %}
{# TODO: improve regex #}
{% set class = item.abbr|default ? item.abbr|replace('/([#]\\w+-?\\w+)|(\\[[^\[\\]]+\\])/', '')|split('.')|slice(1) %}
{% set id = item.abbr|default ? item.abbr|replace('/([.]\\w+-?\\w+)|(\\[[^\[\\]]+\\])/', '')|split('#')|slice(1) %}
{% set itemAttributes =
self.addAttributes(
item.abbr|default
? (
{}|merge(
(class ? {class: class} : {})
)|merge(
(id ? {id: id} : {})
)
)
: item.attributes|default
)
%}
{# Some elements like img doesn't have an end tag so lets cater #}
{% if 'img' in itemElement %}
<{{ itemElement }}{{ itemAttributes }} />
{% else %}
<{{ itemElement }}{{ itemAttributes }}>{% spaceless %}
{% if item.inner|default is iterable %}
{{ self.render(item.inner) }}
{% else %}
{{ item.inner|default|raw }}
{% endif %}
{% endspaceless %}</{{ itemElement }}>
{% endif %}
{% endfor %}
{% endmacro %}
{#
================================================================================
Create a container with button inside
================================================================================
#}
{{ self.render(
[{
abbr: 'main.item.item--hasbutton#anchor',
inner: [{
abbr: 'button.item__button',
inner: 'Send',
}]
}]
) }}
{#
Output:
<main class="item item--hasbutton" id="anchor">
<button class="item__button">Send</button>
</main>
#}
{#
================================================================================
Add some more attributes
================================================================================
#}
{{ self.render(
[{
element: 'main',
attributes: {
class: ['item','item--has-button'],
'data-name': 'button-parent',
},
inner: [{
element: 'button',
attributes: {
class: 'item__button',
type: 'submit',
},
inner: 'Send',
}]
}]
) }}
{#
Output:
<main class="item item--has-button" data-name="button-parent">
<button class="item__button" type="submit">Send</button>
</main>
#}
{#
================================================================================
Add a conditional button
================================================================================
#}
{% set showButton = false %}
{{ self.render(
[{
element: 'main',
attributes: {
class: [
'item',
showButton ? 'item--has-button' : 'item--no-button'
],
},
inner: [
showButton
? theButton
: {
abbr: 'div.item__note',
inner: 'Sorry, no button here'
}
]
}]
) }}
{#
Output:
<main class="item item--no-button">
<div class="item__note">Sorry, no button here</div>
</main>
#}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment