Skip to content

HTML, CSS and first steps

Der Taschenrechner besteht aus zwei Teilen:

  • dem Display und
  • den Tasten
<div class="calculator">
<div class="calculator-display">0</div>
<div class="calculator-keys">...</div>
</div>

Die Tasten müssen auf einen Klick reagieren, daher verwenden wir dafür Buttons:

<div class="calculator-keys">
<button class="calculator-key">+</button>
<button class="calculator-key">-</button>
<button class="calculator-key">&times;</button>
<button class="calculator-key">/</button>
<button class="calculator-key">1</button>
<button class="calculator-key">2</button>
<button class="calculator-key">3</button>
<button class="calculator-key">4</button>
<button class="calculator-key">5</button>
<button class="calculator-key">6</button>
<button class="calculator-key">7</button>
<button class="calculator-key">8</button>
<button class="calculator-key">9</button>
<button class="calculator-key">0</button>
<button class="calculator-key">.</button>
<button class="calculator-key">AC</button>
<button class="calculator-key">=</button>
</div>

Diese Buttons ordnen wir in einem Grid an:

.calculator {
display: grid;
// ...
}

Wir können die Tasten in fünf verschiedene Gruppen einteilen:

  1. Operatoren: +, -, ×, /
  2. Ziffern: 0 - 9
  3. Dezimalpunkt: .
  4. Gleichheitszeichen: =
  5. Löschen: AC

Um die Art der Taste und deren Wert zu identifizieren, verwenden wir zwei custom attributes:

  • data-button-type - sagt uns die Art der Taste (Operator, Ziffer, …)
  • data-key - sagt uns den Wert der Taste

Diese data-attributes fügen wir nun den Tasten hinzu:

<div class="calculator-keys">
<button class="calculator-key" data-key="plus" data-button-type="operator">+</button>
<button class="calculator-key" data-key="minus" data-button-type="operator">-</button>
<button class="calculator-key" data-key="times" data-button-type="operator">&times;</button>
<button class="calculator-key" data-key="divide" data-button-type="operator">/</button>
<button class="calculator-key" data-key="1" data-button-type="number">1</button>
<button class="calculator-key" data-key="2" data-button-type="number">2</button>
<button class="calculator-key" data-key="3" data-button-type="number">3</button>
<button class="calculator-key" data-key="4" data-button-type="number">4</button>
<button class="calculator-key" data-key="5" data-button-type="number">5</button>
<button class="calculator-key" data-key="6" data-button-type="number">6</button>
<button class="calculator-key" data-key="7" data-button-type="number">7</button>
<button class="calculator-key" data-key="8" data-button-type="number">8</button>
<button class="calculator-key" data-key="9" data-button-type="number">9</button>
<button class="calculator-key" data-key="0" data-button-type="number">0</button>
<button class="calculator-key" data-key="decimal" data-button-type="decimal">.</button>
<button class="calculator-key" data-key="clear" data-button-type="clear">AC</button>
<button class="calculator-key" data-key="equal" data-button-type="equal">=</button>
</div>

Wenn wir einen Taschenrechner benutzen, können fünf verschiedene Dinge passieren:

  • Wir klicken auf eine Zahl
  • Wir klicken auf einen Operator
  • Wir klicken auf den Dezimalpunkt
  • Wir klicken auf Istgleich
  • Wir klicken auf Löschen

Wir müssen also überlegen, was passiert, wenn diese Tasten gedrückt werden. Dass es dabei viele Permutationen gibt, macht die Sache einigermaßen kompliziert.

Gehen wir also Schritt für Schritt vor.

Als erstes selektieren wir den Rechner selbst. Für die Tasten verwenden wir das event delegation pattern und ein early return: wir prüfen zuerst, ob direkt auf einen Button geklickt wurde, und wenn nicht, verlassen wir den EventListener gleich wieder.

const calculator = document.querySelector('.calculator');
const calculatorButtonsDiv = calculator.querySelector('.calculator-keys');
calculatorButtonsDiv.addEventListener = ('click', e => {
if(!e.target.closest('button')) return;
});

Die fünf verschiedenen Arten von Tasten können wir mit dem data-button-type Attribut identifizieren. Mit der Eigenschaft dataset des button Objekts können wir auf die data attributes zugreifen ( button.dataset) und den Wert in einer Variablen speichern.

