Skip to content

Lab 05: HTML DOM Manipulation using JavaScript

These rather challenging lab activities exposes how functions can be used within JavaScript, and how it can interact with web elements in the HTML Document Object Model (DOM). Should you require more guidance, there are plenty of online tutorials detailing multiple solutions - it's best if you can explore and test them out to the best of your ability.

Getting Started

HTML DOM

The HTML Document Object Model (DOM) is a standard object model and programming interface for HTML. It defines:

  • The HTML elements as objects
  • The properties of all HTML elements
  • The methods to access all HTML elements
  • The events for all HTML elements

Combining JavaScript with HTML DOM enable dynamic access and change all the elements of an HTML document.

Activity 1: Multiplication Table

School teachers are often advised to look for creative methods into their lessons in order to help engage with students better or help students learn concepts easier with different methods. The multiplication box is one of many creative methods concocted by elementary school math teachers to help students understand multiplication between two-digit numbers (i.e., integer values).

Preparation

Start with the following code templates.

Code Templates
css/style.css
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap");

:root {
  --table-cell-dimension: 100px;
  --text-size: 32px;
}

* {
  font-family: "Plus Jakarta Sans", sans-serif;
}

body {
  padding-top: 2rem;
  text-align: center;
}

h1 {
  font-size: 48px;
}

#num_entry,
#num_entry input[type="number"] {
  font-size: var(--text-size);
  margin-bottom: 1rem;
}

#num_entry input[type="number"] {
  text-align: center;
  width: var(--table-cell-dimension);
}

input[type="number"]:disabled {
  background-color: white;
  border: 2px solid black;
  cursor: not-allowed;
}

/* For Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* For Firefox */
input[type="number"] {
  -moz-appearance: textfield;
}

input[type="button"] {
  font-size: 20px;
  padding: 0.5rem 1rem;
}

hr {
  margin: 2rem 0;
}

table#multiplication-box {
  /* border: 1px solid black; */
  border-collapse: collapse;
  display: inline-block;
  margin-left: auto;
}

table#multiplication-box th,
table#multiplication-box td:not(.td-hidden) {
  border: 1px solid black;
  font-size: var(--text-size);
  height: var(--table-cell-dimension);
  width: var(--table-cell-dimension);
}
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Multiplication Box</title>

    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <h1>Multiplication Box</h1>

    <p>Multiplies 2-digit numbers together.</p>

    <section id="num_entry">
      <input type="number" name="num1" id="num1" min="10" max="99" />
      &times;
      <input type="number" name="num2" id="num2" min="10" max="99" />
      =
      <input
        type="number"
        name="answer"
        id="numAns"
        min="10"
        max="99"
        readonly
      />
    </section>
    <!-- /section#num_entry -->

    <input type="button" value="Multiply" onclick="multiply();" />

    <hr />

    <table id="multiplication-box">
      <tr>
        <td class="td-hidden"></td>
        <td class="td-hidden"></td>
        <th id="num1_show" colspan="2"></th>
      </tr>
      <tr>
        <td class="td-hidden"></td>
        <th>&times;</th>
        <th id="num1a"></th>
        <th id="num1b"></th>
      </tr>
      <tr>
        <th id="num2_show" rowspan="2"></th>
        <th id="num2a"></th>
        <td id="m1"></td>
        <td id="m2"></td>
        <td id="m1_m2"></td>
      </tr>
      <tr>
        <th id="num2b"></th>
        <td id="m3"></td>
        <td id="m4"></td>
        <td id="m3_m4"></td>
      </tr>
      <tr>
        <td class="td-hidden" colspan="2"></td>
        <td id="m1_m3"></td>
        <td id="m2_m4"></td>
        <th id="m_total"></th>
      </tr>
    </table>

    <!-- /table#multiplication-box -->

    <script src="js/times-box.js"></script>
  </body>
</html>

You should be able to see the following in your web browser:

Multiplication Box Page

Multiplication Box Page

This method using the multiplication box involves separating both numbers into tens and units, and then multiplying them separately before adding their horizontal and vertical totals up. The cell at the bottom right denotes the eventual product between the two numbers. For instance, with \(49 \times 16\), both numbers are separated as the pairs (40, 9) and (10, 6) respectively. The following multiplication operations are then carried out first:

  • \(40 \times 10 = 400\)
  • \(40 \times 6 = 240\)
  • \(9 \times 10 = 90\)
  • \(9 \times 6 = 54\)

The numbers are populated as shown in the figure below, where their horizontal totals and vertical totals are calculated and displayed. Between both sets of totals, they produce the same correct product after being added together (i.e., \(490 + 294 = 640 + 144 = 784\)).

Multiplication Box Page

Multiplication Box Page

Take note that the only input fields of concern are the three which make up the equation on top of the multiply button, which is associated with the JavaScript multiply() function we will be completing in this activity. Only the first two are allowed to be populated manually by the user. The third representing the calculated product is set to be read-only.

Step 1: Obtaining Values from Input Fields

Let's work on obtaining the values entered into the first two input fields. The first two lines in the multiply() function is used to obtain the values from input fields input#num1 and input#num2.

times-box.js
1
2
3
4
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);
}
Using .innerHTML or .value When Retrieving Values

TL;DR: In nearly all cases, .value is only used with <input> whilst .innerHTML is for getting values from any non-self-closing tags.

