<div class="container">
  <section class="top-section">
    <h2>Undoable Counter</h2>
    <div class="do-buttons-container">
      <button class="undo" onclick="undo()">Undo</button>
      <button class="redo" onclick="redo()" disabled>Redo</button>
    </div>
  </section>
  <section class="counter-section">
    <ul class="decrement-buttons">
      <li>
        <button onclick="run(-100)">-100</button>
      </li>
      <li>
        <button onclick="run(-10)">-10</button>
      </li>
      <li>
        <button onclick="run(-1)">-1</button>
      </li>
    </ul>
    <div class="counter-container">
      <p class="current-count">0</p>
    </div>
    <ul class="increment-buttons">
      <li>
        <button onclick="run(100)">100</button>
      </li>
      <li>
        <button onclick="run(10)">10</button>
      </li>
      <li>
        <button onclick="run(1)">1</button>
      </li>
    </ul>
  </section>
  <section class="history-section">
    <h3>History</h3>
    <ul class="history-items"></ul>
  </section>
</div>
.container {
  display: flex;
  align-items: center;
  flex-direction: column;
}

.top-section {
  margin-bottom: 5px;
}

.do-buttons-container {
  display: flex;
  justify-content: space-evenly;
  align-items: center;
  width: 80%;
  margin: auto;
}

.decrement-buttons, .counter-container, .increment-buttons {
  display: inline-block;
  padding: 0;
}

.counter-container {
  margin: 0 5px;
  text-align: center;
}

.decrement-buttons li, .increment-buttons li {
  display: inline-block;
  list-style: none;
}

.history-section {
  text-align: center;
}

.history-items {
  border: 1px solid black;
  height: 200px;
  width: 200px;
  overflow-y: auto;
  padding: 0;
}

.history-items li {
  list-style: none;
}

/*
* https://frontendeval.com/questions/undoable-counter
*
* Create a simple counter with undo/redo functionality
*/

const INITIAL_VALUE = 0;
const MAX_UNDOS = 50;
const counterEl = document.querySelector('.current-count');
const redoBtn = document.querySelector('.redo');
const historyItems = document.querySelector('.history-items');

let currentCount = INITIAL_VALUE;
let oldValue, newValue;
let history = [];
let redos = [];

function run(operation, withClear = true) {
  oldValue = currentCount;
  newValue = currentCount += operation;
  console.log(oldValue, currentCount, newValue);
  updateCounter(currentCount);
  if (history.length === MAX_UNDOS) {
    history = [...history.slice(1), operation];
  } else {
    history.push(operation);
  }
  if (withClear) clearRedoArray();
  recordHistory(oldValue, newValue, operation);
}

function updateCounter(value) {
  counterEl.innerText = value;
}

function undo() {
  const toUndo = history.pop();
  if (toUndo) {
    currentCount -= toUndo;
    updateCounter(currentCount);
    redos.push(toUndo);
    if (redoBtn.disabled) redoBtn.disabled = false;
    removeHistory();
  }
}

function redo() {
  const toRedo = redos.pop();
  run(toRedo, false);
  if (!redos.length) redoBtn.disabled = true;
}

function clearRedoArray() {
  redos = [];
  redoBtn.disabled = true;
}

function recordHistory(oldVal, newVal, operation) {
  const listItem = document.createElement('li');
  const action = document.createElement('span');
  const result = document.createElement('span');
  
  action.innerHTML = `${operation}: `;
  result.innerHTML = `(${oldVal} -> ${newVal})`
  
  listItem.appendChild(action);
  listItem.appendChild(result);
  historyItems.appendChild(listItem);
}

function removeHistory() {
  historyItems.removeChild(historyItems.lastChild);
}
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.