Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
THEME DEVELOPMENT WITH
TIMBER AND TWIG
WHO AM I?
EMILY HORSMAN
▸ Tweet at me! @emilymhorsman 😍
▸ Email me! me@emilyhorsman.com
▸ Service & Support at Wise & Hamm...
‣ All links from this talk are available at

emilyhorsman.com/t
!?
WHY WOULD I USE A TEMPLATE ENGINE?
▸ Easier to write D.R.Y. and re-usable views
▸ Less to think about, write your logic...
!?
WHAT’S TIMBER?
▸ Cleaner, faster theme development
▸ Can be used on top of existing themes
▸ Easy use of the Twig templ...
$thumb_id = get_post_thumbnail_id($post->ID);
$thumb_url = wp_get_attachment_url($thumb_id);
<img src="<?php echo $thumb_u...
<img src="{{ post.thumbnail.src }}" alt="{{ post.title }}">
Twig
Timber
{% for post in posts %}
<div class="post{% if post.has_term(3, 'category') %}-cat-three{% endif %}">
<h2>
<a href="{{ post...
FINThanks for your time
I assume you’ll now write WP differently forever
LET’S BUILD A THEME WITH TIMBER
HOW TIMBER WORKS — CONTEXT
Get Data!
posts
taxonomies
menu items
widgets
etc…
Array of data

“Context”
Twig

