tutorial // Mar 18, 2022

How to Use CodeFlask to Embed a Code Editor in JavaScript

How to use the CodeFlask library to dynamically render a code editor in a Joystick app and retrieve its value.

How to Use CodeFlask to Embed a Code Editor in JavaScript

Getting Started

For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.

To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):

Terminal

npm i -g @joystick.js/cli

This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:

Terminal

joystick create app

After a few seconds, you will see a message logged out to cd into your new project and run joystick start. Before you do, we need to install one dependency: codeflask.

Terminal

npm i codeflask

After that's installed, go ahead an start up your server:

Terminal

cd app && joystick start

After this, your app should be running and we're ready to get started.

Writing a component to render the code editor

All of our work for this tutorial will take place in just two files. First, to prepare our UI, we need to add a little bit of CSS to the main index.css file at the root of our project (this is a global CSS file that's loaded for all pages in our app by Joystick):

/index.css

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

The change we've made here is adding two additional properties to the existing CSS rule for the body element: setting margin to 0 and padding to 0. We wanted to do this because, as we'll see, we want our code editor to fill the screen. Without these two lines, we'd see a ~10px gap on all sides which looks like a bug.

/ui/pages/index/index.js

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

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

export default Index;

Next, we want to open up the file at /ui/pages/index/index.js. This file represents a page in our app (meaning, something rendered at a URL in our app). Inside, you will find some example code that is automatically generated when you run joystick create. Here, we've replaced that code with a skeleton component upon which we'll build out our code editor.

New to Joystick and want to learn more about its component library? Read this tutorial on building your first Joystick component.

To begin, we want to do two things: add the base HTML markup we'll need to render our code editor and the JavaScript that will inject the code editor into our page:

/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;

First, we want to focus down in the render function at the bottom of our component. Here, we've added the base HTML that we need to make our code editor work: a single <div></div> tag with an ID that we can pass to codeflask.

The idea here is that this <div></div> will serve as a target that codeflask will "aim" for. If and when it sees it, it will inject the code editor into this <div></div>, rendering it on screen.

Further up in our code, we can see that we've imported CodeFlask from the codeflask package. We've used this casing (known as Pascal-casing) because we expect the codeflask package to export a JavaScript class. Typically, we use Pascal-case to signify that a variable contains a class in JavaScript.

Back on our component, we've added a few additional properties. First, at the very top we've added an object lifecycle and on that object, a method onMount (method is the term used for defining a function on an object in JavaScript).

That method, onMount, is what Joystick calls immediately after the HTML returned by the render function is successfully rendered or mounted on screen. Inside, we can see that we receive an argument component which represents the component instance, or, the in-memory representation of the component we're currently building.

That instance—an object—has full access to all of the other properties of our component. As we can see, from that object, inside of the onMount method we call to component.methods.attachCodeEditor(). If we look a little further down, we can see a methods object being defined with an attachCodeEditor method being defined on that. These are one in the same. Up in the onMount, all we're doing is calling to the attachCodeEditor function defined on the methods object of our component.

If we look at the function, we can see that it takes two arguments: code, a string of code that we want to render in the editor, and as the second argument, component. Automatically behind the scenes, Joystick appends the component instance as the last argument to all functions. So, in our onMount example, because there are no arguments, component becomes the first argument. In attachCodeEditor, because we anticipate an argument being passed, Joystick assigns component as the second argument because that is the "last" possible argument.

Inside of attachCodeEditor, we bring codeflask into the mix. Here, on the component instance, we're assigning a new property flask and assigning it to the result of calling new CodeFlask(). We're doing this now so that later, we can reference the CodeFlask instance elsewhere in our component.

To new CodeFlask() we pass two things: the selector we want to use as the render target for our code editor—in this case, the ID of the <div></div> we're rendering, #code-editor—and an object of options.

For our options, we're keeping things simple. Here, language represents the language we expect to be typing into our editor. This is used for syntax highlighting (behind the scenes, codeflask uses another library called prismjs to highlight/color our code so it's easier to read).

Next, defaultTheme: true tells codeflask to include its own default stylesheet. Though you can write a custom stylesheet to style your code, for our needs the default will do just fine.

Finally, we pass lineNumbers: true to give us line numbers on the left-hand side of our code editor.

Once we've created our codeflask instance, finally, we check to see if the code argument passed to attachCodeEditor contains a truthy—meaning, we've passed more than an empty string which will cause JavaScript to return true when we reference the variable in an if statement—value. If it does we want to call to the .updateCode() method on the codeflask instance we assigned to component.flask.

