Creación de un botón de cargador en Joystick

Cómo crear un componente de botón con un estado de carga dinámico basado en accesorios.

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.

Añadir iconos

Antes de profundizar en nuestro botón de carga, queremos agregar soporte para la biblioteca de iconos de Font Awesome. A partir de esto, usaremos un icono de "girador" que podemos mostrar cuando nuestro botón esté en estado de carga.

Si no tiene una cuenta de Font Awesome, diríjase al sitio y configure una cuenta (no son del tipo spam, así que no se preocupe por ser bombardeado). Una vez que haya iniciado sesión, debe ser redirigido a una pantalla que muestra un "Código de kit", que es una etiqueta de secuencia de comandos que debemos agregar a nuestra aplicación.

Si haces ya tiene una cuenta de Font Awesome, solo diríjase a la página de Kits y verá un botón azul "Nuevo Kit +" en la esquina superior derecha de la página. Haga clic aquí para generar una etiqueta de secuencia de comandos similar a la que ve arriba.

Una vez que tenga acceso a su código de kit, cópielo y abra el proyecto Joystick que acabamos de crear en su IDE. A partir de ahí, queremos abrir el /index.html archivo en la raíz del proyecto.

/index.html

<!doctype html>
<html class="no-js" lang="en">
  <head>
    <meta charset="utf-8">
    <title>Joystick</title>
    <meta name="description" content="An awesome JavaScript app that's under development.">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#FFCC00">
    <link rel="apple-touch-icon" href="/apple-touch-icon-152x152.png">
    <link rel="stylesheet" href="/_joystick/index.css">
    <link rel="manifest" href="/manifest.json">
    <script src="https://kit.fontawesome.com/8c0c20c9e4.js" crossorigin="anonymous"></script>
    ${css}
  </head>
  <body>
    <div id="app"></div>
    ${scripts}
    <script>
      if ("serviceWorker" in navigator) {
        navigator.serviceWorker.register("/service-worker.js");
      }
    </script>
  </body>
</html>

Dentro de ese archivo, justo encima del ${css} etiqueta, queremos pegar en el <script></script> etiqueta que acabamos de copiar de Font Awesome. Una vez hecho esto, guarde el archivo y ahora Font Awesome se cargará globalmente en toda la aplicación.

Implementando un botón de cargador

El resto de nuestro trabajo para este tutorial se centrará en crear un componente de botón reutilizable con un estado de carga interno. Nuestros objetivos serán:

  1. Tener un botón con dos estados:cargando y sin cargar.
  2. Una forma de llamar a una función que hará algún trabajo relevante para nuestro botón.
  3. Una devolución de llamada a la que podemos llamar para decirle al botón que nuestro trabajo ha terminado.

Para comenzar, vamos a crear un nuevo componente de esqueleto en /ui/components/loaderButton/index.js :

/ui/components/loaderButton/index.js

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

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

export default LoaderButton;

Aquí, estamos creando un componente Joystick usando el @joystick.js/ui biblioteca con una sola opción render que devuelve un <button></button> etiqueta para su marcado.

/ui/components/loaderButton/index.js

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

const LoaderButton = ui.component({
  defaultProps: {
    label: 'Button',
    loadingLabel: 'Loading...',
  },
  state: {
    loading: false,
  },
  render: () => {
    return `
      <button>
      </button>
    `;
  },
});

export default LoaderButton;

A continuación, queremos agregar dos detalles menores:un defaultProps opción y state opción. Para defaultProps , estamos anticipando un label prop que se pasa a nuestro componente. Aquí, decimos "si no label o loadingLabel prop, reemplácelos con los predeterminados proporcionados aquí". De manera similar, para state , estamos configurando el valor predeterminado de loading en el estado dentro del componente. Como veremos a continuación, esto entrará en juego cuando actualicemos nuestro marcado a continuación para cambiar lo que se representa en función de nuestro state y props .

/ui/components/loaderButton/index.js

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

const LoaderButton = ui.component({
  defaultProps: {
    label: 'Button',
    loadingLabel: 'Loading...',
  },
  state: {
    loading: false,
  },
  render: ({ state, when, props }) => {
    return `
      <button ${state.loading ? 'disabled' : ''} class="button ${state.loading ? 'is-loading' : ''}">
        ${when(state.loading, `<i class="fas fa-circle-notch fa-spin"></i>`)} ${state.loading ? props.loadingLabel : props.label}
      </button>
    `;
  },
});

export default LoaderButton;

