Lesson Thursday

The last time we added to our ongoing calculator project, we implemented four separate forms, each with their own submit button and event listener. This was great practice in creating and retrieving information from HTML forms in JavaScript; but realistically, it pretty clearly violates the 'Don't Repeat Yourself' principle of programming.

Consolidating Multiple Options into One Form

Now that we've spent time exploring JavaScript branching, let's consolidate all four capabilities of our calculator—addition, subtraction, multiplication, and division—into a single form with a single submit button and event listener. We'll add radio buttons that allow the user to choose whether they'd like to add, subtract, multiply, or divide the two numbers provided in the form.

This new, all-inclusive form should look something like this:

calculator.html
<!DOCTYPE html>
<html>
  <head>
    <title>Calculator</title>
    <link href="css/bootstrap.css" rel="stylesheet" type="text/css">
    <link href="css/styles.css" rel="stylesheet" type="text/css">
    <script src="js/jquery-1.12.0.js"></script>
    <script src="js/scripts.js"></script>
  </head>
  <body>
    <div class="container">
      <h1>Calculator</h1>
      <form id="calculator">
        <div class="form-group">
          <label for="input1">1st number:</label>
          <input id="input1" class="form-control" type="text">
        </div>
        <div class="form-group">
          <label for="input2">2nd number:</label>
          <input id="input2" class="form-control" type="text">
        </div>
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="add" checked>
            add
          </label>
        </div>
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="subtract">
            subtract
          </label>
        </div>
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="multiply">
            multiply
          </label>
        </div>        
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="divide">
            divide
          </label>
        </div>        
        <button type="submit" class="btn">Go!</button>
      </form>
      <div id="output">

      </div>
    </div>
  </body>
</html>

In the HTML above we've given the form an id of calculator and the inputs have the ids input1 and input2 (instead of something like add1 and add2, since this form will be used for all operations). We've also added radio buttons to allow users to choose what operation to perform on the two numbers they provide.

Updating User Interface Logic

Now that we've made these changes, we also need to update scripts.js to point to the newly-updated id's when attaching the form submit listener and reading the input values from our form:

js/scripts.js

// Business logic not included because it will remain the same. 

$(document).ready(function() {
  $("form#calculator").submit(function() {
    event.preventDefault();
    var number1 = parseInt($("#input1").val());
    var number2 = parseInt($("#input2").val());
    var operator = $("input:radio[name=operator]:checked").val();
    var result = add(number1, number2);
    $("#output").text(result);
  });
});

