Graphics using Konva

Using Graphics with Konva

Drawing on the HTML5 Canvas using Konva

When the latest version of HTML (Hypertext Markup Language), HTML5, came out in 2014, one of the new features was the Canvas. This is an area that is made for placing graphics images. With the previous lessons, you have already had a chance to see some simple examples of using HTML5 Canvas graphics. However, if you want to learn more about using HTML5 Canvas graphics, here are some links that you may find useful:

Those links will give you a feel for using the HTML5 Canvas. For our purposes, we want to spend more time learning basic programming, rather than spend time learning about too many of the details of the HTML5 Canvas. So, we are going to get some help by using a JavaScript framework for drawing called Konva.js (Konva.js).

Using an external framework like Konva

Because of the popularity of the JavaScript programming language, many tools are available to make things easier to do than with just plain JavaScript. Some tools, like Konva.js, are relatively limited in scope. These limited scope tools are things that you should become comfortable with using. Other tools, like React and Angular, are very large and can control just about any aspect of a full blown web application. Those large scope tools are very popular because they can speed development time for large web application projects. Those large scope tools are also useful because they make it straightforward to breaking up the project into smaller pieces, thus making it easier for multiple people to work on a large project.

But, before you jump into those large scope tools like React or Angular, I believe it is good to learn plain JavaScript first, and use small scope tools (like Konva.js) to flatten out the learning curve. This is especially true if you are new to programming. So, we will create a web page that uses Konva.js to help out with drawing the graphics so that we can focus on more fundamental programming concepts like selection (or conditional) statements.

Starting off in CodeSandbox

Let’s use a starter template to begin the project for this lesson. Connect to your CodeSandbox account. In a different tab, go to the following link: Frontend for practicalcompute.cc. This will open up the template in CodeSandbox. You will see a view that looks like the following screen shot. Click on the Fork drop-down menu and select your CodeSandBox project name:

frontend template

Go back to the Dashboard and change the name of the sandbox to something like graphics_with_konva.

To install Konva, we need to modify the package.json file. Click the package.json file in the EXPLORER:

package.json

Then change the contents to what is shown below:

package.json
{
  "name": "javascript",
  "version": "1.0.0",
  "description": "The JavaScript template",
  "scripts": {
    "start": "parcel ./src/index.html",
    "build": "parcel build ./src/index.html"
  },
  "dependencies": {
    "konva": "^10.0.2"
  },
  "devDependencies": {
    "parcel": "^2.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^7.2.0"
  },
  "keywords": ["css", "javascript"]
}

The lines to add are lines 9-11. When these lines are added in, CodeSandbox will use npm to install Konva version 10.0.2 (or greater). Let’s test to see if Konva is now available by modifying index.mjs. In the EXPLORER, select the index.mjs file and change the contents to what you see below:

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

function init() {
  console.log("init called");
}

The new lines are 1 and 11. On line 1, we are now importing Konva from "konva". Line 11 just has some lines removed so that the Student class is no longer referenced. If there are no errors, Konva is installed.

Once you have Konva installed, you can modify the index.html file so that it has a location to place the graphics images. So, here is the new version of index.html:

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Graphics with Konva</title>
    <meta charset="UTF-8" />
    <script src="./index.mjs" type="module"></script>
  </head>
  <body>
    <h1>Shapes with Konva</h1>
  </body>
</html>

The new lines are 4 and 9. On line 4, the <title> contents is changed to Graphics with Konva. On line 9, the <h1> contents are changed to Shapes with Konva.

Starting off using vite

From the command prompt, go to your ~/Documents folder and run the following degit command:

$ cd ~/Documents
$ npx degit takebayashiv-cmd/vite-template-my-project graphics_with_konva

This will create the directory graphics_with_konva inside your ~/Documents directory. Change into that directory and run npm install. Then run npm run dev to start the vite server.

$ cd ~/Documents/graphics_with_konva
$ npm install
$ npm run dev

The last command will start up the vite server. You can see the page by opening your browser to http://localhost:5173.

To install Konva, go to the terminal where you are running the vite server and type CTRL+C, to stop the server. Then, run npm install konva to install Konva. Then, start up the vite server again by running npm run dev:

 VITE v7.1.10  ready in 126 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
^C
vern@msi-lianli-build:/home/student/Documents/graphics_with_konva$ npm install konva

added 2 packages, and audited 16 packages in 1s

6 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
vern@msi-lianli-build:/home/student/Documents/graphics_with_konva$ npm run dev

