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();
};