Cómo usar CodeFlask para incrustar un editor de código en JavaScript

Cómo usar la biblioteca CodeFlask para representar dinámicamente un editor de código en una aplicación Joystick y recuperar su valor.

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 hacerlo, necesitamos instalar una dependencia:codeflask .

Terminal

npm i codeflask

Después de que esté instalado, continúe e inicie su servidor:

Terminal

cd app && joystick start

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

Escribiendo un componente para renderizar el editor de código

Todo nuestro trabajo para este tutorial se llevará a cabo en solo dos archivos. Primero, para preparar nuestra interfaz de usuario, necesitamos agregar un poco de CSS al index.css principal archivo en la raíz de nuestro proyecto (este es un archivo CSS global que Joystick carga para todas las páginas de nuestra aplicación):

/index.css

body {
  font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  font-size: 16px;
  background: #fff;
  margin: 0;
  padding: 0;
}

El cambio que hemos realizado aquí es agregar dos propiedades adicionales a la regla CSS existente para el body elemento:ajuste margin a 0 y padding a 0 . Queríamos hacer esto porque, como veremos, queremos que nuestro editor de código llene la pantalla. Sin estas dos líneas, veríamos un espacio de ~10 píxeles en todos los lados que parece un error.

/ui/pages/index/index.js

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

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

export default Index;

A continuación, queremos abrir el archivo en /ui/pages/index/index.js . Este archivo representa una página en nuestra aplicación (es decir, algo representado en una URL en nuestra aplicación). Dentro, encontrará un código de ejemplo que se genera automáticamente cuando ejecuta joystick create . Aquí, hemos reemplazado ese código con un componente de esqueleto sobre el cual construiremos nuestro editor de código.

Para comenzar, queremos hacer dos cosas:agregar el marcado HTML base que necesitaremos para representar nuestro editor de código y el JavaScript que inyectará el editor de código en nuestra página:

/ui/pages/index/index.js

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

const Index = ui.component({
  lifecycle: {
    onMount: (component) => {
      component.methods.attachCodeEditor(`{}`);
    },
  },
  methods: {
    attachCodeEditor: (code = '', component) => {
      component.flask = new CodeFlask('#code-editor', {
        language: 'js',
        defaultTheme: true,
        lineNumbers: true,
      });

      if (code) {
        component.flask.updateCode(code);
      }
    },
  },
  render: () => {
    return `
      <div id="code-editor"></div>
    `;
  },
});

export default Index;

Primero, queremos enfocarnos en el render en la parte inferior de nuestro componente. Aquí, hemos agregado el HTML base que necesitamos para que nuestro editor de código funcione:un solo <div></div> etiqueta con una ID que podemos pasar a codeflask .

La idea aquí es que este <div></div> servirá como objetivo que codeflask "apuntará" a. Si lo ve, inyectará el editor de código en este <div></div> , mostrándolo en la pantalla.

Más arriba en nuestro código, podemos ver que hemos importado CodeFlask del codeflask paquete. Hemos usado este casing (conocido como casing de Pascal) porque esperamos que el codeflask paquete para exportar una clase de JavaScript. Por lo general, usamos mayúsculas y minúsculas de Pascal para indicar que una variable contiene una clase en JavaScript.

Volviendo a nuestro componente, hemos agregado algunas propiedades adicionales. Primero, en la parte superior hemos agregado un objeto lifecycle y en ese objeto, un método onMount (método es el término utilizado para definir una función en un objeto en JavaScript).

Ese método, onMount , es lo que Joystick llama inmediatamente después del HTML devuelto por el render la función se representó o montó correctamente en la pantalla. En el interior, podemos ver que recibimos un argumento component que representa el componente instancia , o la representación en memoria del componente que estamos construyendo actualmente.

Esa instancia, un objeto, tiene acceso total a todas las demás propiedades de nuestro componente. Como podemos ver, desde ese objeto, dentro del onMount método que llamamos a component.methods.attachCodeEditor() . Si miramos un poco más abajo, podemos ver un methods objeto siendo definido con un attachCodeEditor método que se define en eso. Estos son uno en el mismo. Arriba en el onMount , todo lo que hacemos es llamar al attachCodeEditor función definida en el methods objeto de nuestro componente.

