Módulo de Drupal 8 para extender funciones y filtros de Twig

Siempre que tengo que tocar un tema de Drupal 8, instalo un módulo para poder usar mis propias funciones y filtros desde Twig. El código fuente se puede descargar desde el repositorio https://github.com/ErTomy/twig_extension_module
Por ejemplo si queremos sacar una imagen a un tamaño en concreto (en este caso en el tamaño preestablecido imagen_grande):

<img src="{{thumb(item.field_image, 'imagen_grande')}}"/>

O sacar los resultados de una vista de un bloque concreto (donde actualidad sería la vista y block_actualidad el nombre del bloque):

{% for key,item in vista('actualidad', 'block_actualidad') %}
{{item.field_title}}
{% endfor %}

Crearse nuevas funciones o filtros sería tan sencillo como añadirlas en el fichero twig_extension_module/twig_extension_ertomy/src/TwigExtension/ErtomyExtension.php
Si es un filtro añadiríamos un nuevo elemento en el array dentro de la función getFilters() por ejemplo:

public function getFilters() {
    return array(
      'formatea_fecha' => new \Twig_Filter_Function(array('Drupal\twig_extension_ertomy\TwigExtension\ErtomyExtension', 'fechaFilter')),
      'peso' => new \Twig_Filter_Function(array('Drupal\twig_extension_ertomy\TwigExtension\ErtomyExtension', 'pesoFilter')),
      // nuevo filtro a añadir
      'nuevofiltro' => new \Twig_Filter_Function(array('Drupal\twig_extension_ertomy\TwigExtension\ErtomyExtension', 'nuevoFilter')),
    );
}

Y luego crear una función estática por ejemplo en este caso convertiría en mayúsculas el literal que le llegue:

public static function pesoFilter($string){
   return strtoupper($string);
}

Asi en la plantilla podríamos aplicar este filtro de esta forma:

{{ 'La cadena de texto'|nuevofiltro }}

Y para las funciones es exactamente lo mismo pero añadiendo el elemento en el array de la función getFunctions()

Tips&Tricks de Twig para Drupal 8

De vez en cuando me toca montar temas de Drupal 8 desde cero y aquí dejo un chuletero útil:

Lo básico lógicamente es definir variables:

  {% set contador = 0 %}

Los if

{% if item.value == 1 %}
{% elseif item.value == 2 %}
{% else %}
{% endif %}

Y recorrer arrays:

{% for key,item in items %}
{% endfor %}

Si tenemos el depurador activado y queremos saber que variables están disponibles en la plantilla (sin usar el kint que te muestra demasiada información)

{{dump(_context|keys) }}

Obtener la ruta de un campo tipo imagen

{{ file_url(node.field_imagen)}}

Sacar el directorio del tema:

<img src="/{{ directory }}/images/left-arrow.png" alt="">

Sacar la ruta de un enlace a partir del id del nodo:

<a href="{{path('entity.node.canonical', {'node': item.nid})}}">

Pintar la traducción de un literal que tengamos dado de alta las traducciones:

{{ 'Search'|trans }}

Operador ternario

{{ contador >  4? 'display:none': ''}}

Mostrar en formato html un campo que contiene etiquetas:

{{content['#contenido']. body |raw}}

URL de la página actual:

<a href="{{ url('<current>') }}">

Generar un enlace pasándole el título, ruta y estilos:

{{ link(item.title, item.url, { 'class':['foo', 'bar', 'baz']} ) }}

Incluir librerías externas (css y js). Para ello primero debemos tener un bloque de liberias en el fichero libraries.yml del tema o del modulo instalado (en mi caso de mi tema themes/mitema/mitema.libraries.yml):

commands:
  version: VERSION
  js:
    js/commands.js: {}
  dependencies:
    - core/jquery
  css:
    base:
      css/referencias.css: {}

Y luego en la plantilla para incluir todas las librerías

{{ attach_library('mitema/commands) }}

Maxlength en textareas

Es habitual querer limitar el número de caracteres de los textareas, además al diseñador con el que suelo trabajar le ha dado por poner un contador al lado indicando el número de caracteres restantes que le queda al usuario por escribir. El HTML básicamente sería así:

<div>
   <textarea name="pregunta" placeholder="Escribe tu pregunta aquí" maxlength="300"></textarea>
   <span class="contador">300</span>
</div>

Y el javascript que he montado sería el siguiente:

$("textarea[maxlength]").on("propertychange input", function() {
   if (this.value.length > this.maxlength) {
      this.value = this.value.substring(0, this.maxlength);
   }else if($(this).parent().find('span.contador').length){
      $(this).parent().find('span.contador').text($(this).attr('maxlength') - this.value.length);
   }
});