Let's take a <p> element, for example.

<p>
    Content/text/etc. inside here
</p>

The content placed in between the opening <p> and closing </p> tags is what we refer to as inner HTML. Therefore, we use .innerHTML to obtain values that are stored in between similarly structured HTML tags.

document.querySelector("p").innerHTML

However, when it comes to <input> elements, these are represented with self-closing tags. Thus, there isn't really any inner HTML to target. <input> elements do have the a value attribute that contains entered text, though. In this case, it only makes sense to use .value to elicit values out of <input> fields.

document.querySelector("input").innerHTML   // does not work
document.querySelector("input").value // use this for <input> elements instead

Here, we observe that the parseInt() function is applied surrounding the expression used to obtain the value from input#num1 and input#num2. This function ensures that an integer is obtained from the <input> elements, as the value obtained from them typically defaults to being of a "string" type.

Before we proceed to use these values we've entered, we're going to implement a few checks to see if the numbers entered are two-digit integers. This means that:

  • none of the two input fields (i.e., input#num1 and input#num2) should be empty
  • no non-integer values or any value outside of the 10-99 range can be entered

The first criterion is pretty straightforward - we check to see if the value of input#num1 and/or input#num2 results in an empty string (i.e., "").

To implement the second criterion, we'll be using the sister variant function of parseInt() called parseFloat(). While parseInt() obtains an integer from the passed in string (in this case the value of the <input> field), parseFloat() obtains any number (with decimal points) from the passed in string. In summary,

Function "17" "17.5" "17as" "17.5as" "as17" "asap"
parseInt() 17 17 17 17 NaN Nan
parseFloat() 17 17.5 17 17.5 NaN Nan

From the table above, we can probably tell that parseFloat("17.5") !== parseInt("17.5"). We use this idea here to determine if the input number in either input#num1 or input#num2 or both are not integers, and then we proceed to replace them with the integer obtained after using the parseInt() function.

For both criteria, we use an alert box to alert the user of any problems found.

times-box.js
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);

  // replace num1 and num2 with parsed integer (if not integers)
  // alert user when number is replaced
  if (
    parseFloat(document.querySelector("#num1").value) !==
    parseInt(document.querySelector("#num1").value)
  ) {
    if (document.querySelector("#num1").value == "") {
      alert("First number input is empty.");
    } else {
      alert(
        "First number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num1").value = num1;
    }
  }
  if (
    parseFloat(document.querySelector("#num2").value) !==
    parseInt(document.querySelector("#num2").value)
  ) {
    if (document.querySelector("#num2").value == "") {
      alert("Second number input is empty.");
    } else {
      alert(
        "Second number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num2").value = num2;
    }
  }
}

Multiplication Box Page

Alert box pop-up if first number has not been entered.

Multiplication Box Page

Alert box pop-up if second number has not been entered.

Multiplication Box Page

Alert box pop-up if first number is not an integer.

Multiplication Box Page

Alert box pop-up if second number is not an integer.

Step 2: Checking Validity of Input Integers

Now, onward to checking whether the input integers (i.e., num1 and num2) are within the correct range. A two-digit integer can only be either one of the numbers between 10 and 99; the rest of which simply will not fit. For the sake of simplicity, we will only consider positive values.

We implement this check using a compound condition of 4 conditions to be met simultaneously:

  • num1 >= 10
  • num1 <= 99
  • num2 >= 10
  • num2 <= 99

If all four of these conditions are met, we proceed to display num1 and num2 in td#num1_show and td#num2_show respectively. We will expand on the further logic to be implemented from here.

Otherwise, we check to see either num1 < 10 || num1 > 99 or num2 < 10 || num2 > 99, and display an appropriate error message in an alert box.

times-box.js
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);

  // replace num1 and num2 with parsed integer (if not integers)
  // alert user when number is replaced
  if (
    parseFloat(document.querySelector("#num1").value) !==
    parseInt(document.querySelector("#num1").value)
  ) {
    if (document.querySelector("#num1").value == "") {
      alert("First number input is empty.");
    } else {
      alert(
        "First number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num1").value = num1;
    }
  }
  if (
    parseFloat(document.querySelector("#num2").value) !==
    parseInt(document.querySelector("#num2").value)
  ) {
    if (document.querySelector("#num2").value == "") {
      alert("Second number input is empty.");
    } else {
      alert(
        "Second number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num2").value = num2;
    }
  }

  // check if num1 and num2 is between 10 and 99 (inclusive)
  if (num1 >= 10 && num1 <= 99 && num2 >= 10 && num2 <= 99) {
    // show number in num?_show
    document.querySelector("#num1_show").innerHTML = num1;
    document.querySelector("#num2_show").innerHTML = num2;

    // to continue more here
  } else {
    // if first number is not between 10 and 99 (inclusive)
    if (num1 < 10 || num1 > 99) {
      alert("First number should be between 10 and 99!");
      document.querySelector("#num1").value = ""; // empty first input field
    }

    // if secondnumber is not between 10 and 99 (inclusive)
    if (num2 < 10 || num2 > 99) {
      alert("Second number should be between 10 and 99!");
      document.querySelector("#num2").value = ""; // empty second input field
    }
  }
}

Multiplication Box Page

Alert box pop-up if first number is not between 10 and 99 (inclusive).

