012. WordPress REST API y Fetch

En el capítulo de hoy vamos a trabajar con la API REST que ofrece WordPress. Si tenemos acceso a un sitio hecho con WordPress, podemos crear un frontend absolutamente con las características que necesitemos, muy al estilo de como hacen los generadores de sitios estáticos como Gatsby, Jekyll, Hugo, VuePress, Pelican…  Para ello lo primero que vamos a hacer es ir a la página oficial de WordPress, https://wordpress.org, nos vamos a la documentación, hay un concepto denominado the loop (The_Loop), que de hecho es la función que tiene WordPress para traer la información de cada uno de las páginas o entradas de nuestro sitio. Es muy importante comprender qué es lo que ofrece esta función. El enlace directo es https://codex.wordpress.org/The_Loop. Todo lo que podamos modificar, insertar, eliminar… en definitiva, todo el CRUD que podemos hacer desde la interfaz de WordPress, lo podemos hacer desde la API REST.

Para consumir la información no son necesarias credenciales, va a depender de la seguridad que implementemos a nuestro WordPress, y si permitimos peticiones de tipo REST.

En este ejercicio consumiremos tan sólo información de tipo REST, no vamos a hacer peticiones para insertar una nueva entrada o un nuevo usuario, pero sí que podríamos publicar entradas, modificar categorías, hacer login con nuestro usuario… es decir, acceder a los demás verbos adicionales del método GET.

La función The_Loop

Esta función tiene la característica de traer información de cada una de las entradas o páginas de nuestro blog. Información que nos puede dar son el título de la publicación (the_title()), la fecha en la cual fue creada (the_time()), la lista de categorías (the_cathegory()), la URL del artículo que estamos leyendo (the_permalink()), un pequeño contenido (the_excerp()), el contenido (the_content()) que es lo que escribimos en el editor de texto de WordPress, o en el editor de bloques Gutemberg si estamos trabajando a partir de la versión 5, la lista de etiquetas, el nombre del autor, incluso el avatar (imagen) del autor.

Nota: Son varias las páginas escritas en WordPress, como son https://css-tricks.com/, la web de la Casa Blanca en EEUU, la web de jQuery, la web de WordPress.org

Podemos acceder a los endpoints desde el enlace https://developer.wordpress.org/rest-api/reference/. De todo lo que podemos hacer con WordPress, en este capítulo aprenderemos a consumir los posts, para lo cual comenzamos a trabajar creando el archivo wp-api-rest.html, el cual tendrá la siguiente sintaxis.

Nota: cuando instalamos un sitio WordPress, por defecto tenemos el acceso libre para consumir el contenido. Existen varias rutas (endpoints) hacia la API WordPress que nos permiten acceder a posts, pages, users, categories. tags… del WordPress

