6
\$\begingroup\$

I've been trying to learn and implement object oriented programming in JavaScript.

Could you please provide some advice and feedback (primarily on the application of OOP, overall code efficiency and proper use of patterns)?

Code explanation:

The code consists of a calculator constructor which has basic functions: add, subtract, divide and multiply, clear, equal.

The input method captures the content of clicks assigns them to input variable and runs some basic filters.

If the input is a number, it's appended to the variable number as a string.

If any of the operators are clicked, the contents of the variable number and the operator is added to inputArray. This creates a list of numbers and operators in a sequence.

If the '=' sign is clicked, the equal method is run, which loops through inputArray and assigns division and multiplication followed by add and subtract.

Once the equal method completes, inputArray is left with one number, result, which is displayed on the screen.

The printEquation method simply prints out the contents of inputArray on the screen.

Here's the equation that has been typed so far:

function Calculator() {
  "use strict";
  var inputArray = [],
    operations = ["x", "/", "+", "-"],
    number = "",
    i,
    that = this,
    equation = document.getElementById("equation"),
    display = document.getElementById("display");
  display.textContent = "0";

  this.add = function(a, b) {
    var c = inputArray[a] + inputArray[b];
    inputArray[a] = c;
    inputArray.splice(a + 1, 2);
    i -= 2;
  };

  this.substract = function(a, b) {
    var c = inputArray[a] - inputArray[b];
    inputArray[a] = c;
    inputArray.splice(a + 1, 2);
    i -= 2;
  };

  this.divide = function(a, b) {
    var c = inputArray[a] / inputArray[b];
    if (isNaN(c)) {
      c = 0;
    }
    inputArray[a] = c;
    inputArray.splice(a + 1, 2);
    i -= 2;
  };

  this.multiply = function(a, b) {
    var c = inputArray[a] * inputArray[b];
    inputArray[a] = c;
    inputArray.splice(a + 1, 2);
    i -= 2;
  };

  this.equal = function() {
    for (i = 0; i < inputArray.length; i += 1) {
      if (inputArray[i] === "/") {
        that.divide(i - 1, i + 1);
      }
      if (inputArray[i] === "x") {
        that.multiply(i - 1, i + 1);
      }
    }
    for (i = 0; i < inputArray.length; i += 1) {
      if (inputArray[i] === "+") {
        that.add(i - 1, i + 1);
      }
      if (inputArray[i] === "-") {
        that.substract(i - 1, i + 1);
      }
    }
    display.textContent = inputArray[0];
  };

  this.clear = function() {
    inputArray = [];
    number = "";
    display.textContent = "0";
    equation.textContent = "";
  };

  this.printEquation = function() {
    equation.textContent = "";
    for (i = 0; i < inputArray.length; i += 1) {
      equation.textContent += inputArray[i];
    }
  };

  this.input = function(e) {
    var input = e.target.textContent;
    var testInput = operations.indexOf(input) === -1 ? false : true;
    //Add a zero if operator is clicked without any input
    if (testInput && number === "") {
      number = "0";
    }
    //Run clear if equal is clicked without any input
    if (input === "=" && inputArray.length === 0) {
      this.clear;
    } else if (testInput) {
      inputArray.push(parseInt(number, 10));
      inputArray.push(input);
      number = "";
      display.textContent = "0";
      that.printEquation();
    } else if (input === "C") {
      that.clear();
    } else if (input === "=") {
      if (number !== "") {
        inputArray.push(parseInt(number, 10));
        number = "";
        that.printEquation();
        that.equal();
      } else {
        inputArray.pop();
        number = "";
        that.equal();
      }
    } else {
      number += input;
      display.textContent = number;
    }
  };
}

//Initialise calculator
var calci = new Calculator();
var nodes = document.getElementById("calBtn").childNodes;
for (var i = 0; i < nodes.length; i++) {
  if (nodes[i].nodeName.toLowerCase() === "span") {
    nodes[i].addEventListener("click", calci.input)
  }
}
* {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  font: 500 1.1em sans-serif;
  background-color: mintcream;
  color: darkslategray;
}

h1,
h2 {
  font: 500 sans-serif;
}

h1 {
  font-size: 1.5em;
}

.wrapper {
  margin: 0 auto;
  text-align: center;
}

#calculator {
  width: 330px;
  height: auto;
  background-color: blanchedalmond;
  border: 1px solid gray;
  padding: 2px;
  text-align: center;
  margin: 0 auto;
}

#calBtn {
  background-color: inherit;
}

#calBtn span {
  display: inline-block;
  background-color: lightgray;
  width: 71px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  vertical-align: middle;
  margin: 5px;
  cursor: pointer;
  outline: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