Multiplication Box Page

Alert box pop-up if second number is not between 10 and 99 (inclusive).

Multiplication Box Page

Otherwise, your multiplication table should start being populated from here.

Step 3: Populating the Table: Tens and Units Split

The key ideas behind using the multiplication box/table as shown here is to split the two-digit numbers into the tens and units places. In order to elicit the tens digit from the number, we simply obtain the quotient after dividing it by 10. This quotient is then passed into one of the Math functions, namely the floor() function which rounds down a number to the lower boundary (e.g., 3.4 to 3, and 4.9 to 4). The units digit is obtained simply by using the modulo operator on the number; basically, what the remainder is after dividing the number by 10. These values are then stored contiguously in an array for each number (e.g., \(49 \times 16\) produces [4, 9] and [1, 6]). Each of these digit are then placed in the respective cells: td#num1a, td#num1b, td#num2a, td#num2b.

times-box.js
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);

  // replace num1 and num2 with parsed integer (if not integers)
  // alert user when number is replaced
  if (
    parseFloat(document.querySelector("#num1").value) !==
    parseInt(document.querySelector("#num1").value)
  ) {
    if (document.querySelector("#num1").value == "") {
      alert("First number input is empty.");
    } else {
      alert(
        "First number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num1").value = num1;
    }
  }
  if (
    parseFloat(document.querySelector("#num2").value) !==
    parseInt(document.querySelector("#num2").value)
  ) {
    if (document.querySelector("#num2").value == "") {
      alert("Second number input is empty.");
    } else {
      alert(
        "Second number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num2").value = num2;
    }
  }

  // check if num1 and num2 is between 10 and 99 (inclusive)
  if (num1 >= 10 && num1 <= 99 && num2 >= 10 && num2 <= 99) {
    // show number in num?_show
    document.querySelector("#num1_show").innerHTML = num1;
    document.querySelector("#num2_show").innerHTML = num2;

    // split number into digits based on tens and units value
    let arr_num1 = [Math.floor(num1 / 10) * 10, num1 % 10];
    let arr_num2 = [Math.floor(num2 / 10) * 10, num2 % 10];

    // show digits in num?a or num?b
    document.querySelector("#num1a").innerHTML = arr_num1[0];
    document.querySelector("#num1b").innerHTML = arr_num1[1];
    document.querySelector("#num2a").innerHTML = arr_num2[0];
    document.querySelector("#num2b").innerHTML = arr_num2[1];
    // to continue more here
  } else {
    // if first number is not between 10 and 99 (inclusive)
    if (num1 < 10 || num1 > 99) {
      alert("First number should be between 10 and 99!");
      document.querySelector("#num1").value = ""; // empty first input field
    }

    // if secondnumber is not between 10 and 99 (inclusive)
    if (num2 < 10 || num2 > 99) {
      alert("Second number should be between 10 and 99!");
      document.querySelector("#num2").value = ""; // empty second input field
    }
  }
}

Multiplication Box Page

Your function should now produce this result after clicking the button.

Step 4: Populating the Table: Calculating Intermediary Products

By splitting both numbers into the tens and units digits, we can now carry out the following intermediary multiplications operations (taking \(49 \times 16\) for example):

  • \(40 \times 10 = 400\)
  • \(40 \times 6 = 240\)
  • \(9 \times 10 = 90\)
  • \(9 \times 6 = 54\)

Each of these products are placed like as shown in the following output.

times-box.js
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);

  // replace num1 and num2 with parsed integer (if not integers)
  // alert user when number is replaced
  if (
    parseFloat(document.querySelector("#num1").value) !==
    parseInt(document.querySelector("#num1").value)
  ) {
    if (document.querySelector("#num1").value == "") {
      alert("First number input is empty.");
    } else {
      alert(
        "First number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num1").value = num1;
    }
  }
  if (
    parseFloat(document.querySelector("#num2").value) !==
    parseInt(document.querySelector("#num2").value)
  ) {
    if (document.querySelector("#num2").value == "") {
      alert("Second number input is empty.");
    } else {
      alert(
        "Second number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num2").value = num2;
    }
  }

  // check if num1 and num2 is between 10 and 99 (inclusive)
  if (num1 >= 10 && num1 <= 99 && num2 >= 10 && num2 <= 99) {
    // show number in num?_show
    document.querySelector("#num1_show").innerHTML = num1;
    document.querySelector("#num2_show").innerHTML = num2;

    // split number into digits based on tens and units value
    let arr_num1 = [Math.floor(num1 / 10) * 10, num1 % 10];
    let arr_num2 = [Math.floor(num2 / 10) * 10, num2 % 10];

    // show digits in num?a or num?b
    document.querySelector("#num1a").innerHTML = arr_num1[0];
    document.querySelector("#num1b").innerHTML = arr_num1[1];
    document.querySelector("#num2a").innerHTML = arr_num2[0];
    document.querySelector("#num2b").innerHTML = arr_num2[1];

    /**
     * Calculate intermediary multiplication values
     *
     * Key:
     * m1 = num1a * num2a
     * m2 = num1b * num2a
     * m3 = num1a * num2b
     * m4 = num1b * num2b
     */
    const m1 = arr_num1[0] * arr_num2[0];
    const m2 = arr_num1[1] * arr_num2[0];
    const m3 = arr_num1[0] * arr_num2[1];
    const m4 = arr_num1[1] * arr_num2[1];

    // Display intermediary multiplication values
    document.querySelector("#m1").innerHTML = m1;
    document.querySelector("#m2").innerHTML = m2;
    document.querySelector("#m3").innerHTML = m3;
    document.querySelector("#m4").innerHTML = m4;
    // to continue more here
  } else {
    // if first number is not between 10 and 99 (inclusive)
    if (num1 < 10 || num1 > 99) {
      alert("First number should be between 10 and 99!");
      document.querySelector("#num1").value = ""; // empty first input field
    }

    // if secondnumber is not between 10 and 99 (inclusive)
    if (num2 < 10 || num2 > 99) {
      alert("Second number should be between 10 and 99!");
      document.querySelector("#num2").value = ""; // empty second input field
    }
  }
}