> vite-template-my-template@0.0.0 dev
> vite

5:34:54 PM [vite] (client) Re-optimizing dependencies because lockfile has changed

  VITE v7.1.10  ready in 133 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

Use Visual Studio Code to open the folder ~/Documents/graphics_with_konva and make the following changes to index.mjs

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function init() {
  console.log('init called');
}

The new lines are 1 and 11. Line 1 just imports Konva. Line 11 has been changed by deleting two lines from the original code that referred to the Student class.

Change your index.html to the following:

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script type="module" src="index.mjs"></script>
    <title>Graphics with Konva</title>
  </head>
  <body>
    <h1>Shapes with Konva</h1>
  </body>
</html>

The new lines are 6 and 9. Actually a line that linked to the image "vite.svg", has been removed. Line 6 changes the <title> contents to Graphics with Konva. Line 9 changes the <h1> contents to Shapes with Konva.

From this point forward, the same changes will be made to index.html and index.mjs, so only one set of source code will be shown on this page. The screen shots will be from the browser page showing the vite server contents.

Initial steps in using Konva

Let’s start by modifying index.html so that we have a <div> element that we use as our Konva container.

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Graphics with Konva</title>
    <meta charset="UTF-8" />
    <script src="./index.mjs" type="module"></script>
  </head>
  <body>
    <h1>Shapes with Konva</h1>
    <div id="konva_container"></div>
  </body>
</html>

The new line is line 10. This line adds a <div id="konva_container"> element to the markup. This <div> will be used as the container for our Konva graphics. Note the id, as we will refer to this inside of index.mjs.

We will start by modifying index.mjs so that we can add a shape using Konva. We will use a global variable for what Konva calls a layer that will be referenced by several functions eventually.

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

var layer;

function init() {
  console.log("init called");
  const stage = new Konva.Stage({
    container: "konva_container",
    width: 300,
    height: 200,
  });
  layer = new Konva.Layer();
  stage.add(layer);
}

The new lines are 9 and 13-19. Line 9 adds the global variable layer. This is where we need to add the Konva shapes. We make this global, so that we can access it from any function inside of index.mjs. Lines 13-17 create the stage. This is where you set up the contents for the Konva container. Note that we use the id for the <div> set up inside of index.html. We also set the width and height for this Konva container. Line 18 constructs the layer. Once we add this layer to the stage, any shape added to layer will appear inside the Konva container. This is what is done on line 19.

Add a shape to the layer

Now that we have things set up, let’s modify index.mjs to display a Circle. Here is the new version of index.mjs:

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

var layer;

function init() {
  console.log("init called");
  const stage = new Konva.Stage({
    container: "konva_container",
    width: 300,
    height: 200,
  });
  layer = new Konva.Layer();
  stage.add(layer);
  const circle = new Konva.Circle({
    x: 100,
    y: 75,
    radius: 30,
    fill: "green",
  });
  layer.add(circle);
}

The new lines are 20-26. Lines 20-25 define a Konva Circle. The x and y values are the center of the circle. The radius is the radius of the circle. The fill value is the color of the circle.

Line 26 adds this object to the layer so it will be displayed. Here is a screen shot showing the circle drawn using Konva.

first circle

We can do more complex shapes, but this is a start just to demonstrate how things work.

Outlining the steps to complete the app

Now that we know we can add a shape, let’s go over the steps we need to make a more complete app:

  • Input: We want user input to draw some shapes. In this case, we will allow the user to specify things like the location of the shape, the size of the shape, the color and whether or not the shape will be draggable. A draggable shape can be dragged using the mouse. That is not something you could easily do using just the HTML5 Canvas API. So, this will show why using a tool like Konva can be useful. Since we want to be able to specify more properties for the shape, we will make use of the HTML <dialog> element.

  • Output: We will draw the shapes by adding them to the layer. We have already set things up to be able to do this.

There won’t be any selection statements or repetition statements needed, as the main focus will be on the use of the <dialog> element for gathering user input.

Using HTML <dialog> elements