#calBtn span:hover {
  -webkit-box-shadow: none;
  box-shadow: none;
  border: 0.5px solid gray;
}

.screen {
  width: 95%;
  height: auto;
  border: 0.5px solid gray;
  margin: 15px auto 10px auto;
}

#calculator,
#calBtn span,
.screen {
  -webkit-box-shadow: 1px 1px 2px darkslateblue;
  box-shadow: 1px 1px 2px darkslateblue;
  border-radius: 5px;
}

#equation,
#display {
  display: block;
  width: 93%;
  height: 40px;
  line-height: 40px;
  margin: 0 auto;
  text-align: right;
  padding: 1px 0;
}

@media only screen and (max-width: 768px) {
  #calBtn span {
    height: 71px;
    line-height: 71px;
  }
}
<body>
<div class="wrapper">
  <div id="calculator">

    <div class="screen">
      <span id="equation"></span>
      <span id="display"></span>
    </div>
    <div id="calBtn">
      <span>7</span><!--
    --><span>8</span><!--
    --><span>9</span><!--
    --><span>/</span><!--
    --><br><!--
    --><span>4</span><!--
    --><span>5</span><!--
    --><span>6</span><!--
    --><span>x</span><!--
    --><br><!--
    --><span>1</span><!--
    --><span>2</span><!--
    --><span>3</span><!--
    --><span>-</span><!--
    --><br><!--
    --><span>0</span><!--
    --><span>C</span><!--
    --><span>=</span><!--
    --><span>+</span>
    </div>
  </div>
</div>
</body>

\$\endgroup\$
4
  • \$\begingroup\$ Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ Commented Jun 24, 2017 at 12:35
  • \$\begingroup\$ Out of curiosity, do those html comments next to each span have any purpose? \$\endgroup\$
    – t3chb0t
    Commented Jun 24, 2017 at 18:14
  • 1
    \$\begingroup\$ @t3chb0t The Html comments are meant to remove white spaces between the span tags. Removing the comments will add extra margin between each button which isn't pretty. The alternative is to have all the span tags together on one line without spaces. I prefer adding comments to maintain presentation. \$\endgroup\$ Commented Jun 26, 2017 at 14:29
  • \$\begingroup\$ wow, this is a cool trick. I knew they are not there by coincidence and yes, having them in one line wouldn't be so nice ;-) \$\endgroup\$
    – t3chb0t
    Commented Jun 26, 2017 at 14:36

1 Answer 1

2
\$\begingroup\$

This is a really cool snippet! I like that you were able to avoid using eval. After a quick glance I only have a few minor suggestions.

Separate the logic from the interface. You separated them to some degree in that you have the event handlers attached manually after constructing the calculator (even though the event handler is defined in the calculator constructor). I would instead make 2 constructors, a Calculate constructor that can take an expression and evaluate it, and a Calculator constructor that sets up the interface and the event handlers. That way you will be able to use that sweet calculator code in other projects, or extend it for use with a more advanced calculator.

Clear the results of the previous equation before moving the current one to the top row. I believe that is fixed by fixing the typo in your input function where you do this.clear; instead of this.clear();

that is not necessary. You're using that variable instead of just using this in several places. In fact I don't see any places where that is needed at all, just stick with this and leave that alone.

Consider taking advantage of prototype. Using this to assign methods makes us feel like we're writing classes so it feels natural, but JS doesn't have classes*, so embrace the prototype.

*ES6 actually does have classes.

\$\endgroup\$
4
  • \$\begingroup\$ I have a question on point 3. I replaced 'that' with this and the code works perfect. However 'this' refers to window object within input method and throws an error. Why doesn't the other methods reflect the same behavior? I updated the code with your suggestions and I think your 1st and last suggestion is an excellent idea. Will try it out for some extra practice. Thank a ton for taking the time! \$\endgroup\$ Commented Jun 24, 2017 at 11:37
  • 1
    \$\begingroup\$ any method of the constructor or the constructor itself will give this the context of the constructor. Any functions that are not part of the constructor, including anonymous functions, will give this the context of the window. After a quick glance I don't see any anonymous functions in your constructor methods, but I may have overlooked something. See here for more info. \$\endgroup\$ Commented Jun 24, 2017 at 12:57
  • 1
    \$\begingroup\$ Hey, I just figured this. The input method passes the click event as an parameter. The 'this' in input method refers to the click event. So declaring that = this allows us to reference the calculator object. I found the explanation here in the first point [javascriptissexy.com/… \$\endgroup\$ Commented Jun 24, 2017 at 14:42
  • \$\begingroup\$ You're absolutely right.. I completely overlooked the fact that that method was being called by the event handler. \$\endgroup\$ Commented Jun 24, 2017 at 14:48

Not the answer you're looking for? Browse other questions tagged or ask your own question.