Multiplication Box Page

Each of the four separate products should now populate the center table cells.

Step 5: Populating the Table: Obtaining Horizontal and Vertical Totals

Now, we focus on getting the horizontal and vertical totals of the intermediary products. The cell containing the sum across

  • the top row (horizontal) is denoted as #m1_m2
  • the bottom row (horizontal) is denoted as #m3_m4
  • the left column (vertical) is denoted as #m1_m3
  • the right column (vertical) is denoted as #m2_m4
times-box.js
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);

  // replace num1 and num2 with parsed integer (if not integers)
  // alert user when number is replaced
  if (
    parseFloat(document.querySelector("#num1").value) !==
    parseInt(document.querySelector("#num1").value)
  ) {
    if (document.querySelector("#num1").value == "") {
      alert("First number input is empty.");
    } else {
      alert(
        "First number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num1").value = num1;
    }
  }
  if (
    parseFloat(document.querySelector("#num2").value) !==
    parseInt(document.querySelector("#num2").value)
  ) {
    if (document.querySelector("#num2").value == "") {
      alert("Second number input is empty.");
    } else {
      alert(
        "Second number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num2").value = num2;
    }
  }

  // check if num1 and num2 is between 10 and 99 (inclusive)
  if (num1 >= 10 && num1 <= 99 && num2 >= 10 && num2 <= 99) {
    // show number in num?_show
    document.querySelector("#num1_show").innerHTML = num1;
    document.querySelector("#num2_show").innerHTML = num2;

    // split number into digits based on tens and units value
    let arr_num1 = [Math.floor(num1 / 10) * 10, num1 % 10];
    let arr_num2 = [Math.floor(num2 / 10) * 10, num2 % 10];

    // show digits in num?a or num?b
    document.querySelector("#num1a").innerHTML = arr_num1[0];
    document.querySelector("#num1b").innerHTML = arr_num1[1];
    document.querySelector("#num2a").innerHTML = arr_num2[0];
    document.querySelector("#num2b").innerHTML = arr_num2[1];

    /**
     * Calculate intermediary multiplication values
     *
     * Key:
     * m1 = num1a * num2a
     * m2 = num1b * num2a
     * m3 = num1a * num2b
     * m4 = num1b * num2b
     */
    const m1 = arr_num1[0] * arr_num2[0];
    const m2 = arr_num1[1] * arr_num2[0];
    const m3 = arr_num1[0] * arr_num2[1];
    const m4 = arr_num1[1] * arr_num2[1];

    // Display intermediary multiplication values
    document.querySelector("#m1").innerHTML = m1;
    document.querySelector("#m2").innerHTML = m2;
    document.querySelector("#m3").innerHTML = m3;
    document.querySelector("#m4").innerHTML = m4;

    // add horizontal row values and display the sums
    document.querySelector("#m1_m2").innerHTML = m1 + m2;
    document.querySelector("#m3_m4").innerHTML = m3 + m4;

    // add vertical column values and display the sums
    document.querySelector("#m1_m3").innerHTML = m1 + m3;
    document.querySelector("#m2_m4").innerHTML = m2 + m4;
    // to continue more here
  } else {
    // if first number is not between 10 and 99 (inclusive)
    if (num1 < 10 || num1 > 99) {
      alert("First number should be between 10 and 99!");
      document.querySelector("#num1").value = ""; // empty first input field
    }

    // if secondnumber is not between 10 and 99 (inclusive)
    if (num2 < 10 || num2 > 99) {
      alert("Second number should be between 10 and 99!");
      document.querySelector("#num2").value = ""; // empty second input field
    }
  }
}

Multiplication Box Page

The table should now contain the horizontal and vertical totals as shown.

Step 6: Retrieving and Displaying the Final Product

Finally, we get to displaying the final product from multiplying the integers in the input fields. The vertical sum between values in cells #m1_m2 and #m3_m4 or #m1_m3 and #m2_m4 will suffice in this case.

We first store this sum in a variable first before displaying them in the table cell #m_total and the #numAns input field.