The HTML <dialog> element is a way of creating a custom dialog box. This is a very useful tool for gathering user input. For example, you can use a <dialog> element as part of a login screen to authenticate a user. In this particular app, a <dialog> element will allow us to gather multiple user inputs for the properties of a shape. Here is the new version of index.html that uses a <dialog> element.

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Graphics with Konva</title>
    <meta charset="UTF-8" />
    <script src="./index.mjs" type="module"></script>
  </head>
  <body>
    <h1>Shapes with Konva</h1>
    <div id="konva_container"></div>
    <dialog id="circle_dlg">
      <h3>Circle properties</h3>
      centerX:
      <input type="text" id="centerX_box" /><br />
      centerY:
      <input type="text" id="centerY_box" /><br />
      radius:
      <input type="text" id="radius_box" /><br />
      fill:
      <input type="text" id="circle_fill_box" /><br />
      draggable:
      <select id="circle_draggable_select">
        <option>true</option>
        <option>false</option>
      </select>
      <br /><br />
      <button id="circle_cancel_button">Cancel</button>
      <button id="circle_ok_button">Ok</button>
    </dialog>
    <button id="circle_add_button">Add circle</button>
  </body>
</html>

The new lines are 11-29 and 30. Lines 11-28 define a HTML <dialog> element with an id="circle_dlg" attribute. Lines 12 uses a <h3> element to put a small title for the dialog box. Line 13 is a prompt to enter the x-coordinate of the center. Line 14 uses an input text box to get the user’s input here. Note that we don’t know what the user will specify, so using an input text box is the most flexible way to get this input. Lines 15 and 16 do the same thing for the y-coordinate of the center of the circle. Line 17 and 18 do the same thing for the radius of the circle. Line 19 and 20 do the same thing for the fill color of the circle. Line 21 is a prompt to let the user choose if the shape will be draggable or not. Lines 22-25 define a <select> element. Here the only two choices allowed are true or false, so we limit the input using a <select> element. (You could also use radio buttons, instead of a <select>, but the <select> is easier to handle.) Line 26 puts a blank line before the <button> elements. Line 27 defines a <button> to let the user to Cancel, and line 28 defines a <button> that let’s the user choose to enter the shape. So, lines 12-28 will define what the dialog box will look like. Line 30 adds another <button> element. This button will be used to display the circle_dlg as a modal dialog.

To get this to work, we modify the index.mjs file. We will keep things relatively simple for this first pass. Here is the new version of index.mjs:

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

var layer;
var circle_dlg;

function init() {
  console.log("init called");
  const stage = new Konva.Stage({
    container: "konva_container",
    width: 300,
    height: 200,
  });
  layer = new Konva.Layer();
  stage.add(layer);
  circle_dlg = document.getElementById("circle_dlg");
  const circle_add_button = document.getElementById("circle_add_button");
  circle_add_button.addEventListener("click", () => {
    circle_dlg.showModal();
  });
}

The new lines are 10 and 21-25. Line 10 adds the global variable, circle_dlg, that will be used to show and close the <dialog> for the circle input values. Line 21 gets the reference to the <dialog> and stores this as circle_dlg. Line 22 gets a reference to the Add circle button. Lines 23-24 make it so that when the Add circle button is clicked, the circle_dlg.showModal() method is run to display the dialog.

The following screen shot shows the dialog being displayed after the user has clicked on the Add circle button:

add circle clicked

Now that we can see what the dialog will look like, we can add code to index.mjs to make this add the circle to the layer. Here is the new version of index.mjs:

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

var layer;
var circle_dlg;

function addCircle() {
  const centerX_box = document.getElementById("centerX_box");
  const centerY_box = document.getElementById("centerY_box");
  const radius_box = document.getElementById("radius_box");
  const circle_fill_box = document.getElementById("circle_fill_box");
  const circle_draggable_select = document.getElementById(
    "circle_draggable_select"
  );
  const x = Number(centerX_box.value);
  const y = Number(centerY_box.value);
  const radius = Number(radius_box.value);
  const fill = circle_fill_box.value;
  let draggable = true;
  if (circle_draggable_select.value === "false") {
    draggable = false;
  }
  const circle = new Konva.Circle({
    x: x,
    y: y,
    radius: radius,
    fill: fill,
    draggable: draggable,
  });
  layer.add(circle);
  circle_dlg.close();
}

function init() {
  console.log("init called");
  const stage = new Konva.Stage({
    container: "konva_container",
    width: 300,
    height: 200,
  });
  layer = new Konva.Layer();
  stage.add(layer);
  circle_dlg = document.getElementById("circle_dlg");
  const circle_add_button = document.getElementById("circle_add_button");
  circle_add_button.addEventListener("click", () => {
    circle_dlg.showModal();
  });
  const circle_ok_button = document.getElementById("circle_ok_button");
  circle_ok_button.addEventListener("click", addCircle);
}

