Cómo crear migas de pan dinámicas a partir de una ruta de URL

Cómo tomar el valor de url.path en un componente Joystick y convertirlo en una interfaz de usuario dinámica.

Primeros pasos

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje desconectado de cd en su nuevo proyecto y ejecute joystick start :

Terminal

cd app && joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

Agregar rutas anidadas

Para demostrar una interfaz de usuario de migas de pan, vamos a necesitar un conjunto de rutas anidadas con las que podamos trabajar. Para simplificar las cosas, comencemos abriendo el index.server.js archivo en la raíz del proyecto que acabamos de crear y agregue algunas rutas:

Terminal

import node from "@joystick.js/node";
import api from "./api";

node.app({
  api,
  routes: {
    "/": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/nested": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/nested/path": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/nested/path/to": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/nested/path/to/:thing": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

En la aplicación que acabamos de crear, el index.server.js El archivo es el principal "punto de partida" para el servidor de nuestra aplicación. Dentro llamamos al node.app() función del @joystick.js/node paquete para poner en marcha nuestro servidor, pasándole la API que queremos que cargue y las rutas que queremos que estén disponibles en nuestra app.

En lo que queremos centrarnos aquí es en el routes y, en concreto, todas las rutas que hemos añadido a partir de /nested . Aquí, estamos creando un patrón de URL pseudo anidado que podemos usar para probar nuestro código de generación de migas de pan.

Por cada /nested ruta, hacemos exactamente lo mismo:renderizar el index componente de página (acabamos de copiar y pegar el contenido del / función de devolución de llamada de la ruta para cada /nested ruta). La diferencia entre cada uno es el camino en sí. Tenga en cuenta que para cada ruta que hemos agregado profundizamos un nivel adicional:

  • /nested
  • /nested/path
  • /nested/path/to
  • /nested/path/to/:thing

El objetivo final es que con esta estructura, ahora tenemos un conjunto anidado de rutas que podemos representar fácilmente como migas de pan.

A continuación, queremos modificar el /ui/pages/index/index.js archivo que estamos renderizando aquí para construir nuestra interfaz de usuario de migas de pan.

Adición de un generador dinámico de migas de pan

Cuando creamos nuestra aplicación con joystick create app anteriormente, también se nos proporcionó un componente de página de ejemplo en /ui/pages/index/index.js . Ahora, abramos eso y reemplacemos los contenidos existentes con un componente de esqueleto que podemos usar para construir nuestra interfaz de usuario de migas de pan.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Index;

Con eso en su lugar, lo primero que queremos hacer es conectar la creación real de nuestras migas de pan y luego concentrarnos en representarlas en la página. Para hacer esto, vamos a confiar en el methods propiedad de un componente Joystick.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  methods: {
    getBreadcrumbs: (component) => {
      // We'll build our breadcrumbs array here...
    },
  },
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Index;

En un componente de Joystick, la propiedad de métodos contiene un objeto de métodos misceláneos (otro nombre para las funciones definidas en un objeto en JavaScript) relacionado con nuestro componente. Lo que queremos hacer ahora es definir una función getBreadcrumbs() que realizará el trabajo pesado para convertir nuestra ruta de URL en una matriz de objetos que describen cada ruta de navegación que queremos representar.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  methods: {
    getBreadcrumbs: (component) => {
      const pathParts = component?.url?.path?.split('/').filter((part) => part?.trim() !== '');
      return pathParts?.map((part, partIndex) => {
        const previousParts = pathParts.slice(0, partIndex);
        return {
          label: part,
          href: previousParts?.length > 0 ? `/${previousParts?.join('/')}/${part}` : `/${part}`,
        };
      }) || [];
    },
  },
  render: () => {
    return `
      <div>
      </div>
    `;
  },
});

export default Index;

Hemos descargado todo el código aquí en aras de la claridad, así que analicemos paso a paso. Primero, nuestro objetivo es poder llamar a esta función getBreadcrumbs y hacer que nos devuelva una serie de objetos donde cada objeto describe una de nuestras migas de pan.

Para llegar allí, necesitamos obtener la ruta actual que está viendo nuestro usuario. Tenemos dos opciones para esto en nuestra aplicación, ambas igualmente fáciles. Primero, de forma nativa en un navegador web, siempre podemos obtener la ruta actual a través de window.location.pathname valor global (location.pathname para abreviar). Debido a que estamos trabajando dentro de una aplicación Joystick, aquí usaremos el url.path valor (que es idéntico a location.pathname ) disponible en nuestra instancia de componente.

Notará que al definir un método en un componente Joystick, si no se pasan argumentos a esa función cuando la llamamos, Joystick asignará automáticamente el último argumento posible a la instancia del componente. Por ejemplo, si llamamos a methods.getBreadcrumbs('something') , la firma de la función anterior cambiaría a getBreadcrumbs: (someValue, component) => { ... } .

Dentro de nuestra función, desde el component instancia, obtenemos la ruta actual con component.url.path como una cadena. Para llegar a una matriz, primero, debemos dividir nuestro camino en partes. Para hacer eso, necesitamos usar el .split() función disponible en todas las cadenas en JavaScript. Para .split() , podemos pasar un carácter que queremos dividir en . Porque estamos tratando con una ruta como /nested/path/to/123 queremos dividir en el / carácter de barra inclinada. El resultado final es una matriz como esta:

['', 'nested', 'path', 'to', '123']

Esto nos lleva a la mayor parte del camino, pero observe que aquí hay una cadena vacía. Eso es porque cuando hicimos un .split('/') , se contó la primera barra, pero como no hay nada que la preceda, solo obtenemos un valor vacío.

Para manejar esto, observe que la línea completa aquí es:

const pathParts = component?.url?.path?.split('/').filter((part) => part?.trim() !== '');

Lo que dice es "tomar el url.path valor como una cadena, divídalo en una matriz usando el / barra diagonal como separador, luego, filtre cualquier parte en la matriz resultante si recortar todos sus espacios en blanco da como resultado una cadena vacía".

¿El final resulto? Obtenemos una matriz limpia para trabajar como ['nested', 'path', 'to', '123'] en nuestro pathParts variables.

Con esta matriz, tenemos lo que necesitamos para construir nuestras migas de pan. A continuación, necesitamos mapear sobre esta matriz. Para cada iteración, queremos hacer el trabajo necesario para construir nuestro objeto de ruta de navegación. Cada ruta de navegación tendrá dos propiedades:label cuál es el nombre representado que los usuarios verán en la cadena de migas de pan y href cuál es la URL a la que se vinculará la ruta de navegación.

Para el label , nuestro trabajo es fácil:simplemente reutilizaremos el nombre de la ruta part actualmente estamos dando vueltas. href es un poco más complicado. Aquí, debemos asegurarnos de que cada ruta de navegación sucesiva tenga en cuenta lo que vino antes, de modo que cuando hagamos clic en ella, hagamos referencia a la URL correcta.

Para hacerlo, justo dentro de nuestro mapa hemos agregado una nueva variable previousParts que toma nuestro pathParts array y llama al .slice() en él, diciendo "dame todo, desde el primer elemento de la matriz hasta el índice de la parte actual". En otras palabras, esto nos devolverá un nuevo matriz con todo lo que vino antes del actual part .

Abajo en el objeto que estamos devolviendo desde nuestro .map() usamos un operador ternario para establecer el valor de href dependiendo de la longitud del previousParts formación. Si la longitud es 0 , estamos al comienzo de nuestro camino, por lo que no hay partes anteriores para renderizar. Si este es el caso, simplemente devolvemos el href como /${part} .

Si hay hay previousParts , usamos el .join() en esa matriz para convertir la matriz nuevamente en una cadena, concatenando la cadena resultante junto con el nombre del part actual . ¿El final resulto? Para cada iteración, obtenemos algo como esto:

{ label: 'nested', href: '/nested' }
{ label: 'path', href: '/nested/path' }
{ label: 'to', href: '/nested/path/to' }
{ label: '123', href: '/nested/path/to/123' }

Eso es todo para conseguir nuestro pan rallado. Ahora, vamos a mostrarlos en la página.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  methods: {
    getBreadcrumbs: (component) => { ... },
  },
  css: `
    .breadcrumbs {
      display: flex;
    }

    .breadcrumbs li {
      list-style: none;
    }

    .breadcrumbs li:before {
      content: "/";
      display: inline-flex;
      margin-right: 10px;
    }

    .breadcrumbs li:not(:last-child) {
      margin-right: 10px;
    }
  `,
  render: ({ when, each, methods }) => {
    const breadcrumbs = methods.getBreadcrumbs();

    return `
      <div>
        ${when(breadcrumbs?.length > 0, `
          <ul class="breadcrumbs">
            ${each(breadcrumbs, (breadcrumb) => {
              return `
                <li><a href="${breadcrumb?.href}">${breadcrumb?.label}</a></li>
              `;
            })}
          </ul>
        `)}
      </div>
    `;
  },
});