Si observamos la función, podemos ver que toma dos argumentos:code , una cadena de código que queremos representar en el editor, y como segundo argumento, component . Automáticamente en segundo plano, Joystick agrega la instancia del componente como el último argumento de todas las funciones. Entonces, en nuestro onMount ejemplo, porque no hay argumentos, component se convierte en el primer argumento. En attachCodeEditor , porque anticipamos que se pasa un argumento, Joystick asigna component como el segundo argumento porque ese es el "último" argumento posible.

Dentro de attachCodeEditor , traemos codeflask en la mezcla. Aquí, en el component instancia, estamos asignando una nueva propiedad flask y asignándolo al resultado de llamar a new CodeFlask() . Estamos haciendo esto ahora para que luego podamos hacer referencia al CodeFlask instancia en otro lugar de nuestro componente.

A new CodeFlask() pasamos dos cosas:el selector que queremos usar como destino de representación para nuestro editor de código, en este caso, el ID del <div></div> estamos renderizando, #code-editor —y un objeto de opciones.

Para nuestras opciones, mantenemos las cosas simples. Aquí, language representa el idioma que esperamos escribir en nuestro editor. Esto se usa para resaltar la sintaxis (entre bastidores, codeflask usa otra biblioteca llamada prismjs para resaltar/colorear nuestro código para que sea más fácil de leer).

A continuación, defaultTheme: true le dice a codeflask para incluir su propia hoja de estilo predeterminada. Aunque puede escribir una hoja de estilo personalizada para diseñar su código, para nuestras necesidades, el valor predeterminado funcionará bien.

Finalmente, pasamos lineNumbers: true para darnos números de línea en el lado izquierdo de nuestro editor de código.

Una vez que hayamos creado nuestro codeflask instancia, finalmente, verificamos si el code argumento pasado a attachCodeEditor contiene una verdad, lo que significa que hemos pasado más de una cadena vacía que hará que JavaScript devuelva true cuando hacemos referencia a la variable en un if afirmación—valor. Si lo hace queremos llamar al .updateCode() método en el codeflask instancia que asignamos a component.flask .

