3
votes

I have basic HTML as below:

<!DOCTYPE html>
<html>
<head>
    <title>Art Maker!</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Monoton">
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <h1>Pixel Art</h1>

    <h2>Choose Grid Size</h2>
    <form id="sizePicker">
        Grid Height:
        <input type="number" id="inputHeight" name="height" min="1" value="1">
        Grid Width:
        <input type="number" id="inputWidth" name="width" min="1" value="1">
        <input type="submit">
    </form>

    <h2>Pick A Color</h2>
    <input type="color" id="colorPicker">

    <h2>Design Canvas</h2>
    <table id="pixelCanvas"></table>

    <script src="designs.js"></script>
</body>
</html>

JavaScript below are used to:

  1. obtain user input: height & width
  2. draw grid based on height & width
  3. obtain HTML color picker
  4. fill a cell with background color based on step (3) when user click on the cell

I'm stuck at step (4). I created a function respondToClick(event) and attach it to tblRow with eventListener. It should fill the cell with background color when "click"; but it doesn't. Please advise where goes wrong.

// obtain grid size value; height & width

let height = document.getElementById('inputHeight').value;

let width = document.getElementById('inputWidth').value;

const gridHeight = document.getElementById('inputHeight');

gridHeight.addEventListener("input", function() {
  height = document.getElementById('inputHeight').value;
})

const gridWidth = document.getElementById('inputWidth');

gridWidth.addEventListener("input", function() {
  width = document.getElementById('inputWidth').value;
})

/ function to create canvas

const table = document.getElementById('pixelCanvas');

function createCanvas(event) {
  
  for (let h = 1; h <= height; h++) {
    const row = document.createElement('tr');
    
    for (let w = 1; w <= width; w++) {
      const cell = document.createElement('td');
      
      cell.style.cssText = "height: 15px; width: 15px";
      row.appendChild(cell);
    }
    
    table.appendChild(row);
  }  
}

const form = document.querySelector('form');

// bind createCanvas() to "submit"

form.addEventListener('submit', createCanvas);

// event listener to update color

let color = document.getElementById('colorPicker').value;

document.getElementById('colorPicker').onchange = function() {
  color = this.value;
}

// function activated when user click on only

function respondToClick(event) {
  if (event.target.nodeName.toLowerCase() === 'td') {
    event.target.style.backgroundColor = color;
  }
}

const tblRow = document.getElementsByTagName('tr');

tblRow.forEach(row => function() {
  row.addEventListener("click", respondToClick);
});
2
do you get any errors in the console?Alan Omar

2 Answers

1
votes

a couple of issues

  • you should bind the click handler after creating the rows since you bind to the tr. Or better yet, since you already delegate the event, use the table element to bind the handler, since it is there from the beginning.
  • you need to stop the form from actually submitting (causes a reload of the page)
  • you need to clear the canvas when creating a new one

// obtain grid size value; height & width

const gridHeight = document.getElementById('inputHeight');
const gridWidth = document.getElementById('inputWidth');

let height = gridHeight.value;
let width = gridWidth.value;

gridHeight.addEventListener("input", function() {
  height = document.getElementById('inputHeight').value;
})

gridWidth.addEventListener("input", function() {
  width = document.getElementById('inputWidth').value;
})

const table = document.getElementById('pixelCanvas');

function createCanvas(event) {
  event.preventDefault();
  table.innerHTML = '';
  for (let h = 1; h <= height; h++) {
    const row = document.createElement('tr');

    for (let w = 1; w <= width; w++) {
      const cell = document.createElement('td');
      row.appendChild(cell);
    }
    table.appendChild(row);
  }
}

const form = document.querySelector('form');

// bind createCanvas() to "submit"
form.addEventListener('submit', createCanvas);

// event listener to update color
const picker = document.getElementById('colorPicker')
let color = picker.value;

picker.onchange = function() {
  color = this.value;
}