Template
HOW TIMBER WORKS — COMMON CONTEXT
functions.php
class StarterSite extends TimberSite {
function __construct() {
// ...
add...
HOW TIMBER WORKS
<?php
/**
* The template for displaying all pages.
*
* …
*/
$context = Timber::get_context();
$context['p...
BLOG POST
BLOG POST
$context = Timber::get_context();
$post = Timber::query_post();
$context['post'] = $post;
Timber::render( array(...
BLOG POST
<h1><a href="{{ post.link }}”>{{ post.title }}</a></h1>
<p>
By <a href=“{{post.author.path}}">{{post.author.name...
BLOG POST
<ul class="list-inline">
<li>{{ label }}:</li>
{% for term in terms %}
<li>
<a href="{{ term.link }}”>
<span cla...
!!!
post.content

post.thumbnail

post.author

post.title

post.categories

post.tags

post.comments

post.date

post.link...
ARCHIVE/MULTIPLE POSTS PAGE — CATEGORY PAGE EXAMPLE
ARCHIVE/MULTIPLE POSTS PAGE
$context = Timber::get_context();
if ( is_category() ) {
$cat_id = get_query_var('cat');
$cont...
ARCHIVE/MULTIPLE POSTS PAGE
{% if category %}
<div class="jumbotron">
<div class="container">
<h2>{{ category.title }}</h2...
ARCHIVE/MULTIPLE POSTS PAGE
<div class="row">
<div class="col-md-4">
{% if post.thumbnail.src %}
<img src="{{post.thumbnai...
ARCHIVE/MULTIPLE POSTS PAGE — PAGINATION
{% if pagination.prev %}
<li><a href="{{ pagination.prev.link }}">Prev</a></li>
{...
MENUS
MENUS
add_action( 'init', array( $this, 'register_menus' ) );
function register_menus() {
register_nav_menu(‘main-nav-bar'...
MENUS
<nav>
<a href="{{ site.link }}">{{ site.title }}</a>
<ul>
{% for item in nav_bar.get_items %}
<li class="… {% if ite...
WIDGET LOCATIONS
WIDGET LOCATIONS
add_action('widgets_init', …);
register_sidebar(array(
'name' => 'Site Sidebar',
'id' => ‘site_sidebar', ...
WIDGET LOCATIONS
<aside class="sidebar">
{{ sidebar }}
</aside>
base.twig
<nav>

<!-- … -->

{{ nav_widgets }}

</nav>
com...
WE CAN RENDER A WIDGET
lib/widgets/navbar_search.php
class NavbarSearchWidget extends WP_Widget {
public function __constr...
WE CAN RENDER A WIDGET
widgets/navbar_search.twig
{{ args.before_widget }}

<form action="/" class=“form-inline …">
<label...
FILTERS AND FUNCTIONS
{{ post.tags|join(‘, ‘) }}
{% for post in posts %}

{{ cycle([‘odd’, ‘even’], loop.index0) }}

{% en...
CUSTOM FUNCTIONS
add_filter( 'get_twig', array( $this, 'add_to_twig' ) );
function add_to_twig( $twig ) {
$twig->addFuncti...
CUSTOM FILTERS
add_filter( 'get_twig', array( $this, 'add_to_twig' ) );
function add_to_twig( $twig ) {

$twig->addFilter(...
SHORTCODES!
add_shortcode(
‘tease_post',

array( $this, 'tease_post_func' )
);
function tease_post_func($atts) {
$a = shor...
!!!
TEMPLATE INHERITANCE AND BLOCKS
page.php page.twig
base.twig
wrapper.twig
Timber::render(‘page.twig’,…)
{% extends “ba...
TEMPLATE INHERITANCE AND BLOCKS
{% extends "base.twig" %}
{% block content %}
<article>
{{ post.content }}
</article>
{% e...
TEMPLATE INHERITANCE AND BLOCKS
<html …>
<head>
…
{{ fn('wp_head') }}
</head>
<body>
{% block body %}
Default body!
{% end...
!! 🤖
NEAT TRICK TIME: PAGE TEMPLATES
!! 🤖
VARIABLE TEMPLATE EXTENSION
page.php page.twig
base[-fluid].twig
wrapper.twig
Timber::render(‘page.twig’,…)
{% extend...
VARIABLE TEMPLATE EXTENSION!! 🤖
<?php

/**
* Template Name: Fluid Page
*/
$context = Timber::get_context();
$context['base...
VARIABLE TEMPLATE EXTENSION!! 🤖
page.twig (Old)
{% extends "base.twig" %}
{% block content %}…{% endblock %}
{% extends ba...
VARIABLE TEMPLATE EXTENSION!! 🤖
base.twig
<main class=“container”>

{% block content %}

Default content.

{% endblock %}
...
Links:

emilyhorsman.com/t



Questions/Comments:

@emilymhorsman
THANKS FOR BEING AWESOME! 🙏
Theme Development with Timber and Twig
Theme Development with Timber and Twig
Upcoming SlideShare
Loading in …5
×

Theme Development with Timber and Twig

1,162 views

Published on

Talk on Timber and Twig for the Hamilton WordPress group on November 19

Published in: Software
  • Be the first to comment

Theme Development with Timber and Twig

  1. 1. THEME DEVELOPMENT WITH TIMBER AND TWIG
  2. 2. WHO AM I? EMILY HORSMAN ▸ Tweet at me! @emilymhorsman 😍 ▸ Email me! me@emilyhorsman.com ▸ Service & Support at Wise & Hammer ▸ Opinions and code are my own*
  3. 3. ‣ All links from this talk are available at
 emilyhorsman.com/t
  4. 4. !? WHY WOULD I USE A TEMPLATE ENGINE? ▸ Easier to write D.R.Y. and re-usable views ▸ Less to think about, write your logic and data (PHP) away from your presentation (HTML)
  5. 5. !? WHAT’S TIMBER? ▸ Cleaner, faster theme development ▸ Can be used on top of existing themes ▸ Easy use of the Twig template engine with WordPress ▸ Convention for common WordPress concepts
  6. 6. $thumb_id = get_post_thumbnail_id($post->ID); $thumb_url = wp_get_attachment_url($thumb_id); <img src="<?php echo $thumb_url; ?>” alt="<?php echo $post->post_title; ?>"> <img src="{{ post.thumbnail.src }}" alt="{{ post.title }}"> The traditional method… With Timber and Twig…
  7. 7. <img src="{{ post.thumbnail.src }}" alt="{{ post.title }}"> Twig Timber
  8. 8. {% for post in posts %} <div class="post{% if post.has_term(3, 'category') %}-cat-three{% endif %}"> <h2> <a href="{{ post.link }}" rel="bookmark" title="Permanent Link to {{ post.title }}">{{ post.title }}</a> </h2> <small>{{ post.date("F jS, Y") }} by {{ post.author.name }}</small> <div class="entry">{{ post.content }}</div> <p class="postmetadata"> {{ _e('Posted in') ~ post.categories|join(', ') }} </p> </div> {% endfor %}
  9. 9. FINThanks for your time I assume you’ll now write WP differently forever
  10. 10. LET’S BUILD A THEME WITH TIMBER
  11. 11. HOW TIMBER WORKS — CONTEXT Get Data! posts taxonomies menu items widgets etc… Array of data
 “Context” Twig
 Template
  12. 12. HOW TIMBER WORKS — COMMON CONTEXT functions.php class StarterSite extends TimberSite { function __construct() { // ... add_filter(‘timber_context’, array( $this, 'add_to_context' ) ); // ... } function add_to_context( $context ) { $context['site'] = $this; // ... return $context; }
  13. 13. HOW TIMBER WORKS <?php /** * The template for displaying all pages. * * … */ $context = Timber::get_context(); $context['post'] = new TimberPost(); Timber::render(’page.twig’, $context); page.php
  14. 14. BLOG POST
  15. 15. BLOG POST $context = Timber::get_context(); $post = Timber::query_post(); $context['post'] = $post; Timber::render( array( 'single-' . $post->post_type . '.twig', 
 ‘single.twig' ), $context ); single.php
  16. 16. BLOG POST <h1><a href="{{ post.link }}”>{{ post.title }}</a></h1> <p> By <a href=“{{post.author.path}}">{{post.author.name}}</a> {{ post.post_date|date}} </p> {% set terms = post.categories %} {% include “components/terms.twig" with { "label": "Categories" } %} {% set terms = post.tags %} {% include “components/terms.twig” with { "label": "Tags" } %} <article>{{ post.content }}</article> single.twig
  17. 17. BLOG POST <ul class="list-inline"> <li>{{ label }}:</li> {% for term in terms %} <li> <a href="{{ term.link }}”> <span class=“label">{{ term.name }}</span> </a> </li> {% endfor %} </ul> components/terms.twig with { "label": "Tags" }
  18. 18. !!! post.content
 post.thumbnail
 post.author
 post.title
 post.categories
 post.tags
 post.comments
 post.date
 post.link
 post.next
 post.prev
 post.children TimberPost
  19. 19. ARCHIVE/MULTIPLE POSTS PAGE — CATEGORY PAGE EXAMPLE
  20. 20. ARCHIVE/MULTIPLE POSTS PAGE $context = Timber::get_context(); if ( is_category() ) { $cat_id = get_query_var('cat'); $context[‘title'] = single_cat_title(…); $context['category'] = new TimberTerm($cat_id); } $context['posts'] = Timber::get_posts(); $context['pagination'] = Timber::get_pagination(); Timber::render( array( ‘archive.twig', ‘index.twig' ), $context ); archive.php
  21. 21. ARCHIVE/MULTIPLE POSTS PAGE {% if category %} <div class="jumbotron"> <div class="container"> <h2>{{ category.title }}</h2> <p>{{ category.description }}</p> </div> </div> {% endif %} {% for post in posts %} {% include ‘tease-‘ ~ post.post_type ~ '.twig' %} {% endfor %} {% include "components/pagination.twig" %} index.twig
  22. 22. ARCHIVE/MULTIPLE POSTS PAGE <div class="row"> <div class="col-md-4"> {% if post.thumbnail.src %} <img src="{{post.thumbnail.src}}" /> {% endif %} </div> <div class="col-md-8"> <h3>{{post.title}}</h3> <p>{{post.get_preview(30, true, false)}}</p> <a href="{{post.link}}” …>Read More</a> </div> </div> tease-post.twig
  23. 23. ARCHIVE/MULTIPLE POSTS PAGE — PAGINATION {% if pagination.prev %} <li><a href="{{ pagination.prev.link }}">Prev</a></li> {% endif %} {% for page in pagination.pages %} <li {% if page.current %}class="active"{% endif %}> <a href="{{ page.link }}">{{ page.title }}</a> </li> {% endfor %} {% if pagination.next %} <li><a href="{{ pagination.next.link }}">Next</a></li> {% endif %} components/pagination.twig
  24. 24. MENUS
  25. 25. MENUS add_action( 'init', array( $this, 'register_menus' ) ); function register_menus() { register_nav_menu(‘main-nav-bar', 'Main Navbar'); } function add_to_context( $context ) { // … $context[‘site’] = $this; $context[‘nav_bar'] = new TimberMenu('main-nav-bar'); return $context; } functions.php
  26. 26. MENUS <nav> <a href="{{ site.link }}">{{ site.title }}</a> <ul> {% for item in nav_bar.get_items %} <li class="… {% if item.current %}active{% endif %}"> <a href="{{ item.link }}"> {{ item.title }} </a> </li> {% endfor %} </ul> {% if nav_widgets %} {{ nav_widgets }} {% endif %} </nav> components/nav.twig
  27. 27. WIDGET LOCATIONS
  28. 28. WIDGET LOCATIONS add_action('widgets_init', …); register_sidebar(array( 'name' => 'Site Sidebar', 'id' => ‘site_sidebar', // … )); register_sidebar(array( 'name' => 'Site Nav', 'id' => ‘site_nav_widgets', // … )); function add_to_context( $context ) { $context['sidebar'] = Timber::get_widgets('site_sidebar');
 $context['nav_widgets'] =
 Timber::get_widgets('site_nav_widgets'); // … } functions.php
  29. 29. WIDGET LOCATIONS <aside class="sidebar"> {{ sidebar }} </aside> base.twig <nav>
 <!-- … -->
 {{ nav_widgets }}
 </nav> components/nav.twig
  30. 30. WE CAN RENDER A WIDGET lib/widgets/navbar_search.php class NavbarSearchWidget extends WP_Widget { public function __construct() { … } 
 // Render the widget! public function widget($args, $instance) { $context = array( 'args' => $args, 'instance' => $instance, ); Timber::render(
 'widgets/navbar_search.twig',
 $context
 ); } }
  31. 31. WE CAN RENDER A WIDGET widgets/navbar_search.twig {{ args.before_widget }}
 <form action="/" class=“form-inline …"> <label for="search">
 Search in {{ site.link }}
 </label>
 <input … value="{{ fn('the_search_query') }}">
 <button class="btn …” type="submit">
 Search
 </button> </form>
 {{ args.after_widget }}
  32. 32. FILTERS AND FUNCTIONS {{ post.tags|join(‘, ‘) }} {% for post in posts %}
 {{ cycle([‘odd’, ‘even’], loop.index0) }}
 {% endfor %}
  33. 33. CUSTOM FUNCTIONS add_filter( 'get_twig', array( $this, 'add_to_twig' ) ); function add_to_twig( $twig ) { $twig->addFunction( new Twig_SimpleFunction( ‘columns', /* Name exposed to twig */ array( $this, ‘columns_func') )); // … } function columns_func($n) { return 12 / $n; } functions.php <div class="col-md-{{ columns(posts|length) }}">
  34. 34. CUSTOM FILTERS add_filter( 'get_twig', array( $this, 'add_to_twig' ) ); function add_to_twig( $twig ) {
 $twig->addFilter('superscript_date',
 new Twig_Filter_Function(‘superscript_date_func')); // … } function superscript_date_func($text) { return preg_replace(‘/(d+)((th)|(st)|(rd)|(nd))/', ‘${1}<sup>${2}</sup>', $text); } functions.php {{ date|superscript_date }}
  35. 35. SHORTCODES! add_shortcode( ‘tease_post',
 array( $this, 'tease_post_func' ) ); function tease_post_func($atts) { $a = shortcode_atts(array( 'id' => false ), $atts); $context = array( 'post' => new TimberPost($a['id']), ); return Timber::compile('tease-post.twig', $context); } functions.php [tease_post id=78]
  36. 36. !!! TEMPLATE INHERITANCE AND BLOCKS page.php page.twig base.twig wrapper.twig Timber::render(‘page.twig’,…) {% extends “base.twig” %} {% extends “wrapper.twig” %}
  37. 37. TEMPLATE INHERITANCE AND BLOCKS {% extends "base.twig" %} {% block content %} <article> {{ post.content }} </article> {% endblock %} page.twig
  38. 38. TEMPLATE INHERITANCE AND BLOCKS <html …> <head> … {{ fn('wp_head') }} </head> <body> {% block body %} Default body! {% endblock %} {{ fn('wp_footer') }} </body> </html> {% extends "wrapper.twig" %} {% block body %} <main> <section> {% block content %}
 Some dummy forgot the content! {% endblock %} </section> <aside> {% block sidebar %} {{ sidebar }} {% endblock %} </aside> </main> {% endblock %} base.twig wrapper.twig
  39. 39. !! 🤖 NEAT TRICK TIME: PAGE TEMPLATES
  40. 40. !! 🤖 VARIABLE TEMPLATE EXTENSION page.php page.twig base[-fluid].twig wrapper.twig Timber::render(‘page.twig’,…) {% extends base_template %} {% extends “wrapper.twig” %}
  41. 41. VARIABLE TEMPLATE EXTENSION!! 🤖 <?php
 /** * Template Name: Fluid Page */ $context = Timber::get_context(); $context['base_template'] = 'base-fluid.twig'; $context['post'] = new TimberPost(); Timber::render('page.twig', $context); page-fluid.php
  42. 42. VARIABLE TEMPLATE EXTENSION!! 🤖 page.twig (Old) {% extends "base.twig" %} {% block content %}…{% endblock %} {% extends base_template|default("base.twig") %}
 {% block content %}…{% endblock %} page.twig (New)
  43. 43. VARIABLE TEMPLATE EXTENSION!! 🤖 base.twig <main class=“container”>
 {% block content %}
 Default content.
 {% endblock %} </main> <main class=“container-fluid”>
 {% block content %}
 Default content.
 {% endblock %} </main> base-fluid.twig
  44. 44. Links:
 emilyhorsman.com/t
 
 Questions/Comments:
 @emilymhorsman THANKS FOR BEING AWESOME! 🙏

×