times-box.js
function multiply() {
  let num1 = parseInt(document.querySelector("#num1").value);
  let num2 = parseInt(document.querySelector("#num2").value);

  // replace num1 and num2 with parsed integer (if not integers)
  // alert user when number is replaced
  if (
    parseFloat(document.querySelector("#num1").value) !==
    parseInt(document.querySelector("#num1").value)
  ) {
    if (document.querySelector("#num1").value == "") {
      alert("First number input is empty.");
    } else {
      alert(
        "First number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num1").value = num1;
    }
  }
  if (
    parseFloat(document.querySelector("#num2").value) !==
    parseInt(document.querySelector("#num2").value)
  ) {
    if (document.querySelector("#num2").value == "") {
      alert("Second number input is empty.");
    } else {
      alert(
        "Second number isn't an integer. Replacing value at input field with parsed integer value instead.",
      );
      document.querySelector("#num2").value = num2;
    }
  }

  // check if num1 and num2 is between 10 and 99 (inclusive)
  if (num1 >= 10 && num1 <= 99 && num2 >= 10 && num2 <= 99) {
    // show number in num?_show
    document.querySelector("#num1_show").innerHTML = num1;
    document.querySelector("#num2_show").innerHTML = num2;

    // split number into digits based on tens and units value
    let arr_num1 = [Math.floor(num1 / 10) * 10, num1 % 10];
    let arr_num2 = [Math.floor(num2 / 10) * 10, num2 % 10];

    // show digits in num?a or num?b
    document.querySelector("#num1a").innerHTML = arr_num1[0];
    document.querySelector("#num1b").innerHTML = arr_num1[1];
    document.querySelector("#num2a").innerHTML = arr_num2[0];
    document.querySelector("#num2b").innerHTML = arr_num2[1];

    /**
     * Calculate intermediary multiplication values
     *
     * Key:
     * m1 = num1a * num2a
     * m2 = num1b * num2a
     * m3 = num1a * num2b
     * m4 = num1b * num2b
     */
    const m1 = arr_num1[0] * arr_num2[0];
    const m2 = arr_num1[1] * arr_num2[0];
    const m3 = arr_num1[0] * arr_num2[1];
    const m4 = arr_num1[1] * arr_num2[1];

    // Display intermediary multiplication values
    document.querySelector("#m1").innerHTML = m1;
    document.querySelector("#m2").innerHTML = m2;
    document.querySelector("#m3").innerHTML = m3;
    document.querySelector("#m4").innerHTML = m4;

    // add horizontal row values and display the sums
    document.querySelector("#m1_m2").innerHTML = m1 + m2;
    document.querySelector("#m3_m4").innerHTML = m3 + m4;

    // add vertical column values and display the sums
    document.querySelector("#m1_m3").innerHTML = m1 + m3;
    document.querySelector("#m2_m4").innerHTML = m2 + m4;

    // calculate product
    const product =
      parseInt(document.querySelector("#m1_m2").innerHTML) +
      parseInt(document.querySelector("#m3_m4").innerHTML);
    // or
    // const product =
    //  parseInt(document.querySelector("#m1_m3").innerHTML) + parseInt(document.querySelector("#m2_m4").innerHTML);

    // display full product in table
    document.querySelector("#m_total").innerHTML = product;

    // display full product in answer input field
    document.querySelector("#numAns").value = product;
  } else {
    // if first number is not between 10 and 99 (inclusive)
    if (num1 < 10 || num1 > 99) {
      alert("First number should be between 10 and 99!");
      document.querySelector("#num1").value = ""; // empty first input field
    }

    // if secondnumber is not between 10 and 99 (inclusive)
    if (num2 < 10 || num2 > 99) {
      alert("Second number should be between 10 and 99!");
      document.querySelector("#num2").value = ""; // empty second input field
    }
  }
}

Multiplication Box Page

And.. it's complete. Well done!

And voila, we've just created a replica of the multiplication box/table used by some teachers to help teach children about multiplication between two-digit numbers!

Challenge

Try to recreate the same table, but dealing with multiplication between three-digit integers instead of two.

Activity 2: Mark Demo

This intermediate-level activity will provide a guide as to how you can effectively use program control structures to skillfully loop through the same calculation process with different sets of inputs at a time.

Preparation

You'll require the following font file to be kept in a subfolder called fonts in your project directory: SF Pro Display

Additionally, CSS and JS files are to be kept in their own separate subfolders.

Code Templates
style.css
@font-face {
  font-family: "SF Pro Display";
  src: url("../fonts/SF-Pro-Display-Regular.otf");
}

* {
  font-family: "SF Pro Display", sans-serif;
}

h1 {
  font-size: 48px;
}

hr {
  margin: 2rem 0;
}

button {
  background-color: transparent;
  border-radius: 1rem;
  font-size: 24px;
  padding: 0.5rem 1rem;
}

button:hover {
  box-shadow:
    0 0 1rem yellowgreen,
    0 0 5rem lemonchiffon;
  cursor: pointer;
}

button:active {
  background-color: lemonchiffon;
}

.text-center {
  text-align: center;
}
style_input.css
input[type="number"] {
  text-align: right;
}

input:read-only {
  background-color: khaki;
}

/* For Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* For Firefox */
input[type="number"] {
  -moz-appearance: textfield;
}
style_table.css
table {
  border-collapse: collapse;
  margin: 0 auto;
}

table * {
  font-size: 24px;
}

th,
td {
  border: 1px solid black;
  padding: 0.5rem 1rem;
}

th {
  background-color: lightcyan;
}