Vamos a trabajar una técnica para guardar los endpoints que vayamos a consumir. La sintaxis que tenemos en nuestro archivo wp-api-rest.html es la siguiente

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Wordpress REST API</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Raleway:wght@100;700&display=swap"
      rel="stylesheet"
    />
    <style>
      html {
        box-sizing: border-box;
        font-family: "Raleway", sans-serif;
        font-size: 16px;
      }
      *,
      *::after,
      *::before {
        box-sizing: inherit;
      }
      a {
        color: #01579b;
        transition: all 0.3s ease-out;
      }
      a:hover {
        opacity: 0.75;
      }
      img {
        max-width: 100%;
        height: auto;
      }
      h1 {
        text-align: center;
      }
      hr {
        border: thin solid #01579b;
        margin: 3rem auto;
        width: 80%;
      }
      .site {
        margin: 1rem auto;
        padding: 1rem;
        max-width: 50%;
        text-align: center;
      }
      .post {
        margin: 1rem auto;
        padding: 1rem;
        border-radius: 0.5rem;
        max-width: 80%;
        text-align: center;
        background-color: #eceff1;
      }
      .post-author img {
        border-radius: 50%;
      }
      .post-date {
        margin: 1rem auto;
        display: block;
      }
      .post-excerpt,
      .post-categories,
      .post-tags,
      .post-content {
        text-align: left;
      }
      .post-content summary {
        font-size: 1.5rem;
        font-weight: bold;
      }
      .post-content > article {
        padding: 1rem;
        background-color: #e3f2fd;
      }
      .post-content img {
        display: block;
        margin: auto;
      }
      .loader {
        display: none;
        margin: 2rem auto;
      }
    </style>
  </head>
  <body>
    <h1>Wordpress REST API</h1>
    <article id="site" class="site"></article>
    <section id="posts" class="posts"></section>
    <img src="loader.svg" alt="Cargando" class="loader" />
    <template id="post-template">
      <article class="post">
        <img class="post-image" />
        <aside>
          <h2 class="post-title"></h2>
          <figure class="post-author"></figure>
          <small class="post-date"></small>
          <a target="_blank" class="post-link">Ver publicación original</a>
          <p class="post-excerpt"></p>
          <div class="post-categories"></div>
          <div class="post-tags"></div>
          <details class="post-content">
            <summary>Ver contenido de la publicación</summary>
            <article></article>
          </details>
        </aside>
      </article>
      <hr />
    </template>
    <script>
      // Declaramos las variables que vamos a utilizar
      const d = document,
        w = window,
        $site = d.getElementById("site"),
        $posts = d.getElementById("posts"),
        $loader = d.querySelector(".loader"),
        $template = d.getElementById("post-template").content,
        $fragment = d.createDocumentFragment();

      // Creación de constantes ya que no se van a modificar los datos
      const DOMAIN = "https://sutilweb.eu",
        SITE = `${DOMAIN}/wp-json`,
        API_WP = `${SITE}/wp/v2`,
        POSTS = `${API_WP}/posts?_embed`,
        PAGES = `${API_WP}/pages`,
        CATEGORIES = `${API_WP}/categories`;

      let page = 1,
        perPage = 5;

      function getSiteData() {
        fetch(SITE)
          .then((res) => (res.ok ? res.json() : Promise.reject(res)))
          .then((json) => {
            console.log(json);
            $site.innerHTML = `
            <h3>Sitio Web</h3>
            <h2>
                <a href="${json.url}" target="_blank">${json.name}</a>
            </h2>
            <p>${json.description}</p>
            <p>${json.timezone_string}</p>
            `;
          })
          .catch((err) => {
            console.log(err);
            let message = err.statusText || "Ocurrió un error";
            $site.innerHTML = `<p>Error ${err.status}: ${message}</p>`;
          });
      }

      function getPost() {
        $loader.style.display = "block";
        fetch(`${POSTS}&page=${page}&per_page=${perPage}`)
          .then((res) => (res.ok ? res.json() : Promise.reject(res)))
          .then((json) => {
            console.log(json);
            json.forEach((el) => {
              // Variables para recoger las categorías y las etiquetas
              let categories = "",
                tags = "";
              el._embedded["wp:term"][0].forEach(
                (el) => (categories += `<li>${el.name}</li>`)
              );
              el._embedded["wp:term"][1].forEach(
                (el) => (tags += `<li>${el.name}</li>`)
              );
              $template.querySelector(".post-image").src = el._embedded[
                "wp:featuredmedia"
              ]
                ? el._embedded["wp:featuredmedia"][0].source_url
                : "";
              $template.querySelector(".post-image").alt = el.title.rendered;
              $template.querySelector(".post-title").innerHTML =
                el.title.rendered;
              $template.querySelector(".post-author").innerHTML = `
              <img src="${el._embedded.author[0].avatar_urls["48"]}" alt="${el._embedded.author[0].name}">
              <figcaption>${el._embedded.author[0].name}</figcaption>
              `;
              $template.querySelector(".post-date").innerHTML = new Date(
                el.date
              ).toLocaleDateString();
              $template.querySelector(".post-link").href = el.link;
              $template.querySelector(".post-excerpt").innerHTML =
                el.excerpt.rendered;
              $template.querySelector(".post-categories").innerHTML = `
                <p>Categorías: </p>
                <ul>${categories}</ul>
                `;
              $template.querySelector(".post-tags").innerHTML = `
                <p>Etiquetas: </p>
                <ul>${tags}</ul>
                `;
              $template.querySelector(".post-content > article").innerHTML =
                el.content.rendered;
              let $clone = d.importNode($template, true);
              $fragment.appendChild($clone);
            });
            $posts.appendChild($fragment);
            $loader.style.display = "none";
          })
          .catch((err) => {
            console.log(err);
            let message = err.statusText || "Ocurrió un error";
            $posts.innerHTML = `<p>Error ${err.status}: ${message}</p>`;
            $loader.style.display = "none";
          });
      }

      d.addEventListener("DOMContentLoaded", (e) => {
        getSiteData();
        getPost();
      });

      w.addEventListener("scroll", (e) => {
        const { scrollTop, clientHeight, scrollHeight } = d.documentElement;
        console.log(scrollTop, clientHeight, scrollHeight);
        if (scrollTop + clientHeight >= scrollHeight) {
          console.log("Cargar mas posts");
          page++;
          getPost();
        }
      });
    </script>
  </body>
</html>
Scroll al inicio