Last active
October 6, 2018 14:14
-
-
Save ben-rogerson/d36d725f3bbd1fa392dd5a9d6928caf0 to your computer and use it in GitHub Desktop.
Craft 3 Templates: Experimenting with Twig Objects as HTML building blocks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{# | |
# 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