API Docs for: 1.0.0
Show:

File: calculator_v2.js

/**
 *  @module Calculator
 */
/**
 *  Methods and properties for the reverse-polish calculator.
 *  Example instantiation using default display element ids:
 * 
 *       var calc;
 *       window.onload = function () {
 *         calc = new Calculator();
 *       };
 *
 *  @class Calculator
 *  @constructor
 *  @param  {Object} [options]
 *    @param {String} [options.displayId] Element id for the display field; default=rpnc_display. 
 *    @param {String} [options.stackDisplayId] Element id for the stack display field; default=rpnc_stack.
 *  @return {void}
 */
function Calculator(options) {
  var opts = options || {};

  /**
   *  The element in which calculation results are displayed.
   *  @property display
   *  @type {HTMLElement}
   *  @default "rpnc_display"
   */
  this.display = getEl(opts.displayId || "rpnc_display");

  /**
   *  Indicates whether the user is in the process of entering digits.
   *  @property isUserEnteringDigits
   *  @type {boolean}
   *  @default false
   */
  this.isUserEnteringDigits = false;

  /**
   *  Instance of Program class.
   *  @property program
   *  @type {Program}
   */
  this.program = new Program();

  /**
   *  The element in which the contents of the stack are displayed.
   *  @property stackDisplay
   *  @type {HTMLElement}
   *  @default "rpnc_stack"
   */
  this.stackDisplay = getEl(opts.stackDisplayId || "rpnc_stack");
}

/**
 *  Adds item to the stack display's inner HTML.
 *  @method addToStackDisplay
 *  @param {mixed} item Number | String
 *  @return {void}
 */
Calculator.prototype.addToStackDisplay = function (item) {
  if (this.stackDisplay.innerHTML == " ") {
    this.stackDisplay.innerHTML = item;
  }
  else {
    this.stackDisplay.innerHTML += ", " + item;
  }
};

/**
 *  Clears the display elements and the program stack.
 *  @method clearAll
 *  @return {Void}
 */
Calculator.prototype.clearAll = function () {
  this.display.innerHTML = "0";
  this.clearStackDisplay();
  this.isUserEnteringDigits = false;
  this.program.clear();
};

/**
 *  Clears the stack display element.
 *  @method clearStackDisplay
 *  @return {void}
 */
Calculator.prototype.clearStackDisplay = function () {
  this.stackDisplay.innerHTML = " ";
};

/**
 *  Handles the click event on digit keys.
 *  @method digitPressed
 *  @param  {mixed} d Number (0-9) | String (".")
 *  @return {void}
 */
Calculator.prototype.digitPressed = function (d) {
  if (d == ".") {
    if (this.display.innerHTML.indexOf(".") != -1) {
      return;
    }
  }

  if (this.isUserEnteringDigits) {
    this.display.innerHTML += d;
  }
  else {
    this.display.innerHTML = d;
    this.isUserEnteringDigits = true;
  }
};

/**
 *  Loops through the stack and outputs the values to the stack display.
 *  @method displayStack
 *  @return {void}
 */
Calculator.prototype.displayStack = function () {
  this.clearStackDisplay();

  for (var i = 0; i < this.program.stack.length; i++) {
    this.addToStackDisplay(this.program.stack[i]);
  }
};

/**
 *  Event handler for clicks on the enter key.
 *  @method enterPressed
 *  @return {void}
 */
Calculator.prototype.enterPressed = function () {
  this.program.push(getEl("rpnc_display").innerHTML);
  this.displayStack();
  this.isUserEnteringDigits = false;
};

/**
 *  Handles the click event on operator keys (+ - * / etc.).
 *  @method operatorPressed
 *  @param  {String} o
 *  @return {void}
 */
Calculator.prototype.operatorPressed = function (o) {
  switch (o) {
    case "clr":
      this.clearAll();
      break;

    case "undo":
      this.undo();
      break;

    case "π":
      if (this.isUserEnteringDigits) {
        this.enterPressed();
      }
  
      this.digitPressed(Math.PI);
      this.enterPressed();
      break;

    default:
      if (this.isUserEnteringDigits) {
        this.enterPressed();
      }
  
      this.display.innerHTML = this.program.performOperation(o);
      this.displayStack();
      break;
  }
};