Aunque puede no parecer mucho, si cargamos esto en el navegador (como página de índice, aparecerá en http://localhost:2600 en su navegador) ahora, deberíamos ver nuestro editor de código representado en la pantalla.

Recuperando y validando el valor del editor de código

Aunque técnicamente "hemos terminado", sería útil ver cómo usar el editor de código en su propia aplicación. Para demostrar esto, pretenderemos que estamos creando un validador JSON. A continuación, queremos agregar una función checkIfValidJSON() y luego conecte eso al componente que escribimos arriba.

/lib/checkIfValidJSON.js

export default (string = '') => {
  try {
    const json = JSON.parse(string);
    return !!json;
  } catch (exception) {
    return false;
  }
};

En nuestro /lib carpeta (donde almacenamos código misceláneo para nuestra aplicación), hemos agregado un archivo checkIfValidJSON.js que exporta una sola función tomando un string como argumento.

Dentro de esa función, estamos tomando el string pasamos y se lo entregamos a JSON.parse() . Pero hemos ajustado esa llamada a JSON.parse() en un try/catch . Un try/catch dice "intente ejecutar este código, y si arroja un error por algún motivo, ejecute el catch declaración."

Aquí, si el string pasamos a JSON.parse() es JSON no válido, la función arrojará un error. En este caso, si lo hace lanza un error, nuestro catch la declaración se ejecutará y devolverá false de nuestra función exportada. Si es válido JSON, tomaremos el json devuelto variable y coloque un !! (doble explosión) delante de él, que convierte un valor en un valor booleano true o false (si la variable contiene un valor será true , si no, false ).

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import CodeFlask from 'codeflask';
import checkIfValidJSON from '../../../lib/checkIfValidJSON';

const Index = ui.component({
  state: {
    jsonStatus: 'ok',
  },
  lifecycle: {
    onMount: (component) => {
      component.methods.attachCodeEditor(`{}`);
    },
  },
  methods: { ... },
  css: `
    .codeflask {
      height: calc(100vh - 91px) !important;
    }

    header {
      display: flex;
      align-items: center;
      background: #ddd;
      color: #333;
      padding: 20px;
    }

    header button {
      margin-right: 20px;
      height: auto;
      font-size: 16px;
      padding: 10px 15px;
    }

    header p.error {
      background: yellow;
      color: red;
    }

    header p.ok {
      background: yellow;
      color: green;
    }
  `,
  events: {
    'click .validate-json': (event, component) => {
      const json = component.flask.getCode();
      const isValidJSON = checkIfValidJSON(json);

      if (isValidJSON) {
        component.setState({ jsonStatus: 'ok' });
      } else {
        component.setState({ jsonStatus: 'error' });
      }
    },
  },
  render: ({ when, state }) => {
    return `
      <header>
        <button class="validate-json">Validate JSON</button>
        ${when(state?.jsonStatus === 'error', `<p class="error"><strong>JSON Parse Error</strong> - Please double-check your syntax and try again.</p>`)}
        ${when(state?.jsonStatus === 'ok', `<p class="ok">Valid JSON!</p>`)}
      </header>
      <div id="code-editor"></div>
    `;
  },
});

export default Index;

Volviendo a nuestro componente, pongamos esto en práctica. Aquí, hemos agregado todo el código restante que necesitaremos.

Primero, debemos explicar nuestro objetivo:queremos poder validar el JSON que hemos ingresado en nuestro editor de código, a pedido. Para hacer eso, necesitamos una forma de "activar" la validación. Abajo en nuestro render anterior, hemos agregado algunas marcas HTML adicionales.

Hemos agregado un HTML <header></header> etiqueta y dentro de eso un <button></button> etiqueta. La idea aquí es que cuando hacemos clic en el <button></button> , queremos validar el JSON y establecer un valor en el state valor de nuestro componente. Ese valor, jsonStatus , se establecerá en una cadena que contenga error o ok .

Justo debajo de donde representamos nuestro <button></button> , hemos agregado dos declaraciones de interpolación de JavaScript (indicadas por el ${} sintaxis que dice "evalúe el código JavaScript entre llaves y luego devuelva el valor para incrustarlo en la cadena envolvente"), ambos llamando al when() función de renderización pasada automáticamente como parte de la instancia del componente Joystick.

Para acceder a ese when() función, usamos la desestructuración de JavaScript para "arrancar" when y state de ese component objeto de instancia. Para ser claros, si escribimos esto de otra manera, podríamos ver...

render: (component) => {
  return `
    <header>
      <button class="validate-json">Validate JSON</button>
      ${component.when(component.state?.jsonStatus === 'error', `<p class="error"><strong>JSON Parse Error</strong> - Please double-check your syntax and try again.</p>`)}
      ${component.when(component.state?.jsonStatus === 'ok', `<p class="ok">Valid JSON!</p>`)}
    </header>
    <div id="code-editor"></div>
  `;
},

La diferencia anterior es que usamos la desestructuración para crear una referencia abreviada a los valores on el component objeto. Entonces, component se convierte en { when, state } donde when y state son propiedades definidas en el component objeto.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import CodeFlask from 'codeflask';
import checkIfValidJSON from '../../../lib/checkIfValidJSON';

const Index = ui.component({
  state: {
    jsonStatus: 'ok',
  },
  lifecycle: {
    onMount: (component) => {
      component.methods.attachCodeEditor(`{}`);
    },
  },
  methods: { ... },
  css: `
    .codeflask {
      height: calc(100vh - 91px) !important;
    }

    header {
      display: flex;
      align-items: center;
      background: #ddd;
      color: #333;
      padding: 20px;
    }

    header button {
      margin-right: 20px;
      height: auto;
      font-size: 16px;
      padding: 10px 15px;
    }

    header p.error {
      background: yellow;
      color: red;
    }

    header p.ok {
      background: yellow;
      color: green;
    }
  `,
  events: {
    'click .validate-json': (event, component) => {
      const json = component.flask.getCode();
      const isValidJSON = checkIfValidJSON(json);

      if (isValidJSON) {
        component.setState({ jsonStatus: 'ok' });
      } else {
        component.setState({ jsonStatus: 'error' });
      }
    },
  },
  render: ({ when, state }) => {
    return `
      <header>
        <button class="validate-json">Validate JSON</button>
        ${when(state?.jsonStatus === 'error', `<p class="error"><strong>JSON Parse Error</strong> - Please double-check your syntax and try again.</p>`)}
        ${when(state?.jsonStatus === 'ok', `<p class="ok">Valid JSON!</p>`)}
      </header>
      <div id="code-editor"></div>
    `;
  },
});

export default Index;

Volviendo a centrarnos en nuestras declaraciones de interpolación y llamadas a when() , en Joystick, una "función de renderizado" es una función especial que se puede usar dentro del HTML devuelto por el render de un componente función. Aquí, when() es una función de representación que dice "cuando el valor pasado como mi primer argumento es true , devuelve la cadena HTML pasada como mi segundo argumento; de lo contrario, no devuelve nada".

Entonces, aquí estamos diciendo si state.jsonStatus es igual a 'error' queremos renderizar un <p></p> etiqueta con un mensaje de error y si state.jsonStatus es igual a ok , queremos representar un <p></p> etiqueta con un mensaje de "bien". De forma predeterminada, en la parte superior de nuestro componente, hemos agregado un state propiedad que se establece en un objeto que contiene los valores de estado predeterminados para nuestro componente. Aquí, por defecto, queremos jsonStatus para establecerse en ok .

Para ejecutar nuestra validación (y ajustar adecuadamente este jsonStatus value on state), a continuación, queremos centrarnos en el events objeto que hemos agregado a nuestro componente. Aquí, podemos definir detectores de eventos de JavaScript para nuestro componente que dicen "cuando detecte el evento especificado en el selector especificado, llame a esta función".

Aquí, hemos agregado un detector de eventos para un click evento en el .validate-json clase (recuerde, agregamos validate-json como la clase de nuestro <button></button> elemento hacia abajo en nuestro marcado HTML).

Cuando se hace clic en ese botón, queremos llamar a la función que hemos asignado aquí, que toma dos argumentos:el objeto de evento DOM de JavaScript sin procesar que describe el evento que tiene lugar y el component instancia (nuevamente, este paso de component aquí sigue la misma lógica que lo que describimos anteriormente).

Dentro de esa función, primero, necesitamos obtener el valor actual de nuestro editor. Para hacerlo llamamos al .getCode() método en el component.flask valor que asignamos en nuestro attachCodeEditor método arriba. Esto devuelve el valor actual de nuestro editor, cualquiera que sea, como una cadena. Luego, tomamos esa cadena y se la entregamos a nuestro checkIfValidJSON() función.

Recuerda:esa función devolverá true si nuestro JSON es válido y false si no es válido. Justo debajo de esto, si isValidJSON es cierto, llamamos al setState() en nuestra instancia de componente, configurando el jsonStatus valor a ok . Si nuestro JSON no es válido, hacemos lo mismo pero configuramos jsonStatus a 'error' .

El joystick tomará el control desde aquí. Ahora, cuando cambiamos nuestro jsonStatus valor relativo a la respuesta que recibimos de checkIfValidJSON() , Joystick volverá a renderizar nuestro componente. Como discutimos anteriormente, si jsonStatus es error deberíamos ver nuestro mensaje de error y, si está bien, nuestro mensaje de "bien".

Terminando

En este tutorial, aprendimos cómo renderizar un editor de código en una aplicación Joystick usando el codeflask paquete de NPM. Aprendimos cómo representar algo de HTML en un componente y luego usar Code Flask para inyectar dinámicamente un editor de código en nuestra página. También aprendimos cómo recuperar el valor de nuestro editor de código y usarlo para controlar la visualización de nuestro componente en función de su valor.