* good bye loop, my old friend
Searching for...
"Because WordPress is awesome, but the loop isn't."
- http://upstatement.com/timber/
"The most fun I have writing html" - Me
Install Timber https://wordpress.org/plugins/timber-library/
Maintained by Jared Novack, under active development. Active on github and open to pull requests.
Specific docs for Timber - https://github.com/jarednova/timber/wiki
Twig docs - http://twig.sensiolabs.org/documentation
<?php get_header(); ?>
<main class="site-main" role="main">
<?php if ( have_posts() ) : ?>
<?php if ( is_home() && ! is_front_page() ) : ?>
<header>
<h1 class="page-title screen-reader-text"><?php single_post_title(); ?></h1>
</header>
<?php endif; ?>
<?php
// Start the loop.
while ( have_posts() ) : the_post();
get_template_part( 'content', get_post_format() );
endwhile;
// If no content, include the "No posts found" template.
else :
get_template_part( 'content', 'none' );
endif;
?>
</main><!-- .site-main -->
<?php get_footer(); ?>
index.php
<?php
$context = Timber::get_context();
$context['posts'] = Timber::get_posts();
$templates = array('index.twig');
Timber::render($templates, $context);
index.php
{% extends "base.twig" %}
{% block content %}
{% for post in posts %}
{% include ['tease-'~post.post_type~'.twig', 'tease.twig'] %}
{% endfor %}
{% endblock %}
index view file extends the base view file
<!doctype html>
<html>
<head></head>
<body>
{% block header %}
<a href="{{ site.link }}" class="site-logo" aria-label="homepage" title="homepage">Twig!</a>
{% endblock %}
<main role="main" class="page-content">
{% block content %}
{{ post.content }}
{% endblock %}
</main>
{% block footer %}
<footer class="page-foot">© copyright{{ "now"|date('Y') }}</footer>
{% endblock %}
</body>
</html>
base.twig
Build a base "skeleton" template that contains all the common elements of your site and defines blocks that child templates can override.
{% extends "base.twig" %}
{% block content %}
{% for post in posts %}
{% include ['tease-'~post.post_type~'.twig', 'tease.twig'] %}
{% endfor %}
{% endblock %}
index view file extends the base view file
<div class="tease {% if image %}has-img{% endif %}">
<h2 class="tease__title"><a href="{{ link }}">{{ title }}</a></h2>
{% if image %}
<a href="{{ link }}"><img class="tease__img" src="{{ image.src }}" alt="{{ image.alt }}" /></a>
{% endif %}
{{ body_text }}
<a href="{{ link }}" class="btn btn--tertiary">Read more</a>
</div>
{% for item in events %}
{% include "partials/tease.twig" with {
'image': item.image,
'title': item.title,
'body_text': item.get_preview(80),
'link': item.link
} only %}
{% endfor %}
partials/tease.twig
{% for item in news %}
{% include "partials/tease.twig" with {
'title': item.title,
'body_text': item.get_preview(40),
'link': item.link
} only %}
{% endfor %}
{{ function('wp_footer') }}
Including PHP or WordPress functions
Timber::get_posts($query = false, $class = 'TimberPost');
Timber:get_posts (returns TimberPost)
much, much more ...
Caching, TimberMenu, TimberPost, TimberSite, TimberTerm, TimberUser, Timber Filters ...
TimberPost: Used to represent posts retrieved from WordPress, making them available to Twig templates.
<article>
<h1 class="headline">{{post.post_title}}</h1>
<div class="body">
{{post.content}}
</div>
</article>
<img src="{{post.thumbnail.src|resize(300, 200)}}" />
All of these filters are written specifically to interact with WordPress's image API. (So don't worry, no weird TimThumb stuff going on -- this is all using WP's internal image sizing stuff).
Integration with Jetpacks Photon
Image resizing on the fly, no longer want to use the regenerate thumbnails plugin.
"Forms are hard" - Me
"Dealing with HTML forms is one of the most common - and challenging - tasks for a web developer." - Symfony2 site
<?php
$formFactory = WiredForms\Form_Factory::Instance()->formFactory;
$form_builder = $formFactory->createBuilder()
->add('message', 'textarea', array(
'constraints' => new NotBlank(array(
'message' => 'This value should not be blank.'
))
))
->add('email', 'text', array(
'constraints' => new NotBlank(),
'constraints' => new Email(array(
'message' => 'The email {{ value }} is not a valid email.',
))
));
$form = $form_builder->getForm();
$request = Request::createFromGlobals();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// perform some action, such as saving the task to the database
}
$form = $form_builder->getForm();
Timber::render(array('contact.twig'), array('form' => $form->createView()));
To display the form in Twig you have to use Twig Bridge, it provides.
{# Widgets #}
{% block form_widget -%}
{% if compound %}
{{- block('form_widget_compound') -}}
{% else %}
{{- block('form_widget_simple') -}}
{% endif %}
{%- endblock form_widget %}
{% block form_widget_simple -%}
{% set type = type|default('text') -%}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{%- endblock form_widget_simple %}
{% block form_widget_compound -%}
<div {{ block('widget_container_attributes') }}>
{%- if form.parent is empty -%}
{{ form_errors(form) }}
{%- endif -%}
{{- block('form_rows') -}}
{{- form_rest(form) -}}
</div>
{%- endblock form_widget_compound %}
{% block collection_widget -%}
{% if prototype is defined %}
{%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%}
{% endif %}
{{- block('form_widget') -}}
{%- endblock collection_widget %}
{% block textarea_widget -%}
<textarea {{ block('widget_attributes') }}>{{ value }}</textarea>
{%- endblock textarea_widget %}
{% block choice_widget -%}
{% if expanded %}
{{- block('choice_widget_expanded') -}}
{% else %}
{{- block('choice_widget_collapsed') -}}
{% endif %}
{%- endblock choice_widget %}
{% block choice_widget_expanded -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child) -}}
{{- form_label(child) -}}
{% endfor -%}
</div>
{% endblock choice_widget_expanded %}
{% block choice_widget_collapsed -%}
{% if required and empty_value is none and not empty_value_in_choices and not multiple -%}
{% set required = false %}
{%- endif -%}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{% if empty_value is not none -%}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ empty_value|trans({}, translation_domain) }}</option>
{%- endif %}
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
{{- block('choice_widget_options') -}}
{% if choices|length > 0 and separator is not none -%}
<option disabled="disabled">{{ separator }}</option>
{%- endif %}
{%- endif -%}
{% set options = choices -%}
{{- block('choice_widget_options') -}}
</select>
{%- endblock choice_widget_collapsed %}
{% block choice_widget_options -%}
{% for group_label, choice in options %}
{%- if choice is iterable -%}
<optgroup label="{{ group_label|trans({}, translation_domain) }}">
{% set options = choice %}
{{- block('choice_widget_options') -}}
</optgroup>
{%- else -%}
<option value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</option>
{%- endif -%}
{% endfor %}
{%- endblock choice_widget_options %}
{% block checkbox_widget -%}
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{%- endblock checkbox_widget %}
{% block radio_widget -%}
<input type="radio" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{%- endblock radio_widget %}
{% block datetime_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else %}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form.date) -}}
{{- form_errors(form.time) -}}
{{- form_widget(form.date) -}}
{{- form_widget(form.time) -}}
</div>
{% endif %}
{%- endblock datetime_widget %}
{% block date_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
<div {{ block('widget_container_attributes') }}>
{{- date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw -}}
</div>
{%- endif %}
{%- endblock date_widget %}
{% block time_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} %}
<div {{ block('widget_container_attributes') }}>
{{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %}
</div>
{%- endif %}
{%- endblock time_widget %}
{% block number_widget -%}
{# type="number" doesn't work with floats #}
{% set type = type|default('text') %}
{{- block('form_widget_simple') -}}
{%- endblock number_widget %}
{% block integer_widget -%}
{% set type = type|default('number') %}
{{- block('form_widget_simple') -}}
{%- endblock integer_widget %}
{% block money_widget -%}
{{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }}
{%- endblock money_widget %}
{% block url_widget -%}
{% set type = type|default('url') %}
{{- block('form_widget_simple') -}}
{%- endblock url_widget %}
{% block search_widget -%}
{% set type = type|default('search') %}
{{- block('form_widget_simple') -}}
{%- endblock search_widget %}
{% block percent_widget -%}
{% set type = type|default('text') %}
{{- block('form_widget_simple') -}} %
{%- endblock percent_widget %}
{% block password_widget -%}
{% set type = type|default('password') %}
{{ block('form_widget_simple') }}
{%- endblock password_widget %}
{% block hidden_widget -%}
{% set type = type|default('hidden') %}
{{- block('form_widget_simple') -}}
{%- endblock hidden_widget -%}
{% block email_widget -%}
{% set type = type|default('email') %}
{{- block('form_widget_simple') -}}
{%- endblock email_widget %}
{% block button_widget -%}
{% if label is empty -%}
{% set label = name|humanize %}
{%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain) }}</button>
{%- endblock button_widget %}
{% block submit_widget -%}
{% set type = type|default('submit') %}
{{- block('button_widget') -}}
{%- endblock submit_widget %}
{% block reset_widget -%}
{% set type = type|default('reset') %}
{{- block('button_widget') -}}
{%- endblock reset_widget %}
{# Labels #}
{% block form_label -%}
{% if label is not sameas(false) -%}
{% if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %}
{%- endif %}
{% if required -%}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{%- endif %}
{% if label is empty -%}
{% set label = name|humanize %}
{%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label>
{%- endif %}
{%- endblock form_label %}
{% block button_label -%}{%- endblock %}
{# Rows #}
{% block repeated_row -%}
{#
No need to render the errors here, as all errors are mapped
to the first child (see RepeatedTypeValidatorExtension).
#}
{{- block('form_rows') -}}
{%- endblock repeated_row %}
{% block form_row -%}
<div>
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form) -}}
</div>
{%- endblock form_row %}
{% block button_row -%}
<div>
{{- form_widget(form) -}}
</div>
{%- endblock button_row %}
{% block hidden_row -%}
{{ form_widget(form) }}
{%- endblock hidden_row %}
{# Misc #}
{% block form -%}
{{ form_start(form) }}
{{- form_widget(form) -}}
{{ form_end(form) }}
{%- endblock form %}
{% block form_start -%}
{% set method = method|upper %}
{%- if method in ["GET", "POST"] -%}
{% set form_method = method %}
{%- else -%}
{% set form_method = "POST" %}
{%- endif -%}
<form name="{{ form.vars.name }}" method="{{ form_method|lower }}" action="{{ action }}"{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}{% if multipart %} enctype="multipart/form-data"{% endif %}>
{%- if form_method != method -%}
<input type="hidden" name="_method" value="{{ method }}" />
{%- endif -%}
{%- endblock form_start %}
{% block form_end -%}
{% if not render_rest is defined or render_rest %}
{{- form_rest(form) -}}
{% endif -%}
</form>
{%- endblock form_end %}
{% block form_enctype -%}
{% if multipart %}enctype="multipart/form-data"{% endif %}
{%- endblock form_enctype %}
{% block form_errors -%}
{% if errors|length > 0 -%}
<ul>
{%- for error in errors -%}
<li>{{ error.message }}</li>
{%- endfor -%}
</ul>
{%- endif %}
{%- endblock form_errors %}
{% block form_rest -%}
{% for child in form -%}
{% if not child.rendered %}
{{- form_row(child) -}}
{% endif %}
{%- endfor %}
{% endblock form_rest %}
{# Support #}
{% block form_rows -%}
{% for child in form %}
{{- form_row(child) -}}
{% endfor %}
{%- endblock form_rows %}
{% block widget_attributes -%}
id="{{ id }}" name="{{ full_name }}"
{%- if read_only %} readonly="readonly"{% endif -%}
{%- if disabled %} disabled="disabled"{% endif -%}
{%- if required %} required="required"{% endif -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is sameas(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not sameas(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{%- endblock widget_attributes %}
{% block widget_container_attributes -%}
{%- if id is not empty %}id="{{ id }}"{% endif -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is sameas(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not sameas(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{%- endblock widget_container_attributes %}
{% block button_attributes -%}
id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is sameas(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not sameas(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{%- endblock button_attributes %}
base form template provided by Twig Bridge
<form action="{{ post.link }}" class="contact-form" method="post" {{ form_enctype(form) }}>
{{ form_row(form.message, {
'label_attr': {'class': 'form__label'},
'attr': {'placeholder': 'Type your message here'}
}) }}
{{ form_row(form.name, {
'label_attr': {'class': 'form__label'},
'attr': {'placeholder': 'name'}
}) }}
{{ form_row(form.email, {
'label_attr': {'class': 'form__label'},
'attr': {'placeholder': 'email'}
}) }}
{{ form_widget(form) }}
</form>
"require": {
"symfony/form": "2.5.3",
"symfony/validator": "~2.2",
"symfony/http-foundation": "~2.2",
"symfony/security-csrf": "2.5.3",
"symfony/twig-bridge": "2.5.3",
"symfony/translation": "2.3.*",
"symfony/config": "2.3.*",
"symfony/yaml": "2.5.3"
}
Configuration guide - http://symfony.com/doc/current/components/form/introduction.html
Not the most straight forward thing to setup.
I'm planning on making a WP plugin which takes care of the implementation for you.
Following along with the configuration guide you will have to.
My implementation can be found here:
https://github.com/codekipple/wp-day-one