tr:not(:first-of-type):not(:last-of-type):hover {
  background-color: greenyellow;
}
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mark Demo</title>

    <link rel="stylesheet" href="css/style.css" />
    <link rel="stylesheet" href="css/style_input.css" />
    <link rel="stylesheet" href="css/style_table.css" />
  </head>
  <body>
    <section class="text-center">
      <h1>Mark Demo</h1>
    </section>
    <!-- /section.text-center -->

    <table>
      <tr class="text-center">
        <th>#</th>
        <th>Asgn 1 [100%]</th>
        <th>Asgn 2 [100%]</th>
        <th>Asgn 1 [20 points]</th>
        <th>Asgn 2 [30 points]</th>
        <th>Total [50 points]</th>
      </tr>
      <tr>
        <th>1</th>
        <td><input type="number" name="stu1_asgn1" id="stu1_asgn1" /></td>
        <td><input type="number" name="stu1_asgn2" id="stu1_asgn2" /></td>
        <td>
          <input
            type="number"
            name="stu1_asgn1_points"
            id="stu1_asgn1_points"
            readonly
          />
        </td>
        <td>
          <input
            type="number"
            name="stu1_asgn2_points"
            id="stu1_asgn2_points"
            readonly
          />
        </td>
        <td>
          <input
            type="number"
            name="stu1_total_points"
            id="stu1_total_points"
            readonly
          />
        </td>
      </tr>
      <tr>
        <th>2</th>
        <td><input type="number" name="stu2_asgn1" id="stu2_asgn1" /></td>
        <td><input type="number" name="stu2_asgn2" id="stu2_asgn2" /></td>
        <td>
          <input
            type="number"
            name="stu2_asgn1_points"
            id="stu2_asgn1_points"
            readonly
          />
        </td>
        <td>
          <input
            type="number"
            name="stu2_asgn2_points"
            id="stu2_asgn2_points"
            readonly
          />
        </td>
        <td>
          <input
            type="number"
            name="stu2_total_points"
            id="stu2_total_points"
            readonly
          />
        </td>
      </tr>
      <tr>
        <th>3</th>
        <td><input type="number" name="stu3_asgn1" id="stu3_asgn1" /></td>
        <td><input type="number" name="stu3_asgn2" id="stu3_asgn2" /></td>
        <td>
          <input
            type="number"
            name="stu3_asgn1_points"
            id="stu3_asgn1_points"
            readonly
          />
        </td>
        <td>
          <input
            type="number"
            name="stu3_asgn2_points"
            id="stu3_asgn2_points"
            readonly
          />
        </td>
        <td>
          <input
            type="number"
            name="stu3_total_points"
            id="stu3_total_points"
            readonly
          />
        </td>
      </tr>
      <tr>
        <th colspan="3">Average</th>
        <th><input type="number" name="avg1" id="avg1" readonly /></th>
        <th><input type="number" name="avg2" id="avg2" readonly /></th>
        <th>
          <input type="number" name="avg_total" id="avg_total" readonly />
        </th>
      </tr>
    </table>

    <!-- /table -->

    <hr />

    <section class="text-center">
      <button onclick="calculate();">Calculate Total and Average</button>
      <button onclick="clearTable();">Clear Table</button>
    </section>
    <!-- /section.text-center -->

    <script src="js/script.js"></script>
  </body>
</html>
script.js
function calculate() {
  // Variables to contain total values
  let asgn1_total = 0,
    asgn2_total = 0,
    grand_total = 0;

  // We'll use a constant to referece the <table> element to try to shorten the code.
  const TABLE = document.querySelector("table");

  // Calculate number of rows in between top and bottom row
  const NUMBER_OF_STUDENTS = TABLE.rows.length - 2;

  // we will be adding code from here
}

function clearTable() {
  // Initialize Asgn 1 & Asgn 2 (100% each) to 0
  document
    .querySelectorAll(`input:not(:read-only)`)
    .forEach((inputField) => (inputField.value = "0"));
  // Initialize Asgn & Total Points to 0.00
  document
    .querySelectorAll(`input:read-only`)
    .forEach((inputField) => (inputField.value = "0.00"));
}

window.onload = clearTable();

You should be able to see the following in your web browser:

Mark Demo Page

Mark Demo Page

Here, each row represents a student's record. Each student would have had 2 assessment components graded on a 0 to 100 percent scale. When the button at the bottom of the page (i.e., Calculate Total and Average) is selected, each student's Assignment 1 and 2's respective point weightage is calculated based on the input marks. This is followed up by the calculation of points obtained by each student, along with the cumulative average of each point component.

The button has an onclick attribute that invokes the calculate() function when clicked on. Another button to clear the table is present alongside while having its functionality tied in with the clearTable() function.

Understanding the clearTable() Function

The clearTable() function contains two statements. The first statement targets all input fields not set to read-only to "0", whilst the second statement sets the remaining input fields' value attribute to "0.00". Both statements use the forEach() method, but a regular for loop will also suffice here. The forEach() method retrieves each related input field and autonomously sets their value attributes accordingly.

Step 1: Obtaining Values from Input Fields

The starting point in script.js should give some light onto the function of concern: the calculate() function. At first, we initialize variables asgn1_total, asgn2_total and grand_total to contain the total sum of all points. We will revisit them later, but for now we initialize all of them with the value 0.