calculatorButtonsDiv.addEventListener('click', e => {
// ...
const button = e.target;
const { buttonType } = button.dataset;
if (buttonType === 'number') {
console.log('Pressed number');
}
if (buttonType === 'decimal') {
console.log('Pressed decimal');
}
if (buttonType === 'operator') {
console.log('Pressed operator');
}
if (buttonType === 'equal') {
console.log('Pressed equal');
}
if (buttonType === 'clear') {
console.log('Pressed clear');
}
})

Also nochmal: wenn wir einen Taschenrechner in die Hand nehmen, dann drücken wir üblicherweise eine dieser fünf Arten von Tasten in einer ganz bestimmten Reihenfolge (nämlich einer Reihenfolge, die einen Sinn ergibt und ein Ergebnis berechnet):

  • eine Zahl
  • einen Operator
  • den Dezimalpunkt
  • das Istgleich-Zeichen
  • die Löschtaste

Alle fünf Typen auf einmal zu berücksichtigen verursacht einen mental overload. Deshalb überlegen wir uns, was ein “normaler Mensch” eintippen würde. Dieses was würde ein normaler Mensch tun nennt man den happy path.

Nennen wir unseren “normalen Menschen” Mary: Wenn Mary den Taschenrechner in die Hand nimmt, wird sie vermutlich als erstes eine Ziffer eintippen.

Wenn der Taschenrechner erstmals aufgerufen wird, zeigt er im Display vermutlich die Ziffer 0 an. Diese Ziffer wollen wir mit der eingegebenen Ziffer ersetzen. Dazu müssen wir zuerst herausfinden, welchen Wert die angeklickte Ziffer hat. Diesen Wert finden wir im key data attribute.

calculatorButtonsDiv.addEventListener('click', e => {
const button = e.target;
const { buttonType, key } = button.dataset;
// ...
});

Als nächstes müssen wir das aktuell angezeigte Resultat finden. Wir selektieren .calculator-display, lesen den textContent aus und speichern diesen in einer neuen Variable result.

const display = calculator.querySelector('.calculator-display');
calculatorButtonsDiv.addEventListener('click', e => {
const button = e.target;
const { buttonType, key } = button.dataset;
const result = display.textContent;
// ...
});

Wenn also aktuell im Display 0 angezeigt wird, wollen wir das durch die eingegebene Ziffer ersetzen:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (result === '0') {
display.textContent = key;
}
});

Für denn Fall, dass die Ziffer im Display nicht 0 beträgt (die Wahrscheinlichkeit ist groß, dass bereits eine Ziffer eingegeben wurde), wollen wir die neu eingegebene Ziffer an die bestehende anhängen:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (result === '0') {
display.textContent = key;
} else {
display.textContent = result + key;
}
// ...
});

Damit können jetzt beliebig lange Ziffernketten eingegeben werden. Der nächste happy path Fall wäre entweder ein Klick auf den Dezimalpunkt oder auf einen Operator. Da es nur einen Dezimalpunkt gibt, aber vier Operatoren, schauen wir uns zuerst den Dezimalpunkt an.

Wenn der Dezimalpunkt geklickt wird, wollen wir einen . an das Ergebnis anhängen:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'decimal') {
display.textContent = result + '.';
}
// ...
});

If Mary clicks another number after the decimal key

Section titled “If Mary clicks another number after the decimal key”

Für den Fall, dass Mary nach dem Dezimalpunkt wieder eine Ziffer klickt, brauchen wir nichts tun, das wird nämlich schon in der ersten if Abfrage erledigt.

Die Operatoren sind +, -, &times; und /. Wenn eine dieser Tasten gedrückt wird, wollen wir sie hervorheben, damit Mary weiß, dass der Operator aktiv ist. Wir hängen also eine is-pressed Klasse an den geklickten Operator an.

// ...
// pressed state for operator
if (buttonType = 'operator') {
button.classList.add('is-pressed');
}

Dann kann Mary weiterklicken …

If Mary clicks a number key after an operator key

Section titled “If Mary clicks a number key after an operator key”

Was auch immer jetzt auf dem Display steht, wir müssen die Anzeige resetten und die neu eingegebene Nummer ins Display schreiben. Gleichzeitig müssen wir den hervorgehobenen Status des Operators wieder aufheben.

Zuerst letzteres: wir entfernen is-pressed vom Button:

// ...
// release operator pressed state
const operatorKeys = [...caclulatorButtonsDiv.children].filter(
button => button.dataset.buttonType === 'operator');
operatorKeys.forEach(button => button.classList.remove('is-pressed'));
// all the if-statements ...

