calc

by sperske

HTML

<div class="calc">
  <div>
    <button class="clear">C</button>
    <input name="display" readonly="readonly" placeholder="enter some numbers" />
    <button class="input" data-value="7">7</button>
    <button class="input" data-value="8">8</button>
    <button class="input" data-value="9">9</button>
    <button class="operation" data-value="/">/</button>
    <button class="input" data-value="4">4</button>
    <button class="input" data-value="5">5</button>
    <button class="input" data-value="6">6</button>
    <button class="operation" data-value="*">&times;</button>
    <button class="input" data-value="1">1</button>
    <button class="input" data-value="2">2</button>
    <button class="input" data-value="3">3</button>
    <button class="operation" data-value="+">+</button>
    <button class="input" data-value="0">0</button>
    <button class="input" data-value=".">.</button>
    <button class="equals">=</button>
    <button class="operation" data-value="-">-</button>
  </div>
</div>

CSS

body {
  padding: 0;
  margin: 0;
}

.calc {
  height: 100%;
  min-width: 350px;
  background-color: #8dcfed;
  padding: 10px;
}

.calc>div {
  display: grid;
  row-gap: 10px;
  column-gap: 5px;
  grid-template-rows: repeat(4, 1fr);
  grid-template-columns: repeat(4, 1fr);
}

.calc input,
.calc button {
  font-family: Helvetica, "Helvetica Neue";
  font-size: 28px;
  font-weight: bold;
}

.calc input {
  grid-column: span 3;
  padding-right: 15px;
  text-align: right;
  background-color: #78a7bb;
  border: none;
  box-shadow: inset 0 8px #618698;
  color: #fff;
  border-radius: 8px;
  margin-bottom: -8px;
}

.calc button {
  color: #81898e;
  background-color: #fff;
  width: 100%;
  height: 68px;
  border-radius: 8px;
  border: none;
  box-shadow: 0 8px #78a7bb;
}

.calc button:active {
  transform: translateY(4px);
  box-shadow: 0 4px #78a7bb;
}

.calc button.clear:active {
  box-shadow: 0 4px #ff7b83;
}

.calc button.operation:active {
  box-shadow: 0 4px #78a7bb;
}

.calc button.equals:active {
  box-shadow: 0 4px #9cab44;
}

.calc button.clear {
  background-color: #ff9fa7;
  box-shadow: 0 8px #ff7b83;
  color: #fff;
}

.calc button.equals {
  background-color: #f1ff8b;
  box-shadow: 0 8px #9cab44;
  color: #81898e;
}

.calc button.operation {
  background-color: #fff0f5;
  box-shadow: 0 8px #78a7bb;
  color: #81898e;
}

JavaScript

const re = /([+-/*//])?(\d*\.*(\d*))*/g;
const display = $("input[name=display]");
let allow_op = true;
let allow_decimal = false;

const ops = {
  "+"(a, b) {
    return a + b;
  },
  "-"(a, b) {
    return a - b;
  },
  "*"(a, b) {
  	return a * b;
  },
  "/"(a, b) {
  	return a / b;
  }
};

function calc(s) {
  let sum = 0;
  let m;
  while ((m = re.exec(s)) !== null) {
    if (m.index === re.lastIndex) {
      re.lastIndex++;
    }
    const op = m[1] || "+";
    const value = parseFloat(m[2]);
    if (!isNaN(value) && ops[op]) {
      sum = ops[op](sum, value);
    }
  }
  return sum;
}

/***
 * Takes an input and tests if it should
 * be allowed based on what has been typed before.
 * TODO(sperske): currently allows too many decimals
 * @param{string} input to parse
 * @returns boolean if allowed
 */
function allowInput(input) {
  let allow = true;
  switch (input) {
    case ".":
      allow = allow_decimal;
      allow_decimal = false;
      allow_op = false;
      break;
    case "0":
      allow_decimal = true;
      allow_op = true;
      break;
    case "-":
    case "+":
      allow = allow_op;
      allow_decimal = false;
      allow_op = false;
      break;
    default:
      allow_decimal = true;
      allow_op = true;
  }
  return allow;
}

$("button.clear").on("click", (el) => {
  display.val("");
  allow_op = true;
  allow_decimal = false;
});

$("button.input, button.operation").on("click", (el) => {
  const button = $(el.target);
  const value = button.data("value");
  if (allowInput(value)) {
    display.val(display.val() + button.data("value"));
  }
});
$("button.equals").on("click", (el) => {
  display.val(calc(display.val()));
  allow_op = true;
  allow_decimal = false;
});