We will also make reference to the <table> element a lot here. In order to shorten the code as to simplify the process, we declare a constant variable named TABLE referencing this <table> element. In addition, we refer to the number of student records to be put in as TABLE.rows.length - 2. Of course, we can immediately take it as just 3 since there isn't any functionality to add more rows that'll question whether the number of records will stay put. Not only that, the way you calculate the number of students will depend on whether you semantic tags within your <table> element; if so, the value of NUMBER_OF_STUDENTS constant should be calculated differently.

script.js
function calculate() {
  // Variables to contain total values
  let asgn1_total = 0,
    asgn2_total = 0,
    grand_total = 0;

  // We'll use a constant to referece the <table> element to try to shorten the code.
  const TABLE = document.querySelector("table");

  // Calculate number of rows in between top and bottom row
  const NUMBER_OF_STUDENTS = TABLE.rows.length - 2;

  // we will be adding code from here
}

function clearTable() {
  // Initialize Asgn 1 & Asgn 2 (100% each) to 0
  document
    .querySelectorAll(`input:not(:read-only)`)
    .forEach((inputField) => (inputField.value = "0"));
  // Initialize Asgn & Total Points to 0.00
  document
    .querySelectorAll(`input:read-only`)
    .forEach((inputField) => (inputField.value = "0.00"));
}

window.onload = clearTable();

Step 2: Catering for Unfilled Input Fields

Now here's the bit that would take many lines if done without any program control structures in place. We mitigate needing to write numerous lines of code by introducing a for loop that will iterate over each row while obtaining values from each of its associated input fields. The id attribute values for each input field referencing the total marks (100%) for each student is named in a fashion similar to this: #stu${i}_asgn1 and #stu${i}_asgn2. More specifically, you will find input fields with the following id attribute values:

  • Student 1: #stu1_asgn1, #stu1_asgn2
  • Student 2: #stu2_asgn1, #stu2_asgn2
  • Student 3: #stu3_asgn1, #stu3_asgn2

