Cómo obtener y renderizar datos en componentes de joystick

Agregar la opción de datos a los componentes de Joystick para obtener datos en el servidor y representarlos en componentes en el servidor y el cliente.

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 . Antes de ejecutar esto, necesitamos instalar una dependencia adicional, node-fetch :

Terminal

cd app && npm i node-fetch

Una vez instalado, desde el mismo app directorio que acaba de cd 'd, puede iniciar la aplicación:

Terminal

joystick start

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

Conexión de un punto final de API mediante captadores

Lo primero que debemos hacer es obtener acceso a algunos datos que representaremos en nuestro componente. Si bien podríamos solo represente algunos datos estáticos (o codificados), sería mejor extraer algunos datos de una API de terceros para que podamos ver el poder y la flexibilidad de esta técnica.

/api/index.js

import fetch from 'node-fetch';
import { URL, URLSearchParams } from 'url';

export default {
  getters: {
    posts: {
      get: (input = {}) => {
        const url = new URL('https://jsonplaceholder.typicode.com/posts');

        if (input?.id) {
          const searchParams = new URLSearchParams(input);
          url.search = searchParams.toString();
        }

        return fetch(url).then((response) => response.json());
      },
    },
  },
  setters: {},
};

En una aplicación Joystick, los "captadores" nos permiten definir puntos finales de API para "obtener" datos. Detrás de escena, los getters se convierten en puntos finales de la API REST HTTP simple en su aplicación (por ejemplo, http://localhost:2600/api/_getters/posts ).

Arriba, estamos definiendo un captador nuevo llamado posts que obtendrá una lista de publicaciones de JSON Placeholder API, una API REST gratuita que proporciona datos de prueba para pruebas y creación de prototipos.

Los captadores son uno de los dos tipos de extremos de la API en una aplicación Joystick y los otros son establecedores (estos datos "establecidos" en nuestra aplicación, la parte "crear, actualizar y eliminar" de CRUD). En una aplicación Joystick, los getters y setters se definen juntos en un solo objeto exportado desde el /api/index.js archivo que vemos arriba (referido como el "esquema" de su API en Joystick).

Este objeto luego se importa a /index.server.js y pasado como parte de las opciones al node.app() función—como api —del @joystick.js/node paquete. Esto le dice a Joystick que cargue automáticamente todos los getters y setters definidos en el archivo que vemos arriba cuando inicia el lado del servidor de nuestra aplicación.

Para este tutorial, estamos definiendo un captador único posts que devuelve datos de la API de marcador de posición JSON. Para que funcione, agregamos una nueva propiedad posts al objeto asignado a getters que a su vez tiene asignado un objeto.

Ese objeto contiene una propiedad get que se asigna a una función que es responsable de "obtener" nuestros datos y devolverlos a la solicitud HTTP que llamó al captador. Dentro de esa función, comenzamos creando una instancia de un objeto de URL a través de new URL() constructor (observa que hemos importado esto arriba desde el url paquete:está integrado en Node.js y nosotros no necesita instalarlo por separado).

A ese constructor, le pasamos la URL para la que queremos crear el objeto. En este caso, queremos usar el /posts punto final de la API de marcador de posición JSON ubicada en https://jsonplaceholder.typicode.com/posts .

A continuación, hacemos una verificación para ver si nuestro getter pasó algún input variables cuando se llamó (cómo funciona esto tendrá más sentido más adelante, pero piense en esto como si se pasara como un POST cuerpo a una solicitud HTTP). Si tenemos un id valor definido en nuestra entrada (el ID de una publicación en la API de marcador de posición JSON como 1 o 5 ), queremos crear una nueva instancia del URLSearchParams clase, pasando nuestro objeto de entrada. Aquí, cada propiedad del objeto se convertirá en un parámetro de consulta. Por ejemplo, un input valor de...

{ id: 5 }

se convertirá en...

?id=5

Para que ese valor sea útil, configuramos el .search propiedad del url objeto que creamos arriba al valor de searchParams emitido como un valor de cadena (usando el .toString() función).

Finalmente, con nuestro url completo objeto, llamamos al fetch() función que importamos del node-fetch paquete arriba, pasando el url objeto (fetch entiende cómo interpretar este objeto). Porque esperamos fetch() para devolvernos una promesa de JavaScript, al final, llamamos a .then() para decir "después de recibir una respuesta entonces haz esto".

El "esto" que estamos haciendo es tomar el response objeto y convertirlo a un formato JSON con el .json() método. Lo que esperamos obtener de esta cadena de métodos es una matriz de objetos que representan publicaciones de la API de marcador de posición JSON.

Con esto en su lugar, ahora estamos listos para conectar nuestros datos. Para hacer eso, vamos a necesitar una ruta donde podamos renderizar el componente que vamos a crear. Muy rápido, pasemos al /index.server.js archivo y configure esa ruta.

Cableando una ruta para nuestro componente

Si abrimos el /index.server.js en la raíz de nuestra aplicación, veremos que el joystick create app La función que llamamos anteriormente creó un archivo que importa y ejecuta automáticamente node.app() para nosotros junto con algunas rutas de ejemplo.

/index.servidor.js

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",
      });
    },
    "/posts": (req, res) => {
      res.render("ui/pages/posts/index.js");
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

Por defecto, una ruta raíz en / y un catch-all o 404 ruta en * (es decir, todo lo que no coincide con una ruta superior a esta) está predefinido para nosotros. Aquí, hemos agregado una ruta adicional /posts . A esa ruta, le hemos asignado una función para manejar la solicitud entrante tomando el req y res objetos. Aunque no lo parezca, detrás de escena, Joystick convierte esto en una ruta simple de Express.js, similar a nosotros escribiendo app.get('/posts', (req, res) => { ... }) .

Dentro de esa función, hacemos una llamada a una función especial agregada por Joystick al res objeto llamado .render() . Esta función, como su nombre lo indica, está diseñada para renderizar un componente Joystick en respuesta a una solicitud. A él, le pasamos la ruta a un componente de nuestra aplicación que queremos que represente, junto con un objeto de opciones (si es necesario, que no está aquí, así que lo hemos omitido).

Cuando esta ruta coincide con un navegador, Joystick irá y obtendrá este componente y el lado del servidor lo procesará en HTML para nosotros y enviará ese HTML de vuelta al navegador. Internamente, res.render() conoce el data opción en los componentes de Joystick. Si ve esto en un componente, "recoge" la llamada y obtiene los datos como parte del proceso de representación del lado del servidor.

Así es como vamos a llamar al posts captador que definimos anteriormente. Nuestro objetivo es hacer que cuando se cargue nuestra página, obtengamos HTML renderizado del lado del servidor sin datos ya cargados.

A continuación, debemos crear el componente en la ruta que estamos pasando a res.render() arriba.

Conectando un componente Joystick con datos de la API

Para comenzar, primero, necesitamos agregar el archivo que asumimos existirá en /ui/pages/posts/index.js :

/ui/pages/posts/index.js

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

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

export default Posts;

Aquí, solo estamos agregando un componente de esqueleto usando el ui.component() función importada del @joystick.js/ui paquete (instalado automáticamente para nosotros por joystick create ).

En la cadena HTML regresamos de nuestro render función, por ahora solo estamos representando un <div></div> vacío . Si visitamos la ruta que agregamos en el servidor en nuestro navegador en http://localhost:2600/posts , deberíamos ver una página en blanco en blanco.

Ahora estamos listos para conectar nuestros datos. Agreguemos todo lo que necesitamos y analicemos (no necesitamos mucho código):

/ui/pages/posts/index.js

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

const Posts = ui.component({
  data: async (api = {}, req = {}, input = {}) => {
    return {
      posts: await api.get('posts', {
        input,
      }),
    };
  },
  render: ({ data, each }) => {
    return `
      <div>
        <ul>
          ${each(data?.posts, (post) => {
            return `
              <li>
                <h4>${post.title}</h4>
                <p>${post?.body?.slice(0, 80)}...</p>
              </li>
            `;
          })}
        </ul>
      </div>
    `;
  },
});

export default Posts;

Lo crea o no, esto es todo lo que necesitamos para obtener nuestros datos y procesarlos en el lado del servidor en nuestra aplicación y en el navegador.

En la parte superior de nuestra definición de componente, hemos agregado una nueva opción data asignado a una función. Esta función recibe tres argumentos:

  1. api que es un objeto que contiene una versión isomorfa (lo que significa que funciona en el navegador y en el servidor) del get() y set() funciones integradas en ambos @joystick.js/ui y @joystick.js/node por llamar a nuestros getters y setters.
  2. req que es una versión segura para el navegador de la solicitud HTTP entrante (esto nos da acceso a req.params y req.context.user para que podamos hacer referencia a ellos al buscar datos).
  3. input cualquier dato de entrada pasado al recuperar datos a través del data.refetch() método (cubriremos esto en un momento).

Dentro de esa función, devolvemos un objeto que queremos asignar como el valor de data en nuestra instancia de componente. Aquí, como queremos recuperar una lista de publicaciones, definimos una propiedad posts y establecerlo igual a una llamada a api.get('posts') donde el 'posts' part es el nombre del captador que definimos anteriormente en el tutorial.

Debido a que esperamos que ese captador devuelva una matriz de objetos que representan nuestras publicaciones, asignamos nuestra llamada directamente a esa función, anteponiendo el await palabra clave (y añadiendo async a la función le pasamos a data ) para decirle a JavaScript que espere hasta que esta llamada responda antes de continuar interpretando el código.

El resultado final aquí es que en el servidor, nuestros datos se obtienen automáticamente y se configuran en data propiedad en nuestra instancia de componente. Abajo en el render función, podemos ver que hemos agregado una llamada para desestructurar o "arrancar" un data y each propiedad del argumento pasado a la función de representación (este es un objeto que representa la instancia del componente).

Abajo en nuestro HTML, hemos agregado un <ul></ul> etiqueta de lista desordenada, y dentro de ella, estamos usando la interpolación de JavaScript ${} sintaxis para decir "entre corchetes, llama al each() función que pasa el valor de data.posts ."

Esa función, each() recorrerá la matriz de publicaciones que le estamos pasando y, para cada una, devolverá una cadena de HTML de la función que le pasamos como segundo argumento. Esa función toma el elemento actual o, en este caso, post que se repite para su uso en el código HTML que se devuelve.

Aquí, generamos el title de cada publicación y una versión truncada del body para cada publicación en la matriz.

Si cargamos nuestro navegador ahora, deberíamos ver algunas publicaciones renderizadas en el navegador.

Si bien hemos terminado técnicamente, antes de terminar, aprendamos rápidamente cómo recuperar datos después de la carga inicial de la página.

/ui/pages/posts/index.js

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

const Posts = ui.component({
  data: async (api = {}, req = {}, input = {}) => {
    return {
      posts: await api.get('posts', {
        input,
      }),
    };
  },
  events: {
    'submit form': (event, component) => {
      event.preventDefault();
      const input = component.DOMNode.querySelector('input');

      if (input.value) {
        component.data.refetch({ id: input.value });
      } else {
        component.data.refetch();
      }
    },
  },
  render: ({ data, each }) => {
    return `
      <div>
        <form>
          <input type="text" placeholder="Type a post ID here..." />
          <button type="submit">Get Post</button>
        </form>
        <ul>
          ${each(data?.posts, (post) => {
            return `
              <li>
                <h4>${post.title}</h4>
                <p>${post?.body?.slice(0, 80)}...</p>
              </li>
            `;
          })}
        </ul>
      </div>
    `;
  },
});

export default Posts;

Si estamos creando una interfaz de usuario no trivial, es probable que en algún momento queramos recuperar datos en función de algún tipo de interacción del usuario o en algún intervalo (por ejemplo, sondeo de nuevos datos cada 5 segundos).

En el data propiedad asignada a nuestra instancia de componente, Joystick nos da un .refetch() método al que podemos llamar para realizar una recuperación a pedido. Si miramos el HTML devuelto por nuestro render() función, podemos ver que hemos agregado algunas líneas más, agregando un simple <form></form> con una entrada y un botón.

Recuerde que anteriormente en el servidor cuando definimos nuestro getter, agregamos el potencial para un id para ser aprobado para que podamos obtener una publicación específica. Por defecto, no estamos pasando nada, pero demostramos nuestro uso de data.refetch() (y la capacidad de pasarle valores de entrada), aquí estamos agregando un detector de eventos para el submit de nuestro formulario evento para hacer exactamente eso.

Mirando el events propiedad que hemos agregado a nuestra definición de componente, cuando se envía nuestro formulario, primero, queremos asegurarnos de llamar al event.preventDefault() función en el event argumento que se nos pasa (este es el evento DOM del navegador mientras sucede) para evitar que se llame al controlador de envío de formularios estándar o integrado en el navegador (esto desencadena una actualización de la página que queremos omitir).

Debajo de esto, tomamos el component instancia que se pasa automáticamente como la segunda propiedad a nuestros controladores de eventos en Joystick. En ese objeto, un DOMNode se agrega la propiedad que nos da acceso al componente actual tal como se muestra en el navegador (el código que escribimos aquí, nuestro componente Joystick, es solo una abstracción para generar estos nodos DOM dinámicamente).

En ese component.DOMNode valor que llamamos el querySelector método, pasando el selector de un elemento al que queremos acceder. Aquí, queremos obtener el <input /> etiqueta que se representa en nuestro componente. A cambio, esperamos recuperar el nodo DOM para ese elemento de entrada (por qué lo estamos almacenando en una variable llamada input ).

Debajo de esto, llamamos condicionalmente a component.data.refetch() en función de si nuestro input tiene un valor Si lo hace , queremos pasar ese valor como id propiedad en nuestro objeto de entrada. Aquí, el objeto que pasamos a component.data.refetch() se asigna automáticamente al input valor que pasamos al servidor cuando llamamos api.get('posts') arriba en nuestro data función.

Si input.value está vacío , queremos omitir el paso de cualquier entrada.

El resultado final de esto es que si hacemos pasar un valor (el ID de una publicación, por ejemplo, 1 o 5 ), le pasaremos eso a nuestro captador y esperamos recibir una sola publicación de la API de marcador de posición JSON. Si no pasa un valor, esperaremos la respuesta predeterminada de nuestra lista completa de publicaciones.

De vuelta en el navegador, si cargamos esto y escribimos un número en la entrada y presionamos "Obtener publicación", deberíamos ver nuestra lista reducida automáticamente a esa publicación. Si eliminamos el número y presionamos "Obtener publicaciones" nuevamente, deberíamos ver la lista completa restaurada.

Terminando

En este tutorial, aprendimos cómo conectar un punto final de API usando la función de captadores en Joystick que llamamos desde un componente usando el Joystick data property para obtener automáticamente y representar en el lado del servidor nuestro HTML con los datos dentro. También aprendimos cómo renderizar un componente a través de una ruta usando el res.render() en Joystick y cómo recuperar datos dentro de un componente en respuesta al comportamiento de un usuario.