Though it may not look like much, if we load this up in the browser (as the index page, this will show up at http://localhost:2600 in your browser) now, we should see our code editor rendered on screen.

Retrieving and validating the code editor value

While we're technically "done," it would be helpful to see how to actually put the code editor to use in your own app. To demonstrate this, we're going to pretend that we're creating JSON validator. Next, we want to add a function checkIfValidJSON() and then wire that up to the component we wrote above.

/lib/checkIfValidJSON.js

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

In our /lib folder (where we store miscellaneous code for our app), we've added a file checkIfValidJSON.js which exports a single function taking a string as an argument.

Inside of that function, we're taking the string we pass and handing it off to JSON.parse(). But, we've wrapped that call to JSON.parse() in a try/catch. A try/catch says "try to run this code, and if it throws an error for any reason, execute the catch statement."

Here, if the string we pass to JSON.parse() is invalid JSON, the function will throw an error. In this case, if it does throw an error, our catch statement will execute and return false from our exported function. If it's valid JSON, we will take the returned json variable and place a !! (double-bang) in front of it which converts a value into a Boolean true or false (if the variable contains a value it will be true, if not, 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;

Back in our component, let's put this to use. Here, we've added in all of the remaining code that we'll need.

First, we should explain our goal: we want to be able to validate the JSON we've typed into our code editor, on-demand. To do that, we need a way to "trigger" the validation. Down in our render function above, we've added some additional HTML markup.

We've added an HTML <header></header> tag and inside of that a <button></button> tag. The idea here is that when we click the <button></button>, we want to validate the JSON and set a value on the state value of our component. That value, jsonStatus, will either be set to a string containing error or ok.

Just beneath where we're rendering our <button></button>, we've added two JavaScript interpolation statements (denoted by the ${} syntax which says "evaluate the JavaScript code in between the braces and then return the value to embed it in the wrapping string"), both calling to the when() render function automatically passed as part of the Joystick component instance.

To access that when() function, we use JavaScript destructuring to "pluck off" when and state from that component instance object. To be clear, if we wrote this another way, we might see...

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>
  `;
},

The difference above being that we use destructuring to create a short-hand reference to values on the component object. So, component becomes { when, state } where when and state are properties defined on the component object.

/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;

Focusing back on our interpolation statements and calls to when(), in Joystick, a "render function" is a special function that can be used within the HTML returned by a component's render function. Here, when() is a render function that says "when the value passed as my first argument is true, return the HTML string passed as my second argument—otherwise, return nothing."

So, here, we're saying if state.jsonStatus is equal to 'error' we want to render a <p></p> tag with an error message and if state.jsonStatus is equal to ok, we want to render a <p></p> tag with an "okay" message. By default, way at the top of our component, we've added a state property which is set to an object containing the default state values for our component. Here, by default, we want jsonStatus to be set to ok.

To actually run our validation (and appropriately adjust this jsonStatus value on state), next, we want to focus on the events object we've added to our component. Here, we can define JavaScript event listeners to our component which say "when you detect the event specified on the selector specified, call this function."

Here, we've added an event listener for a click event on the .validate-json class (remember, we added validate-json as the class of our <button></button> element down in our HTML markup).

When that button is clicked, we want to call the function we've assigned here which takes two arguments: the raw JavaScript DOM event object describing the event taking place and the component instance (again, this passing of component here follows the same logic as what we described earlier).

Inside of that function, first, we need to get the current value of our editor. To do it, we call to the .getCode() method on the component.flask value we assigned in our attachCodeEditor method up above. This returns the current value of our editor—whatever it may be—as a string. Next, we take that string and hand it off to our checkIfValidJSON() function.

Remember: that function will return true if our JSON is valid and false if it's invalid. Just below this, if isValidJSON is true, we call to the setState() method on our component instance, setting the jsonStatus value to ok. If our JSON is invalid, we do the same thing but set jsonStatus to 'error'.

Joystick will take over from here. Now, when we change our jsonStatus value relative to the response we receive from checkIfValidJSON(), Joystick will re-render our component. Like we discussed above, if jsonStatus is error we should see our error message rendered, and if it's okay, our "okay" message.

Wrapping up

In this tutorial we learned how to render a code editor in a Joystick app using the codeflask package from NPM. We learned how to render some HTML in a component and then use Code Flask to dynamically inject a code editor into our page. We also learned how to retrieve the value from our code editor and use that to control the display of our component based on its value.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode