Thymeleaf fragmets

Los fragmentos de Thymeleaf son bloques de código que podemos definir para posteriormente poder reutilizarlos en las distintas páginas de nuestras aplicaciones. Los fragmentos se pueden definir en ficheros separados o agrupar varios dentro de un mismo fichero para tenerlos juntos como en una plantilla.

Como se define un fragment

Los fragments se definen mediante th:fragment="nombre_fragmento", el fragment incluye tanto la etiqueta sobre la que se pone como su contenido, es decir, que al utilizar el siguiente fragment estaríamos incluyendo el footer con su clase y su contenido.


<footer th:fragment="pie" class="bg-dark">
    <span class="info-copyrigth"><a href="https://programadoointentandolo.com">programadoointentandolo.com</a> © 2018</span>
</footer>

En el siguiente ejemplo tenemos una plantilla en la que tenemos definidos varios fragments para los típicos elementos que se suelen compartir entre pantallas.


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head">
    <meta charset="UTF-8"/>
    <title th:text="${titulo}"></title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
    <link rel="stylesheet" th:href="@{/css/estilos.css}">
</head>
<body>
    <header th:fragment="header">
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
          <a class="navbar-brand" href="#">
            <img th:src="@{/img/logo.png}" width="30" height="30" alt="">
          </a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
        
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
              <li class="nav-item active">
                <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
              </li>
              <li class="nav-item">
                <a class="nav-link" th:href="@{/productos/lista}">Productos</a>
              </li>
            </ul>
            <form class="form-inline my-2 my-lg-0">
              <input class="form-control mr-sm-2" type="search" placeholder="Buscar..." aria-label="Search">
              <button class="btn btn-outline-warning my-2 my-sm-0" type="submit">Buscar</button>
            </form>
          </div>
        </nav>
    </header>
    
    
    <div class="container">
    </div>
    
    <footer th:fragment="footer" class="bg-dark">
        <span class="info-copyrigth"><a href="https://programadoointentandolo.com">programadoointentandolo.com</a> © 2018</span>
    </footer>
</body>
</html>

Como se usa los fragmentos

Para utilizar los fragments hay 2 opciones th:insert= y th:replace.

Con th:insert="ruta/archivo::nombre_fragmento" el código del fragmento se incluye dentro del elemento en el que se usa esta etiqueta, por ejemplo con <body th:insert="layouts/base::header"> se incluye el fragmet header dentro del body.

Otra opción es usar th:replace="ruta/archivo::nombre_fragmento" para reemplazar la etiqueta por el fragmento, si usamos <header th:replace="layouts/base::header"></header> el resultado va a ser que esa etiqueta header se va a sustituir por el contenido del fragmento header en lugar de incluirlo dentro como en el caso anterior.

Fragmentos con parámetros

Una característica que puede resultar interesante en ocasiones es que los fragmentos pueden recibir parámetros que podemos utilizar para cambiar la forma en la que se muestra el contenido o lo que queremos que se muestre sin necesidad de crear múltiples fragmentos similares.

Vamos con un ejemplo sencillo, por ejemplo si queremos que nuestra aplicación puede tener varios temas y queremos hacerlo con los fragments porque es lo que acabamos de aprender, porque si no probablemente nunca lo haríamos, pues podemos conseguirlo cambiando un par de líneas en lugar de tener que duplicar los fragments para cada tema si no usamos los fragmets parametrizables.


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<!-- Head -->
<head th:fragment="head(tema)">
    <meta charset="UTF-8"/>
    <title th:text="${titulo}"></title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
    <link rel="stylesheet" th:href="@{/css/estilos.css}">
    <link rel="stylesheet" th:href="@{/css/tema_} + ${tema} + .css">
</head>

<body>

    <!-- Cabecera -->
    <header th:fragment="header(tema)">
        <nav th:class="|navbar navbar-expand-lg navbar-${tema} bg-${tema}|">
          <a class="navbar-brand" href="#">
            <img th:src="@{/img/logo.png}" width="30" height="30" alt="">
          </a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
        
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
              <li class="nav-item active">
                <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
              </li>
              <li class="nav-item">
                <a class="nav-link" th:href="@{/productos/lista}">Productos</a>
              </li>
            </ul>
            <form class="form-inline my-2 my-lg-0">
              <input class="form-control mr-sm-2" type="search" placeholder="Buscar..." aria-label="Search">
              <button class="btn btn-outline-warning my-2 my-sm-0" type="submit">Buscar</button>
            </form>
          </div>
        </nav>
    </header>
    
    <!-- Contenido -->
    <div class="container">
    </div>
    
    <!-- Pie de pagina -->
    <footer th:fragment="footer(tema)" th:class="|bg-${tema}|">
        <span class="info-copyrigth"><a href="https://programadoointentandolo.com">programadoointentandolo.com</a> © 2018</span>
    </footer>
</body>
</html>

Como se ve en el ejemplo el uso es bastante simple, los parámetros se ponen entre paréntesis después del nombre del fragment y ya podemos utilizarlo dentro como si fuese una variable más para cargar un css como en el head, para completar el nombre de una clase como en el header y el footer o para lo que creamos conveniente.

Y la forma de llamar a un fragment con parámetros es lo que puedes esperar, en el lugar en el que tendría que ir el parámetro ponemos el valor que le queremos dar.

<footer th:replace="layouts/base::footer(dark)"></footer>

El resultado que conseguimos con esto es siguiente:

Si quieres probar el código tal y como estaba en el momento correspondiente a este post puedes hacerlo desde este enlace y si prefieres echarle un vistazo a como se encuentra en este momento o al repositorio en general pues puedes hacerlo aquí.