Ahora la parte importante. Aquí, hemos ampliado el marcado devuelto por nuestro render() función para incluir la lógica condicional necesaria para mutar el estado de nuestro botón relativo a props y state .

Porque el render() está devolviendo una cadena de HTML, aquí, aprovechamos la interpolación de cadenas de JavaScript (una forma de evaluar una variable y devolver su resultado dentro de una cadena) para construir dinámicamente el HTML que representará el estado actual de nuestro botón.

Desde la apertura <button etiqueta, la primera declaración que vemos es ${state.loading ? 'disabled' : ''} . Esto dice "si el valor actual de state.loading es true , devuelve una cadena con disabled dentro de él y, de lo contrario, devolver una cadena vacía". Para obtener acceso a state , lo extraemos de la instancia del componente pasada a nuestro render() función. Aquí, usamos la desestructuración de JavaScript para "separar" ese valor, exponiendo las propiedades definidas en él como variables directamente dentro de nuestra función de representación.

En términos de qué estamos haciendo aquí, si nuestro botón está en estado de carga, queremos deshabilitarlo para evitar clics adicionales mientras se completa el trabajo que le hemos asignado a ese botón. Aquí, agregamos dinámicamente el disabled atributo a nuestro <button></button> etiqueta basada en el valor de state.loading . Entonces, si estamos cargando, deshabilite el botón, y si no lo estamos, hágalo activo/se puede hacer clic.

A la derecha de esto, usando el mismo concepto con ${state.loading ? 'is-loading' : ''} , diciendo "si state.loading es cierto, queremos agregar dinámicamente una clase CSS a nuestro <button></button> llamado is-loading ." Esto nos permitirá agregar algunos estilos CSS más adelante en función del estado de carga del botón.

En la siguiente línea (ahora dentro de nuestro <button></button> etiqueta), usamos una función especial (conocida como "función de renderizado" en Joystick) llamada when() para renderizar condicionalmente el ícono de carga (hemos elegido el ícono Circle Notch de Font Awesome que incluye una clase de animación incorporada fa-spin ) para nuestro botón si el valor de state.loading es true . El primer argumento pasado a when() es el valor que queremos "probar" para veracidad y el segundo valor es una cadena de HTML para representar si el primer valor es verdadero.

Finalmente, usamos la misma sintaxis de interpolación que la primera para representar condicionalmente la etiqueta de nuestro <button></button> , justo a la derecha de nuestro icono. Aquí, decimos si state.loading es true , queremos renderizar el loadingLabel valor de props, de lo contrario, solo queremos representar el label regular apoyo.

/ui/components/loaderButton/index.js

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

const LoaderButton = ui.component({
  defaultProps: {
    label: 'Button',
    loadingLabel: 'Loading...',
  },
  state: {
    loading: false,
  },
  css: `
    .button {
      padding: 20px;
      border: none;
      background: #333;
      color: #fff;
      border-radius: 3px;
      font-size: 15px;
      cursor: pointer;
    }

    .button:active {
      position: relative;
      top: 1px;
    }

    .button i {
      margin-right: 5px;
    }

    .button.is-loading,
    .button:disabled {
      opacity: 0.9;
      pointer-events: none;
    }
  `,
  render: ({ state, when, props }) => {
    return `
      <button ${state.loading ? 'disabled' : ''} class="button ${state.loading ? 'is-loading' : ''}">
        ${when(state.loading, `<i class="fas fa-circle-notch fa-spin"></i>`)} ${state.loading ? props.loadingLabel : props.label}
      </button>
    `;
  },
});

export default LoaderButton;

Entrando en los últimos detalles. Aquí, hemos agregado los estilos CSS necesarios para nuestro botón. Aquí, hemos definido estilos para un botón negro simple que "rebota" hacia abajo cuando se hace clic (para simular la profundidad de un botón físico) y su opacidad cambia al 90 % sin interacciones de desplazamiento/clic cuando está cargando o deshabilitado. estado.

/ui/components/loaderButton/index.js

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

const LoaderButton = ui.component({
  defaultProps: {
    label: 'Button',
    loadingLabel: 'Loading...',
  },
  state: {
    loading: false,
  },
  css: `...`,
  events: {
    'click button': (event, component) => {
      if (component.props.onClick) {
        component.setState({ loading: true }, () => {
          component.props.onClick(event, () => {
            component.setState({ loading: false });
          });
        });
      }
    }
  },
  render: ({ state, when, props }) => {
    return `
      <button ${state.loading ? 'disabled' : ''} class="button ${state.loading ? 'is-loading' : ''}">
        ${when(state.loading, `<i class="fas fa-circle-notch fa-spin"></i>`)} ${state.loading ? props.loadingLabel : props.label}
      </button>
    `;
  },
});

