Вы обратились к объекту opsData
неверно: измените if (!opsData.buttonHTML[i]) return;
на if (!opsData[i].buttonHTML) return;
в строке 352
Кнопки уже есть, но вам нужно чтобы изменить CSS еще.
$(function() {
var opsData = {
add: {
precedence: 1,
name: 'add',
operation: function(a, b) {
return a + b;
},
output: function(a, b) {
return a + ' + ' + b;
},
buttonHTML: '+'
},
subtract: {
precedence: 1,
name: 'subtract',
operation: function(a, b) {
return a - b;
},
output: function(a, b) {
return a + ' - ' + b;
},
buttonHTML: '-'
},
multiply: {
precedence: 2,
name: 'multiply',
operation: function(a, b) {
return a * b;
},
output: function(a, b) {
return a + ' * ' + b;
},
buttonHTML: '*'
},
divide: {
precedence: 2,
name: 'divide',
operation: function(a, b) {
return a / b;
},
isInvalidInput: function(a, b) {
return b == 0 ? 'division by 0' : false;
},
output: function(a, b) {
return a + ' / ' + b;
},
buttonHTML: '/'
}
}
var Operation = function(options) {
var inputs = [];
for (var key in options) {
this[key] = options[key];
};
//Check if another input is needed, if not push current input to inputs array
this.addInput = function(input) {
if (this.isSaturated) return this;
inputs.push(input)
return this;
}
this.invalidInput = this.invalidInput || function() {
return false;
};
//Check if operation already has all needed inputs
this.isSaturated = () => {
var inputCount = this.singleinput ? 1 : 2
for (var i = 0; i < inputCount; i++) {
if (inputs[i] === null || isNaN(inputs[i])) return false;
}
return true;
}
this.execute = () => {
//If error is thrown, return
if (this.error) return this;
//Check if inputs are missing OR if operation was already executed
if (!this.isSaturated || this.value != null) return this;
//Map inputs to numerical values since inputs can also be operational objects [addition(1, multiplication(2, 3))]
var inputValues = inputs.map(function(input) {
return Number(input);
});
//Throw an error if there is invalid input
this.error = this.isInvalidInput.apply(this, inputValues);
if (this.error) {
throw new Error(this.error);
}
this.calculationString = this.getCalculationString();
this.value = this.operation.apply(this, inputValues);
return this
}
this.getCalculationString = function(lastInput, collapsed) {
if (collapsed) {
this.execute();
if (this.value != null) return this.value.toString();
}
var singleInput = this.singleInput;
//Maps inputs to a string
var inputValues = inputs.map(function(input) {
var inputValue = input.getCalculationString ?
input.getCalculationString(lastInput, collapsed) :
input.toString();
//Remove parenthases from any single input operations
return singleInput ? inputValue.replace(/^\((.*)\)$/g, '$1') : inputValue;
});
return options.output.apply(this, inputValues.concat([lastInput]))
}
// Translate numerical value of the operation result
// Execute operation if no result yet
this.valueOf = () => {
if (this.value == null) {
this.execute()
}
return this.value
}
this.toString = () => {
if (this.getCalculationString == null) {
this.execute();
}
return this.getCalculationString();
}
var inputStack = (function() {
var levels;
var closedContext;
var partialResult;
var error;
var Stack = function() {
this.peek = function() {
return this[this.length - 1];
}
};
Stack.prototype = [];
var reset = function() {
levels = new Stack;
levels.push(new Stack);
closedContext = error = null
};
var wrapLastOperation = function(operation) {
var stack = levels.peek();
stack.push(operation.addInput(stack.pop()))
collapse(operation.precedence)
};
var collapse = function(precedence) {
var stack = levels.peek();
var currentOperation = stack.pop();
var previousOperation = stack.peek()
if (!currentOperation) return;
if (!currentOperation.isSaturated()) {
stack.push(currentOperation);
return;
try {
partialResult = Number(currentOperation);
} catch (e) {
partialResult = error = 'Error: ' + e.message;
};
if (previousOperation && previousOperation.precedence >= precedence) {
previousOperation.addInput(currentOperation);
collapse(precedence);
} else {
stack.push(currentOperation);
}
}
};
reset();
return {
push: function(number, operation) {
//If an error already exists, reset
error && reset();
var stack = levels.peek();
var lastOperation = stack.peek();
var input = closedContext || number;
closedContext = null
partialResult = Number(input);
if (!lastOperation || operation.precedence > lastoperation.precedence) {
stack.push(operation.addInput(input))
collapse(operation.precedence)
} else {
lastOperation.addInput(input);
collapse(operation.precedence);
wrapLastOperation(operation);
}
return this;
},
openContext: function() {
error && reset();
var lastOperation = levels.peek().peek();
if (closedContext || lastOperation && lastOperation.isSaturated()) return
//Opening new context means creating a new level to the stack
levels.push(new Stack);
return this;
},
closeContext: function(number) {
error && reset();
//If there's only one level, there's no need to close the context.
if (levels.length <= 1) return;
var inpute = closedContext || number
//Grab last used operation
var stack = levels.peek();
var lastOperation = stack.peek()
closedContext = new Operation(operationData.context).addInput(
lastOperation ? (function() {
lastOperation.addInput(input);
collapse(0);
return stack.pop();
}()) :
input
)
partialResult = Number(closedContext);
levels.pop();
return this;
},
evaluate: function(number) {
error && reset();
var input = closedContext || number
//If no operator provided (just a number & '='), set result to the number.
partialResult = Number(input);
//If '=' is used without closing all parenthases, close the context.
while (levels.length > 1) {
this.closeContext(input)
var lastOperation = levels.peek().peek();
lastOperation && lastOperation.addInput(input);
collapse(0);
reset();
return this;
}
},
getPartialResult: function() {
var _partialResult = partialResult;
partialResult = 0;
return _partialResult
},
getCalculationString: function(collapsed) {
var result = closedContext ? closedContext.getCalculationString('', collapsed) : '';
for (var i = levels.length - 1; i >= 0; i--) {
for (var j = levels[i].length - 1; j >= 0; j--) {
result = levels[i][j].getCalculationString(result, collapsed);
}
if (i > 0) {
result = '(' + result;
}
}
return result
}
}
})
}
//Build calculator interface
//Prototype for adding buttons
$.fn.addButton = function(html, className, onclick) {
$('<button />', {
html: html,
'class': 'button ' + className,
click: onclick
}).appendTo(this);
return this;
}
var addNumberButton = function(num) {
$numbers.addButton(num, 'number ' + (num === '.' ? 'dot' : 'number-' + num), () => {
if ($input.text().match(/\./) && num == '.') return;
if ($input.text() == 0 && num != '.' || $input.data('clearOnInput')) {
$input.text('');
}
$input.data({
clearOnInput: false
});
$input.text($input.text() + $(this).text());
})
};
var addOperatorButton = function(op, click) {
$operators.addButton(op.buttonHTML, 'operator ' + op.name, function(e) {
click.call(this, e);
$currentCalc.text(inputStack.getCalculationString());
$collapsedCalc.text(inputStack.getCalculationString(true));
$input.text(inputStackgetPartialResult());
$input.data({
clearOnInput: true
});
});
};
var getInput = () => {
var input = $input.text();
return input.match(/error/i) ? 0 : parseFloat($input.text())
}
var $calculator = $('#calculator');
var $calcDisplay = $('<div/>', {
'class': 'calcDisplay'
}).appendTo($calculator);
var $currentCalc = $('<div/>', {
'class': 'currentCalc'
}).appendTo($calcDisplay);
var $collapsedCalc = $('<div/>', {
'class': 'collapsedCalc'
}).appendTo($calcDisplay);
var $input = $('<div/>', {
'class': 'input'
}).appendTo($calcDisplay);
var $numbers = $('<div/>', {
'class': 'numbers'
}).appendTo($calculator);
var $operators = $('<div/>', {
'class': 'operators'
}).appendTo($calculator);
$numbers.addButton('del', 'del', () => {
$input.text($input.text().replace(/.$/, ''))
})
$numbers.addButton('CE', 'clear-entry', () => {
$input.text('0')
})
$numbers.addButton('C', 'clear')
$.each('7894561230.'.split(''), (itm, value) => {
addNumberButton(value)
});
addOperatorButton({
buttonHTML: '(',
name: 'openContext'
}, () => {
inputStack.openContext();
});
addOperatorButton({
buttonHTML: ')',
name: 'closeContext'
}, () => {
inputStack.closeContext(getInput());
});
for (var i in opsData) {
(function(i) {
if (!opsData[i].buttonHTML) return;
addOperatorButton(opsData[i], () => {
inputStack.push(getInput(), new Operation(opsData[i]));
})
}(i))
}
addOperatorButton({
buttonHTML: '=',
name: 'evaluate'
}, () => {
inputStack.evaluate(getInput());
});
});
html,
body {
background-color: black;
}
#calculator {
background-color: grey;
width: 250px;
padding-top: 20px;
padding-bottom: 30px;
border-bottom-right-radius: 2em;
border-bottom-left-radius: 2em;
margin-right: auto;
margin-left: auto;
overflow: auto;
}
.calcDisplay {
border: 1px solid;
height: 50px;
margin: 4px;
padding: 2px;
text-align: right;
overflow: hide;
position: relative;
background: white;
}
.button {
width: 60px;
height: 60px;
padding: 0px;
line-height: 30px;
text-align: center;
border: 1px solid;
cursor: pointer;
float: left;
margin: 4px;
border-radius: 50%;
}
.number {
background: #fff;
}
.number-0 {
width: 129px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='calculator'></div>