Um die Anzeige zu resetten, müssen wir wissen, ob der vorhergehende Button ein Operator war. Eine Möglichkeit ist das Setzen eines custom attributes. Nennen wir es data-previous-button-type.

calculatorButtonsDiv.addEventListener('click', e => {
// ..
calculator.dataset.previousButtonType = buttonType;
});

Falls der zuvor geklickte Button ein Operator war, wollen wir die geklickte Nummer anzeigen:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
const { previousButtonType } = calculator.dataset;
if (buttonType === 'number') {
if (result === '0') {
display.textContent = key;
} else {
display.textContent = result + key;
}
if (previousButtonType === 'operator') {
display.textContent = key;
}
}
// ...
});

Wir sind nun fast fertig mit dem Happy Path. Mary will nun das Ergebnis wissen und klickt auf den Istgleich-Button.

Der Rechner soll nun ein Ergebnis ausrechnen, das auf den folgenden drei Werten basiert:

  • der erste Wert (bevor der Operator geklickt wurde)
  • der Operator
  • der zweite Wert (der, der aktuell angezeigt wird)

Um den ersten Wert zu erhalten, müssen wir das Ergebnis speichern, bevor wir es durch die zweite Nummer ersetzen. Wir machen das mit einem neuen custom attribute, data-first-value:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'operator') {
button.classList.add('is-pressed');
calculator.dataset.firstValue = result;
// ...}
});

Gleichzeitig müssen wir auch den Operator speichern:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'operator') {
button.classList.add('is-pressed');
calculator.dataset.firstValue = result;
calculator.dataset.operator = key;
}
// ...
});

Jetzt haben wir alle Werte, die wir brauchen. Loggen wir sie sicherheitshalber einmal in die Konsole:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'equal') {
const firstValue = calculator.dataset.firstValue;
const operator = calculator.dataset.operator;
const secondValue = result;
console.log(firstValue);
console.log(operator);
console.log(secondValue);
}
});

Jetzt können wir rechnen! Das Ergebnis zeigen wir dann im Display an.

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'equal') {
const firstValue = calculator.dataset.firstValue;
const operator = calculator.dataset.operator;
const secondvalue = result;
let result;
if (operator === 'plus') newResult = firstValue + secondValue;
if (operator === 'minus') newResult = firstValue - secondValue;
if (operator === 'times') newResult = firstValue * secondValue;
if (operator === 'divide') newResult = firstValue / secondValue;
display.textContent = newResult;
}
// ...
});

Aber jetzt haben wir ein Problem! Das Resultat stimmt nicht! Warum? Weil die Werte als Strings gespeichert wurden und der Wert aus dem Display auch ein String ist. Die Strings werden beim Addieren einfach nur zusammengesetzt.

Um den Fehler zu beheben, müssen wir zuerst firstValue? und secondValuein Zahlen umwandeln. Das geht mitparseIntoderparseFloat. parseIntmacht ganze Zahlen,parseFloat` Dezimalzahlen, letzteres brauchen wir in diesem Fall.

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'equal') {
const firstValue = parseFloat(calculator.dataset.firstValue);
const operator = calculator.dataset.operator;
const secondValue = parseFloat(result);
// ...
}
});

Die Löschen-Taste funktioniert folgendermaßen:

  1. Wenn wir Löschen klicken, wenn AC angezeigt wird, werden alle Daten im Rechner gelöscht
  2. Wenn wir Löschen klicken, wenn CE angezeigt wird, wird die aktuell angezeigte Nummer auf 0 gesetzt.

Wenn wir auf irgendeine Taste drücken (außer AC), dann soll AC sich zu CE ändern.

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType !== 'clear') {
const clearButton = calculator.querySelector('[data-button-type-clear]');
clearButton.textContent = 'CE';
}
});

Wenn Mary auf CE klickt, wollen wir die aktuell angezeigte Nummer auf Null setzen:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'clear') {
display.textContent = '0';
button.textContent = 'AC';
}
// ...
});

Wenn Mary auf AC klickt, wollen wir alle Werte aus dem Rechner entfernen:

calculatorButtonsDiv.addEventListener('click', e => {
// ...
if (buttonType === 'clear') {
if (button.textContent === 'AC') {
delete calculator.dataset.firstValue;
delete calculator.dataset.operator;
}
display.textContent = '0';
button.textContent = 'AC';
}
// ...
});