Notice a pattern in how the id attribute values are aptly named? By iterating an iterator variable (we'll be using i here), we can run this in a for loop to write out each required statement once!

The first order of action in this for loop is to check whether there are any unfilled input fields in any of the six input fields with the aforementioned id attribute values. Should there be any, they will be automatically filled with "0". This will help prevent errors during the calculation of total and average points.

script.js
function calculate() {
  // Variables to contain total values
  let asgn1_total = 0,
    asgn2_total = 0,
    grand_total = 0;

  // We'll use a constant to referece the <table> element to try to shorten the code.
  const TABLE = document.querySelector("table");

  // Calculate number of rows in between top and bottom row
  const NUMBER_OF_STUDENTS = TABLE.rows.length - 2;

  for (let i = 1; i <= NUMBER_OF_STUDENTS; ++i) {
    /**
     * Check if #stu?_asgn1 and #stu?_asgn2 are empty
     * If empty, place in 0 values.
     */
    if (document.querySelector(`#stu${i}_asgn1`).value == "") {
      document.querySelector(`#stu${i}_asgn1`).value = "0";
    }
    if (document.querySelector(`#stu${i}_asgn2`).value == "") {
      document.querySelector(`#stu${i}_asgn2`).value = "0";
    }

    // we will be adding code from here
  }
}

function clearTable() {
  // Initialize Asgn 1 & Asgn 2 (100% each) to 0
  document
    .querySelectorAll(`input:not(:read-only)`)
    .forEach((inputField) => (inputField.value = "0"));
  // Initialize Asgn & Total Points to 0.00
  document
    .querySelectorAll(`input:read-only`)
    .forEach((inputField) => (inputField.value = "0.00"));
}

window.onload = clearTable();

Negative Value for Marks

The procedure above doesn't cater for negative marks. Assuming that the marks entered should be nonnegative, how would you go about catering for this requirement?

Step 3: Generating Total Points

Now we get to calculating the total points obtained from the entered Assignment 1 and 2 marks. From the table, we can probably guess that the total points obtained for the

  • Assignment 1 component is to be scaled from 100% to 20 points (i.e., multiply by 0.2)
  • Assignment 2 component is to be scaled from 100% to 30 points (i.e., multiply by 0.3)

During each for loop iteration, we store the points obtained for Assignment 1 and Assignment 2 into asgn1_points and asgn2_points respectively. Then from here, we display them in #stu${i}_asgn1_points and #stu${i}_asgn2_points, where i still represents the iteration number/row count/student count. These point values are displayed in 2 decimal places using an appended .toFixed() function, which takes in 2 as the parameter denoting the number of decimal places to be shown.

After both asgn1_points and asgn2_points have been calculated, we proceed with adding both of them to stu_total before displaying them in a similar fashion in #stu${i}_total_points with the same number of decimal places shown.

Also, during each iteration,

  • the value of asgn1_points is added to asgn1_total,
  • the value of asgn2_points is added to asgn2_total, and
  • the value of grand_points is added to grand_total.
script.js
function calculate() {
  // Variables to contain total values
  let asgn1_total = 0,
    asgn2_total = 0,
    grand_total = 0;

  // We'll use a constant to referece the <table> element to try to shorten the code.
  const TABLE = document.querySelector("table");

  // Calculate number of rows in between top and bottom row
  const NUMBER_OF_STUDENTS = TABLE.rows.length - 2;

  for (let i = 1; i <= NUMBER_OF_STUDENTS; ++i) {
    /**
     * Check if #stu?_asgn1 and #stu?_asgn2 are empty
     * If empty, place in 0 values.
     */
    if (document.querySelector(`#stu${i}_asgn1`).value == "") {
      document.querySelector(`#stu${i}_asgn1`).value = "0";
    }
    if (document.querySelector(`#stu${i}_asgn2`).value == "") {
      document.querySelector(`#stu${i}_asgn2`).value = "0";
    }

    // calculate the marks from total percentage
    let asgn1_points =
      parseFloat(document.querySelector(`#stu${i}_asgn1`).value) * 0.2;
    let asgn2_points =
      parseFloat(document.querySelector(`#stu${i}_asgn2`).value) * 0.3;
    document.querySelector(`#stu${i}_asgn1_points`).value =
      asgn1_points.toFixed(2);
    document.querySelector(`#stu${i}_asgn2_points`).value =
      asgn2_points.toFixed(2);

    // calculate total marks for given student
    let stu_total =
      parseFloat(document.querySelector(`#stu${i}_asgn1_points`).value) +
      parseFloat(document.querySelector(`#stu${i}_asgn2_points`).value);
    document.querySelector(`#stu${i}_total_points`).value =
      stu_total.toFixed(2);

    // add to respective totals
    asgn1_total += asgn1_points;
    asgn2_total += asgn2_points;
    grand_total += stu_total;
  }
  // Note: use parseFloat() to get number with decimal places, and parseInt() to get Integer values (non-decimal)
  // we will be adding code from here
}

function clearTable() {
  // Initialize Asgn 1 & Asgn 2 (100% each) to 0
  document
    .querySelectorAll(`input:not(:read-only)`)
    .forEach((inputField) => (inputField.value = "0"));
  // Initialize Asgn & Total Points to 0.00
  document
    .querySelectorAll(`input:read-only`)
    .forEach((inputField) => (inputField.value = "0.00"));
}

window.onload = clearTable();

Step 4: Generating Average Points

Finally, we get to calculating the average points obtained among all students for Assignment 1, Assignment 2, and accumulatively. The averages for each component are stored in asgn1_avg, asgn2_avg, and grand_avg respectively. These averages are calculated by dividing their respective totals by the number of students (i.e., NUMBER_OF_STUDENTS).

The last course of action is to display them in the input fields with id attribute values #avg1, #avg2, #avg_total.

script.js
function calculate() {
  // Variables to contain total values
  let asgn1_total = 0,
    asgn2_total = 0,
    grand_total = 0;

  // We'll use a constant to referece the <table> element to try to shorten the code.
  const TABLE = document.querySelector("table");

  // Calculate number of rows in between top and bottom row
  const NUMBER_OF_STUDENTS = TABLE.rows.length - 2;

  for (let i = 1; i <= NUMBER_OF_STUDENTS; ++i) {
    /**
     * Check if #stu?_asgn1 and #stu?_asgn2 are empty
     * If empty, place in 0 values.
     */
    if (document.querySelector(`#stu${i}_asgn1`).value == "") {
      document.querySelector(`#stu${i}_asgn1`).value = "0";
    }
    if (document.querySelector(`#stu${i}_asgn2`).value == "") {
      document.querySelector(`#stu${i}_asgn2`).value = "0";
    }

    // calculate the marks from total percentage
    let asgn1_points =
      parseFloat(document.querySelector(`#stu${i}_asgn1`).value) * 0.2;
    let asgn2_points =
      parseFloat(document.querySelector(`#stu${i}_asgn2`).value) * 0.3;
    document.querySelector(`#stu${i}_asgn1_points`).value =
      asgn1_points.toFixed(2);
    document.querySelector(`#stu${i}_asgn2_points`).value =
      asgn2_points.toFixed(2);

    // calculate total marks for given student
    let stu_total =
      parseFloat(document.querySelector(`#stu${i}_asgn1_points`).value) +
      parseFloat(document.querySelector(`#stu${i}_asgn2_points`).value);
    document.querySelector(`#stu${i}_total_points`).value =
      stu_total.toFixed(2);

    // add to respective totals
    asgn1_total += asgn1_points;
    asgn2_total += asgn2_points;
    grand_total += stu_total;
  }
  // Note: use parseFloat() to get number with decimal places, and parseInt() to get Integer values (non-decimal)

  // calculate average
  const asgn1_avg = asgn1_total / NUMBER_OF_STUDENTS;
  const asgn2_avg = asgn2_total / NUMBER_OF_STUDENTS;
  const grand_avg = grand_total / NUMBER_OF_STUDENTS;

  // assign average to correct input fields
  document.querySelector(`#avg1`).value = asgn1_avg.toFixed(2);
  document.querySelector(`#avg2`).value = asgn2_avg.toFixed(2);
  document.querySelector(`#avg_total`).value = grand_avg.toFixed(2);
  // Note: toFixed(2) sets number values to display 2 decimal places
}

function clearTable() {
  // Initialize Asgn 1 & Asgn 2 (100% each) to 0
  document
    .querySelectorAll(`input:not(:read-only)`)
    .forEach((inputField) => (inputField.value = "0"));
  // Initialize Asgn & Total Points to 0.00
  document
    .querySelectorAll(`input:read-only`)
    .forEach((inputField) => (inputField.value = "0.00"));
}

window.onload = clearTable();

This is the end - you've successfully completed the Mark Demo page.. congrats!!