// function activated when user click on only

function respondToClick(event) {
  if (event.target.nodeName.toLowerCase() === 'td') {
    event.target.style.backgroundColor = color;
  }
}

table.addEventListener("click", respondToClick);
table {
  border: 1px solid black;
 }
 
 td{
 width:15px;
 height:15px;
 overflow:hidden;
 }
<h1>Pixel Art</h1>

<h2>Choose Grid Size</h2>
<form id="sizePicker">
  Grid Height:
  <input type="number" id="inputHeight" name="height" min="1" value="1"> Grid Width:
  <input type="number" id="inputWidth" name="width" min="1" value="1">
  <input type="submit">
</form>

<h2>Pick A Color</h2>
<input type="color" id="colorPicker">

<h2>Design Canvas</h2>
<table id="pixelCanvas" cellspacing="0" cellpadding="0"></table>
0
votes

Following are the problems in your code:

  • document.getElementsByTagName(...) returns a HTMLCollection on which you can't call .forEach() method. Instead use document.querySelectorAll(...) which returns a NodeList which has a method named .forEach().

    const tblRow = document.querySelectorAll('tr');
    
  • You need to prevent the default behaviour of the submit event using Event.preventDefault()

    function createCanvas(event) {
       event.preventDefault();    
       ...
    }
    
  • Since you had called .forEach() method on the HTMLCollection returned by document.getElementsByTagName('tr'), there was an error and no event listener was added on any of the tr element.

    Instead of adding click event listener on each tr element, you can take advantage of Event Bubbling and just add the event listener on the table element.

    table.addEventListener("click", respondToClick);
    

Following code snippet shows the fixed code example:

let height = document.getElementById('inputHeight').value;
let width = document.getElementById('inputWidth').value;
const gridHeight = document.getElementById('inputHeight');
const gridWidth = document.getElementById('inputWidth');
const table = document.getElementById('pixelCanvas');
const form = document.querySelector('form');
const colorPicker = document.getElementById('colorPicker');
let color = colorPicker.value;

gridHeight.addEventListener("input", function() {
  height = document.getElementById('inputHeight').value;
})

gridWidth.addEventListener("input", function() {
  width = document.getElementById('inputWidth').value;
})

function createCanvas(event) {
  event.preventDefault();

  for (let h = 1; h <= height; h++) {
    const row = document.createElement('tr');

    for (let w = 1; w <= width; w++) {
      const cell = document.createElement('td');
      row.appendChild(cell);
    }

    table.appendChild(row);
  }
}

form.addEventListener('submit', createCanvas);

colorPicker.addEventListener('change', function() {
  color = this.value;
});

function respondToClick(event) {
  if (event.target.matches('td')) {
    event.target.style.backgroundColor = color;
  }
}

table.addEventListener("click", respondToClick);
table,
tr,
td {
  border: 1px solid;
  border-collapse: collapse;
  ;
}

td {
  width: 30px;
  height: 30px;
}
<h1>Pixel Art</h1>

<h2>Choose Grid Size</h2>
<form id="sizePicker">
  Grid Height:
  <input type="number" id="inputHeight" name="height" min="1" value="1" /> Grid Width:
  <input type="number" id="inputWidth" name="width" min="1" value="1" />
  <input type="submit" />
</form>

<h2>Pick A Color</h2>
<input type="color" id="colorPicker" />

<h2>Design Canvas</h2>
<table id="pixelCanvas"></table>

Side Note: Inside the responseToClick() event handler, you have the following line of code:

cell.style.cssText = "height: 15px; width: 15px";

Although this works, it is better to define these styles in the CSS. I have done the same in the above code snippet.

Another point that should be noted is that after fixing the code as shown in the above code snippet, you might want to remove the previously created table contents before creating a new one. If this is what you want to do, then you can do this by setting the .innerHTML of the table element to an empty string inside the createCanvas() event handler.