Here we've changed the form id we're attaching the event listener to in the line $("form#calculator").submit(function() {, and the ids of the form input fields we're gathering values from in the two lines var number1 = parseInt($("#input1").val()); and var number2 = parseInt($("#input2").val());. We've also added a line to grab the radio button input to determine which operation we'd like to perform on the two numbers: var operator = $("input:radio[name=operator]:checked").val();

Now, if we launch our HTML page in the browser we should see our two inputs and radio buttons:

calculator with radio buttons

Testing and Debugging with console.log()

If we submit the form, it should work. However, we're still calling the add() function no matter what mathematical operation the user selects with the line var result = add(number1, number2);. Before we address this by adding code to call different functions depending on the radio button value, let's briefly confirm that we're correctly gathering our new form inputs.

We'll add the following instances of console.log() to our user interface logic:

js/scripts.js
// business logic code not included here because we're not changing it

$(document).ready(function() {
  $("form#calculator").submit(function() {
    event.preventDefault();
    var number1 = parseInt($("#input1").val());
    var number2 = parseInt($("#input2").val());
    var operator = $("input:radio[name=operator]:checked").val();
    console.log("1st number: " + number1);  // for debugging
    console.log("2nd number: " + number2);  // for debugging
    console.log("operator: " + operator);  // for debugging
    var result = add(number1, number2);
    $("#output").text(result);
  });
});

Here, we've added three console.log() lines that log our inputted values to the JavaScript console. For the purposes of debugging, console.log() works similarly toalert() but prints values in the JavaScript console, instead of a pop-up dialog box. Now, if we complete and submit the form, we should see something like this appear in the console:

calculator-multiple-operators-with-console-log

This confirms that the code gathering our form input values is indeed retrieving the correct information, and doing so successfully. Using console.log() in this manner is a great debugging approach. If our form were not functioning correctly, or if our calculator was providing us odd, unexpected results (such as NaN) we could use console.log()to double-check that our values are being retrieved. And, if they weren't, we could pinpoint which value is causing the issue, and double-check that we're retrieving this value with the correct HTML id.

console.log() should be a go-to tool in your debugging tool belt when working in JavaScript!

Implementing Branching

Now that we've confirmed we're successfully collecting form input values, let's add branching to call the appropriate function based on the user's selected radio button:

js/scripts.js
// business logic code not included here because we're not changing it

$(document).ready(function() {
  $("form#calculator").submit(function() {
    event.preventDefault();
    var number1 = parseInt($("#input1").val());
    var number2 = parseInt($("#input2").val());
    var operator = $("input:radio[name=operator]:checked").val();
    var result;
    if (operator === "add") {
      result = add(number1, number2);
    } else if (operator === "subtract") {
      result = subtract(number1, number2);
    } else if (operator === "multiply") {
      result = multiply(number1, number2);
    } else if (operator === "divide") {
      result = divide(number1, number2);
    }
    $("#output").text(result);
  });
});

Here, we've simply added an if...else statement that calls different methods depending on what radio button the user has selected. For instance, if they select "divide", we run our divide() function; if they select "subtract", we run our subtract() function, etc. Regardless of which operation they opt to perform, we still insert the answer onto our page with the same line, $("#output").text(result);.

Notice in the code above that we declared the variable result outside the if...else statement, but we did not immediately give it a value. We simply told JavaScript that it exists. It will have the value of undefined until we define its value depending on which branch we're following. Then, after the if...else statement, we stick the value of result into the #output div.

Additional Note on Separation of Logic

Also, notice that because we made sure to separate our front and back end logic in scripts.js, we only had to change the user interface logic when we made changes to our HTML! This is yet another benefit of keeping user interface and business logic well-separated; when you need to alter how the user will interact with your site, you'll only need to update JavaScript in one place, instead of untangling the whole file.

Example Code


calculator.html
<!DOCTYPE html>
<html>
  <head>
    <title>Calculator</title>
    <link href="css/bootstrap.css" rel="stylesheet" type="text/css">
    <link href="css/styles.css" rel="stylesheet" type="text/css">
    <script src="js/jquery-1.12.0.js"></script>
    <script src="js/scripts.js"></script>
  </head>
  <body>
    <div class="container">
      <h1>Calculator</h1>
      <form id="calculator">
        <div class="form-group">
          <label for="input1">1st number:</label>
          <input id="input1" class="form-control" type="text">
        </div>
        <div class="form-group">
          <label for="input2">2nd number:</label>
          <input id="input2" class="form-control" type="text">
        </div>
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="add" checked>
            add
          </label>
        </div>
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="subtract">
            subtract
          </label>
        </div>
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="multiply">
            multiply
          </label>
        </div>        
        <div class="radio">
          <label>
            <input type="radio" name="operator" value="divide">
            divide
          </label>
        </div>        
        <button type="submit" class="btn">Go!</button>
      </form>
      <div id="output">

      </div>
    </div>
  </body>
</html>
js/scripts.js
var add = function(number1, number2) {
  return number1 + number2;
};

var subtract = function(number1, number2) {
  return number1 - number2;
};

var multiply = function(number1, number2) {
  return number1 * number2;
};

var divide = function(number1, number2) {
  return number1 / number2;
};

$(document).ready(function() {
  $("form#calculator").submit(function() {
    event.preventDefault();
    var number1 = parseInt($("#input1").val());
    var number2 = parseInt($("#input2").val());
    var operator = $("input:radio[name=operator]:checked").val();
    var result;
    if (operator === "add") {
      result = add(number1, number2);
    } else if (operator === "subtract") {
      result = subtract(number1, number2);
    } else if (operator === "multiply") {
      result = multiply(number1, number2);
    } else if (operator === "divide") {
      result = divide(number1, number2);
    }
    $("#output").text(result);
  });
});