The new lines are 12-37 and 53-54. This may seem like a lot of lines of new code. But, lines 12-37 define the addCircle() function and this contains a lot of lines because there are many user inputs for the circle dialog box. Lines 13-19 get references to the input text boxes and the <select> list that are used for gathering input. Lines 20-22 use the Number() function to convert the input for the x, y and radius values into numbers. Line 23 gets the fill color. This is a string, so this does not use the Number() function. Line 24 sets draggable to true. On lines 25-27, if the user has selected "false", then draggable is set to false. Lines 28-34 creates the Konva Circle using the user input values. Line 35 adds that circle to the layer, and line 36 closes the circle_dlg dialog box. So, there are quite a few lines, but mostly because we are gathering input for five properties of the circle.

Modal dialog boxes stop the program execution until the user deals with the dialog box. But, since JavaScript runs asynchronously, you cannot guarantee that subsequent lines of code won’t be executed before the user deals with the dialog box. So, if your program must get the results of the user entering something in the dialog box, you need to use async and await to make the program wait for the dialog box to be closed.

Line 53 gets a reference the the Ok button inside the circle dialog. Line 54 makes it so that the addCircle() function defined on lines 12-37 is called when this Ok button is clicked.

The following animated gif shows the user clicking on the Add circle button and filling out the circle dialog. The created circle is added. Since the draggable was set to true, you can drag the circle around. Note that when the circle gets outside of the Konva container, part of the circle is no longer displayed.

circle dialog

If you want to replay the animation, click on the Reload gif button.

Adding another shape

Now that we know how <dialog> elements work, let’s add another shape. We can use the Konva.Rect object to draw a rectangle. For a rectangle, the properties we want the user to be able to add are:

  • x - the x-coordinate of the top left corner (input text box).

  • y - the y-coordinate of the top left corner (input text box).

  • width - the width of the rectangle (input text box).

  • height - the height of the rectangle (input text box).

  • fill - the color of the rectangle (input text box).

  • draggable - whether or not the rectangle is draggable (select list).

Here is the new version of index.html which adds a dialog for a rectangle object:

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Graphics with Konva</title>
    <meta charset="UTF-8" />
    <script src="./index.mjs" type="module"></script>
  </head>
  <body>
    <h1>Shapes with Konva</h1>
    <div id="konva_container"></div>
    <dialog id="circle_dlg">
      <h3>Circle properties</h3>
      centerX:
      <input type="text" id="centerX_box" /><br />
      centerY:
      <input type="text" id="centerY_box" /><br />
      radius:
      <input type="text" id="radius_box" /><br />
      fill:
      <input type="text" id="circle_fill_box" /><br />
      draggable:
      <select id="circle_draggable_select">
        <option>true</option>
        <option>false</option>
      </select>
      <br /><br />
      <button id="circle_cancel_button">Cancel</button>
      <button id="circle_ok_button">Ok</button>
    </dialog>
    <dialog id="rect_dlg">
      <h3>Rectangle properties</h3>
      x:
      <input type="text" id="rectX_box" /><br />
      y:
      <input type="text" id="rectY_box" /><br />
      width:
      <input type="text" id="rect_width_box" /><br />
      height:
      <input type="text" id="rect_height_box" /><br />
      fill:
      <input type="text" id="rect_fill_box" /><br />
      draggable:
      <select id="rect_draggable_select">
        <option>true</option>
        <option>false</option>
      </select>
      <br /><br />
      <button id="rect_cancel_button">Cancel</button>
      <button id="rect_ok_button">Ok</button>
    </dialog>
    <button id="circle_add_button">Add circle</button>
    <button id="rect_add_button">Add rectangle</button>
  </body>
</html>

The new lines are 30-50 and 52. Lines 30-50 define the <dialog> element for getting the rectangle properties from the user. Line 31 defines a <h3> element to serve as a title for the dialog box. Lines 32 and 33 prompt for x and define an input text box to get the x value. Lines 34 and 35 do a similar thing for the y value. Lines 36-37 do a similar thing for the width, lines 38-39 do a similar thing for the height and lines 40-41 do a similar thing for the fill color. Line 42 is a prompt for the draggable value, and lines 43-46 create a <select> element to provide the user with a choice of making the rectangle draggable or not. Line 47 uses break elements to put a blank line before the Cancel and Ok buttons. Line 48 defines a Cancel button for the rectangle dialog and line 49 defines an Ok button for the rectangle dialog. That Ok button will be clicked on when the user wants to submit the values for a rectangle.

