【问题标题】:Issues dynamically creating buttons with jQuery使用 jQuery 动态创建按钮的问题
【发布时间】:2020-08-07 00:59:45
【问题描述】:

我正在尝试将操作员按钮动态添加到我的计算器中,但是我没有任何运气。我创建了动态创建数字和操作员按钮的函数。数字已成功创建,但一旦我尝试添加运算符,什么也没有发生。我正在尝试使用 for-in 循环在括号按钮和评估按钮之间添加运算符。似乎当我在运算符之前创建评估按钮时,评估按钮已成功创建但运算符没有。如果我移动代码以创建运算符 before 评估按钮,则两者都不会出现。我相当确定问题出在我的 for-in 循环中,但我不太确定在哪里。非常感谢任何和所有帮助/指导!

 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: '/'
  }
}

$.fn.addButton = function(html, className, onclick) {
  $('<button />', {
    html: html,
    'class': 'button ' + className,
    click: onclick
    }).appendTo(this);
  return this;
}

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

for (var i in opsData) {
  (function(i) {
    if (!opsData.buttonHTML[i]) return;
    addOperatorButton(opsData[i], () => {
      inputStack.push(getInput(), new Operation(opsData[i]));
    })
  }(i))
}

我的完整代码笔的链接在这里:https://codepen.io/tazmancooks/pen/PoNwGMX

抱歉,如果我的问题没有很好地表达出来,我对 jQuery 和 Javascript 还是很陌生。

【问题讨论】:

    标签: javascript html jquery calculator


    【解决方案1】:

    您错误地访问了 opsData 对象:在 第 352 行

    中将 if (!opsData.buttonHTML[i]) return; 更改为 if (!opsData[i].buttonHTML) return;

    按钮现在在那里,但您仍需要更改 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>

    【讨论】:

      【解决方案2】:

      问题是当您尝试遍历 opsData 对象时。

      for (var i in opsData) {
        (function(i) {
          if (!opsData.buttonHTML[i]) return;
            addOperatorButton(opsData[i], () => {
            inputStack.push(getInput(), new Operation(opsData[i]));
          })
        }(i))
      }
      

      当您应该访问 opsData[i].buttonHTML 时,您正在访问 buttonHTML 的 [i] 属性

      【讨论】:

        猜你喜欢
        • 2012-02-14
        • 2013-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-30
        • 1970-01-01
        • 1970-01-01
        • 2016-04-28
        相关资源
        最近更新 更多