export default Index;

La parte a la que queremos prestar atención está abajo en el render() función. Aquí, hemos intercambiado la representación de nuestro <div></div> vacío con nuestro pan rallado.

A nuestro render() función, anticipamos que Joystick nos pasará un objeto que representa la instancia del componente actual. En lugar de escribir render: (component) => {} aquí, usamos la desestructuración de JavaScript para "arrancar" las variables específicas que queremos de ese objeto. Entonces, en lugar de escribir component.when , component.each , etc., podemos escribir when , each y methods (señalando las mismas propiedades usando taquigrafía).

Usando el methods propiedad de esto, justo dentro de render() , hacemos una llamada al methods.getBreadcrumbs() almacenar el resultado (nuestra matriz de objetos de migas de pan) en una variable breadcrumbs . Con esta matriz, a continuación, usamos el when() función de renderizado en Joystick que nos permite renderizar condicionalmente algo de HTML cuando el primer valor que pasamos a la función es true .

Aquí, queremos devolver una cadena de HTML que represente un <ul></ul> (que representa nuestra lista de migas de pan). Dentro de ese <ul></ul> para renderizar cada ruta de navegación, usamos el each() función de representación para decir dada la matriz pasada como primer argumento, para cada elemento de esa matriz, llame a la función pasada como segundo argumento.