/**
 *  Clears the last item input: if digits being entered then backspace one digit else clear last item
 *  from stack and re-run program.
 *  @method undo
 *  @return {void}
 */
Calculator.prototype.undo = function () {
  if (this.isUserEnteringDigits) {
    if (this.display.innerHTML.length > 1) {
      this.display.innerHTML = this.display.innerHTML.substr(0, this.display.innerHTML.length - 1);
    }
    else {
      this.display.innerHTML = 0;
      this.isUserEnteringDigits = false;
    }
    return;
  }

  this.program.undo();
  this.display.innerHTML = this.program.runProgram();
  this.displayStack();
};

/**
 *  The model for the rpn calculator: creates a push-pop stack object to hold and process digits and operators.
 *  @class Program
 *  @constructor
 *  @return {void}
 */
function Program() {
  /**
   *  A stack to store the input values and functions.
   *  @property stack
   *  @type {Array} 
   */
  this.stack = [];
}

/**
 *  Clears the stack by setting its length to zero.
 *  @method clear
 *  @return {void} 
 */
Program.prototype.clear = function () {
  this.stack.length = 0;
};

/**
 *  Fetches the top item off the stack.
 *  @method pop
 *  @return {mixed} Number | String
 */
Program.prototype.pop = function () {
  return this.stack.pop();
};

/**
 *  Put an item onto the top of the stack.
 *  @method push
 *  @return {void}
 */
Program.prototype.push = function (item) {
  this.stack.push(item);
};

/**
 *  Recursive function to parse stack and perform all operations.
 *  @method parseStack
 *  @param  {Array} stack   must be a copy of this.stack
 *  @return {float}
 */
Program.prototype.parseStack = function (stack) {
  var operand_1, operand_2;
  var result = 0;
  var topOfStack = (stack.length) ? stack.pop() : 0;

  if (!isNaN(parseFloat(topOfStack))) {
    result = parseFloat(topOfStack);
  }
  else {
    switch (topOfStack) {
      case "+":
        operand_2 = this.parseStack(stack);
        operand_1 = this.parseStack(stack);
        result = operand_1 + operand_2;
        break;

      case "*":
        operand_2 = this.parseStack(stack);
        operand_1 = this.parseStack(stack);
        result = operand_1 * operand_2;
        break;

      case "-":
        operand_2 = this.parseStack(stack);
        operand_1 = this.parseStack(stack);
        result = operand_1 - operand_2;
        break;

      case "/":
        operand_2 = this.parseStack(stack);
        operand_1 = this.parseStack(stack);

        if (operand_2) {
          result = operand_1 / operand_2;
        }

        break;

      // case "π":
        // result = Math.PI;
        // break;

      case "cos":
        operand_1 = this.parseStack(stack);
        result = Math.cos(operand_1);
        break;

      case "sin":
        operand_1 = this.parseStack(stack);
        result = Math.sin(operand_1);
        break;

      case "sqrt":
          operand_1 = this.parseStack(stack);
          result = Math.sqrt(operand_1);
          break;
 
        case "+/-":
        result = this.parseStack(stack) * -1;
        break;
    }
  }

  return result;
};

/**
 *  Function called when an operator key (e.g. + or /) is pressed.
 *  @method performOperation
 *  @param {Object} o  may be a Number or String
 *  @return {float}
 */
Program.prototype.performOperation = function (o) {
  this.push(o);

  return this.runProgram();
};

/**
 *  Calls parseStack, which calculates the stack.
 *  @method runProgram
 *  @return {float} 
 */
Program.prototype.runProgram = function ()
{
  // calls parseStack with a copy of this.stack
  return this.parseStack([].concat(this.stack));
};

/**
 *  Removes the last-entered operand from the stack (pops and discards it).
 *  @method undo
 *  @return {void}
 */
Program.prototype.undo = function () {
  this.pop();
};