Screenshot 2023-07-16 at 11.01.55 AM.png
Cover image for joseph_appsmith

Joseph Petty Verified userVerified user

Sr. Developer Advocate

Appsmith

Building a Drawing Pad Widget using p5.js and an Iframe

Goal

Use the p5.js JavaScript library to create a multi-color drawing pad widget. 

Prerequisites

An Appsmith account

A new or existing app to build the new widget in

Overview

P5.js is a JavaScript library for creating drawings and graphics programatically. This guide will cover how to use the p5.js library in an Iframe Widget to capture the user's mouse movements and create a multi-color drawing pad widget! 

This will enable apps that allow users to create and save SVG drawings, for use cases like 

  1. Add a new JS Object

    Paste the following code into the new JS Object:

    export default {
     canvas: `
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
    <script>
     let windowWidth = 500;
     let windowHeight = 500;
     let canvas;
     let drawing = [];
     let currentPath = [];
     let isDrawing = false;
     let colorPicker;
     let clearButton;
     let undoButton;
     let currentColor = '#000000';
     function setup() {
       canvas = createCanvas(windowWidth, windowHeight);
       canvas.position(0, 40);
       canvas.mousePressed(startPath);
       canvas.mouseReleased(endPath);
       colorPicker = document.getElementById('color-picker');
       colorPicker.addEventListener('change', onColorChange);
       clearButton = document.getElementById('clear-button');
       clearButton.addEventListener('click', clearDrawing);
       undoButton = document.getElementById('undo-button');
       undoButton.addEventListener('click', undoDrawing);
     }
     function draw() {
       background(255);
       drawing.forEach(path => {
         stroke(path.color);
         strokeWeight(10);
         noFill();
         beginShape();
         path.forEach(({ x, y }) => vertex(x, y));
         endShape();
       });
       if (isDrawing) {
         stroke(currentColor);
         strokeWeight(10);
         noFill();
         currentPath.push({ x: mouseX, y: mouseY });
         beginShape();
         currentPath.forEach(({ x, y }) => vertex(x, y));
         endShape();
       }
     }
     function startPath() {
       isDrawing = true;
       currentPath = [];
       currentPath.color = currentColor;
       stroke(currentColor);
       drawing.push(currentPath);
     }
     function endPath() {
       currentPath.color = currentColor;
       isDrawing = false;
       autoSaveDrawing();
     }
     function onColorChange() {
       currentColor = colorPicker.value;
       currentColor = color(currentColor);
     }
     function clearDrawing() {
       drawing = [];
       autoSaveDrawing();
     }
     function undoDrawing() {
       drawing.pop();
       autoSaveDrawing();
     }
     function autoSaveDrawing() {
       const svg = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 \${width} \${height}">
         <rect x="0" y="0" width="\${width}" height="\${height}" fill="white"/>
         \${drawing
           .map(
             path => \`<path d="\${path
               .map(({ x, y }) => (x === path[0].x && y === path[0].y ? \`M\${x} \${y}\` : \`L\${x} \${y}\`))
               .join(' ')}" stroke="\${path.color}" stroke-width="10" fill="none"/>\`
           )
           .join('')}
       </svg>\`;
       parent.postMessage({ svg }, '*');
     }
     // Initial canvas setup
     setup();
    </script>
    <style>
     #buttonbar {
       display: flex;
       justify-content: flex-end;
     }
     #color-picker,
     #undo-button,
     #clear-button {
       margin-left: 10px;
     }
    </style>
    <div id="buttonbar">
     <input type="color" id="color-picker" value="#000000" />
     <button id="undo-button">Undo</button>
     <button id="clear-button">Clear</button>
    </div>
    <canvas id="canvas"></canvas>
    `
    }
     

    NOTE

    • The windowWidth and windowHeight properties at the top of the script can be changed to set the canvas (and output SVG) size. 
  2. Add a new Iframe widget to the canvas

    1. Add the new Iframe widget to your app
    2. Set the srcDoc property to: {{JSObject1.canvas}}
  3. Accessing the SVG drawing for saving

    The script posts a message from the Iframe to the parent window (your Appsmith app), which contains an object with an svg property. Each new line drawn will post a new message, updating the drawing which can be accessed at Iframe1.message.svg.

    drawing pad svg
  4. Modifying the Code (optional)

    You may want to adjust the JSObject code to insert variables from the rest of your app, for values like the color, image size, etc. This code has been written as a template literal (string), making it easy to insert ${variables}. However, the code also contains some template literals nested in the autoSaveDrawing() function that had to be \escaped. The extra backslashes tell the JS interpreter to ignore the next character so that it continues to be read as text instead of code. 

    escaping template literal

Conclusion

Once again, the Iframe Widget has come to the rescue, enabling new use cases and possibilities with just a few lines of code and an external library. Check out the links below for more awesome Iframe examples! 

Additional Resources