export default LoaderButton;

Ahora la parte importante. Nuestro último trabajo para nuestro componente:manejo de eventos de clic. Aquí, hemos agregado el events opción a nuestro componente que nos ayuda a definir los detectores de eventos de JavaScript. En el objeto pasado a events , definimos un detector de eventos especificando primero un nombre de propiedad en forma de <event> <selector> patrón donde <event> es el tipo del evento DOM que queremos escuchar y <selector> es el elemento que queremos escuchar para el evento on .

A esa propiedad, le asignamos la función que se llama cuando se detecta el evento especificado en el selector especificado. Para esa función, recibimos dos argumentos:el DOM sin formato event que tuvo lugar y nuestro component instancia.

Dentro de la función aquí, primero verificamos si nos han pasado un onClick función a través de props . Esto es importante. Esta es la función que queremos llamar para hacer el trabajo que determinará el estado de carga de nuestro botón (por ejemplo, cargar un archivo, guardar un cambio, etc.). Si esa función existe, primero, nos aseguramos de configurar state.loading a true usando el .setState() en nuestra instancia de componente (le pasamos a esa función un objeto con las propiedades de estado que queremos actualizar, junto con sus nuevos valores).

Como segundo argumento de esto, pasamos una devolución de llamada para disparar después state.loading está establecido en true . Dentro de eso llamamos al onClick función pasada a través de accesorios, entregándole el evento DOM que se disparó y como segundo argumento, una función para llamar una vez que se haya "terminado" cualquier trabajo.

Dentro de esa función, observe que revertimos state.loading volver a false . La idea es que una vez que se señale que el trabajo está "terminado", ya no queremos mostrar nuestro botón en un estado de carga (es decir, queremos que se pueda hacer clic en él).

Ahora, para la parte divertida, tomemos nuestro componente y pongámoslo en uso.

Usando el botón del cargador

Para probar nuestro botón de carga, vamos a modificar el componente ubicado en /ui/pages/index/index.js ya que ya está conectado a la ruta raíz de nuestra aplicación.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import LoaderButton from '../../components/loaderButton';

const Index = ui.component({
  render: ({ component }) => {
    return `
      <div>
        ${component(LoaderButton, {
          label: 'Start Machine',
          loadingLabel: 'Starting machine...',
          onClick: (_event, callback) => {
            setTimeout(() => {
              if (callback) callback();
            }, 3000);
          },
        })}
      </div>
    `;
  },
});

export default Index;

Aquí, estamos sobrescribiendo completamente el contenido existente de este archivo. Arriba, hemos importado nuestro LoaderButton componente. Abajo en el render() función para nuestro Index componente, hemos "arrancado" el component() función render para ayudarnos a renderizar nuestro LoaderButton componente en el Index página.

A esa función le pasamos el LoaderButton componente como lo importamos en la parte superior del archivo. Y como segundo argumento, pasamos un objeto de props que queremos pasar a nuestro componente. Si miramos aquí, podemos ver los tres accesorios que esperamos:label , loadingLabel y onClick .

Para el onClick , tomamos el evento DOM que esperamos (aquí, prefijamos el nombre del argumento con un _ guión bajo para sugerir que no vamos a usar la variable en nuestro código. Después de esto, tomamos nuestra devolución de llamada "hecha". Aquí, para simular hacer algo de trabajo, hemos agregado un setTimeout() durante 3 segundos (el 3000 es 3 segundos en milisegundos) y dentro, si nos pasaran un callback , lo llamamos.

¡Simple como eso! Ahora, si cargamos http://localhost:2600 en nuestro navegador, deberíamos ver nuestro botón, y si hacemos clic en él, deberíamos verlo cambiar a su estado de carga.

Terminando

En este tutorial, aprendimos cómo crear un botón de carga con Joystick. Aprendimos cómo agregar íconos usando un enlace CDN de Font Awesome, y luego, cómo conectar un componente personalizado que podría recibir accesorios cuando se usa para cambiar la etiqueta, cargar texto y la funcionalidad onClick cuando se hace clic en el botón. Finalmente, aprendimos cómo usar el botón, usando un setTimeout() para demostrar un trabajo de ejecución prolongada y cambiar nuestro botón a su estado predeterminado después de que se completó la carga.