Line 52 adds an Add rectangle button, that will be used to bring up the rect_dlg dialog.

Next, we modify index.mjs to use the <dialog id="rect_dlg"> element we just added in index.html. Here is the new version of index.mjs:

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

var layer;
var circle_dlg;
var rect_dlg;

function addCircle() {
  const centerX_box = document.getElementById("centerX_box");
  const centerY_box = document.getElementById("centerY_box");
  const radius_box = document.getElementById("radius_box");
  const circle_fill_box = document.getElementById("circle_fill_box");
  const circle_draggable_select = document.getElementById(
    "circle_draggable_select"
  );
  const x = Number(centerX_box.value);
  const y = Number(centerY_box.value);
  const radius = Number(radius_box.value);
  const fill = circle_fill_box.value;
  let draggable = true;
  if (circle_draggable_select.value === "false") {
    draggable = false;
  }
  const circle = new Konva.Circle({
    x: x,
    y: y,
    radius: radius,
    fill: fill,
    draggable: draggable,
  });
  layer.add(circle);
  circle_dlg.close();
}

function addRect() {
  const rectX_box = document.getElementById("rectX_box");
  const rectY_box = document.getElementById("rectY_box");
  const rect_width_box = document.getElementById("rect_width_box");
  const rect_height_box = document.getElementById("rect_height_box");
  const rect_fill_box = document.getElementById("rect_fill_box");
  const rect_draggable_select = document.getElementById(
    "rect_draggable_select"
  );
  const x = Number(rectX_box.value);
  const y = Number(rectY_box.value);
  const width = Number(rect_width_box.value);
  const height = Number(rect_height_box.value);
  const fill = rect_fill_box.value;
  let draggable = true;
  if (rect_draggable_select.value === "false") {
    draggable = false;
  }
  const rect = new Konva.Rect({
    x: x,
    y: y,
    width: width,
    height: height,
    fill: fill,
    draggable: draggable,
  });
  layer.add(rect);
  rect_dlg.close();
}

function init() {
  console.log("init called");
  const stage = new Konva.Stage({
    container: "konva_container",
    width: 300,
    height: 200,
  });
  layer = new Konva.Layer();
  stage.add(layer);
  circle_dlg = document.getElementById("circle_dlg");
  const circle_add_button = document.getElementById("circle_add_button");
  circle_add_button.addEventListener("click", () => {
    circle_dlg.showModal();
  });
  rect_dlg = document.getElementById("rect_dlg");
  const rect_add_button = document.getElementById("rect_add_button");
  rect_add_button.addEventListener("click", () => {
    rect_dlg.showModal();
  });
  const circle_ok_button = document.getElementById("circle_ok_button");
  circle_ok_button.addEventListener("click", addCircle);
  const rect_ok_button = document.getElementById("rect_ok_button");
  rect_ok_button.addEventListener("click", addRect);
}

The new lines are 11, 40-68, 84-88 and 91-92. Line 11 adds the global variable, rect_dlg, that will be used to hold a reference to the rect dialog. Lines 40-68 define the addRect() function, that will be called when the user clicks on the Ok button inside of the rect dialog.

Lines 41-48 get references to all the elements used to gather input for the rect dialog. Lines 49-52 use the Number() function to convert the values from the input boxes for x, y, width and height. Line 53 gets the fill color from the input box for the fill. Lines 54-57 will set the draggable value, depending on whether the user selects "true" or "false" from the select list. Lines 58-65 construct the Konva.Rect object using the user input values. Line 66 adds that object to layer, and line 77 closes the rect dialog.

Line 84 stores the reference to the rect dialog. Line 85 gets a reference to the Add rectangle button, and lines 86-88 make it so that when the user clicks on that button, the rect dialog is shown as a modal dialog.

Line 91 gets a reference to the Ok button in the rect dialog. Line 92 makes it so that when the user clicks on that Ok button, the addRect() function defined on lines 40-68 gets called to add the rectangle to the layer.

Here are some screen shots showing the dialog boxes for adding two circles that are draggable, and a rectangle that is fixed in place:

Here is the first circle:

circle1

Here is the second circle:

circle2

Here is the rectangle, which is added last:

rect1

The following animated gif file shows that you cannot move the blue rectangle, but you can move both circles. Konva places the objects in multiple depths so that the first object is behind the second and third objects. It is possible to change this positioning, but that is beyond the scope of this lesson:

shapes2