Para esa función, esperamos recibir cada elemento de la matriz que le pasamos a each , o uno de nuestros breadcrumb objetos. Dentro de la función, Joystick espera que devolvamos una cadena de HTML para cada iteración del breadcrumbs formación. Porque estamos dentro de un <ul></ul> etiqueta, para cada ruta de navegación que queremos representar un <li></li> etiqueta con un <a></a> etiqueta dentro de ella. A partir de ahí, solo usamos la interpolación de JavaScript para pasar el valor de nuestro href y label del actual breadcrumb objeto.

¡Eso es todo! Arriba, hemos agregado un css propiedad con un estilo simple para limpiar las cosas. Si abrimos un navegador y cambiamos entre nuestras rutas anidadas, deberíamos ver nuestras migas de pan actualizarse dinámicamente.

Terminando

En este tutorial, aprendimos cómo configurar algunas rutas anidadas en una aplicación Joystick. Luego, aprendimos cómo crear un componente Joystick que tomó la ruta actual y lo convirtió en una matriz de objetos de ruta de navegación que podríamos usar para renderizar en nuestra interfaz de usuario. Finalmente, aprendimos cómo renderizar condicionalmente nuestras migas de pan en nuestra interfaz de usuario, usando el when de Joystick. y each renderizar funciones.