Note that the blue rectangle is fixed so it cannot be moved at the beginning of the gif. The circles can be moved, but the first circle is always behind the second circle and the second circle is behind the rectangle. You can click on the Reload gif button to rerun the animation.

Making the Cancel buttons work

The last thing we need to do is make the Cancel buttons in the dialog boxes close the dialog box. Here is the new version of index.mjs that does this:

index.mjs
import Konva from "konva";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

var layer;
var circle_dlg;
var rect_dlg;

function addCircle() {
  const centerX_box = document.getElementById("centerX_box");
  const centerY_box = document.getElementById("centerY_box");
  const radius_box = document.getElementById("radius_box");
  const circle_fill_box = document.getElementById("circle_fill_box");
  const circle_draggable_select = document.getElementById(
    "circle_draggable_select"
  );
  const x = Number(centerX_box.value);
  const y = Number(centerY_box.value);
  const radius = Number(radius_box.value);
  const fill = circle_fill_box.value;
  let draggable = true;
  if (circle_draggable_select.value === "false") {
    draggable = false;
  }
  const circle = new Konva.Circle({
    x: x,
    y: y,
    radius: radius,
    fill: fill,
    draggable: draggable,
  });
  layer.add(circle);
  circle_dlg.close();
}

function addRect() {
  const rectX_box = document.getElementById("rectX_box");
  const rectY_box = document.getElementById("rectY_box");
  const rect_width_box = document.getElementById("rect_width_box");
  const rect_height_box = document.getElementById("rect_height_box");
  const rect_fill_box = document.getElementById("rect_fill_box");
  const rect_draggable_select = document.getElementById(
    "rect_draggable_select"
  );
  const x = Number(rectX_box.value);
  const y = Number(rectY_box.value);
  const width = Number(rect_width_box.value);
  const height = Number(rect_height_box.value);
  const fill = rect_fill_box.value;
  let draggable = true;
  if (rect_draggable_select.value === "false") {
    draggable = false;
  }
  const rect = new Konva.Rect({
    x: x,
    y: y,
    width: width,
    height: height,
    fill: fill,
    draggable: draggable,
  });
  layer.add(rect);
  rect_dlg.close();
}

function init() {
  console.log("init called");
  const stage = new Konva.Stage({
    container: "konva_container",
    width: 300,
    height: 200,
  });
  layer = new Konva.Layer();
  stage.add(layer);
  circle_dlg = document.getElementById("circle_dlg");
  const circle_add_button = document.getElementById("circle_add_button");
  circle_add_button.addEventListener("click", () => {
    circle_dlg.showModal();
  });
  rect_dlg = document.getElementById("rect_dlg");
  const rect_add_button = document.getElementById("rect_add_button");
  rect_add_button.addEventListener("click", () => {
    rect_dlg.showModal();
  });
  const circle_ok_button = document.getElementById("circle_ok_button");
  circle_ok_button.addEventListener("click", addCircle);
  const rect_ok_button = document.getElementById("rect_ok_button");
  rect_ok_button.addEventListener("click", addRect);
  const circle_cancel_button = document.getElementById("circle_cancel_button");
  const rect_cancel_button = document.getElementById("rect_cancel_button");
  circle_cancel_button.addEventListener("click", () => {
    circle_dlg.close();
  });
  rect_cancel_button.addEventListener("click", () => {
    rect_dlg.close();
  });
}

The new lines are 93-94 and 95-100. Lines 93 and 94 get references to the Cancel buttons in the circle and rect dialog boxes, respectively. Lines 95-97 make it so that the circle dialog is closed if you click on the Cancel button in the circle dialog. Lines 98-100 doe the same thing for the rect dialog.

A Cancel button in a dialog box allows the user to change their mind, and not enter any data. So, it is a good idea to include such a button when you make your custom dialog boxes.

Summary

  • Using a limited scope tool like Konva can be quite useful. You get much better graphic effects than most people would be able to add in themselves. Even if you could do things like making objects that are draggable, this would be a lot of code.

  • Creating custom dialog boxes for gathering user input with <dialog> elements is a very useful skill. The basics of setting up a custom dialog box, and then processing the input are relatively straightforward using plain JavaScript.

  • For a custom dialog, you use <input type="text"> elements for input that has a wide range of possible values. But, you do have to think about what happens when the user puts in invalid input.

  • For a custom dialog, using a <select> list is good in the sense that you don’t have to worry much about invalid input. But, this can only be used when there are a limited number of choices for the data you are trying to get.