JavaScript/Tutorials/Taschenrechner
Das Beispiel baut mit Hilfe eines HTML-Formulars einen halbwegs "echt" aussehenden Taschenrechner auf. Der Anwender kann diesen Taschenrechner wie üblich bedienen. Außerdem berechnet dieser Taschenrechner auch Serienrechnungen wie 1+2+3+4+5+6+7+8+9 und beherrscht die Punkt-vor-Strich-Regel bei Rechnungen wie 1+2*3. Zusätzlich können die Wurzelfunktion, die Quadratfunktion, sowie der natürliche Logarithmus genutzt werden.
Inhaltsverzeichnis
Vorüberlegungen
Dieser Taschenrechner wurde bereits 2001 von Stefan Münz vorgestellt. Damals wurden die Klickbuttons mit onclick
-Attributen versehen, um die Funktionalität zu ermöglichen. Um eine zeitgemäßere dynamische Ereignisbehandlung einzusetzen, wurde auf diese JavaScript-spezifischen Attribute verzichtet, um das Markup streng von der JavaScript-Programmlogik zu trennen. Außerdem wurde eine Tastaturunterstützung implementiert, mit der die Zahleneingabe und die vier Grundrechenarten wie gewohnt bedienbar sind.
HTML
Anhand dieses Beispiels können Sie studieren, wie man Formulareingaben mit JavaScript zur direkten Interaktion verwenden kann.
<form id="calc">
<output>0</output>
<fieldset>
<button>√</button>
<button>x²</button>
<button>ln</button>
<button>C</button>
<button>7</button>
<button>8</button>
<button>9</button>
<button>+</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>-</button>
<button>1</button>
<button>2</button>
<button>3</button>
<button>×</button>
<button>0</button>
<button>.</button>
<button>=</button>
<button>÷</button>
</fieldset>
</form>
Unser Taschenrechner besteht aus einem Formular mit einem output-Element für das Display und einem fieldset mit 20 Buttons. Sie benötigen keine weiteren Attribute oder Klassen.
JavaScript
Schauen wir uns an, wie JavaScript mit dem obigen Dokument interagieren kann.
Initialisierung
"use strict";
document.addEventListener("DOMContentLoaded", function () {
var form = document.getElementById("calc"),
out = document.querySelector("#calc output"),
overwrite = true;
//weiterer Code
//...
});
Im Head findet sich ein Scriptbereich mit einer Handlerfunktion von DOMContentLoaded, in der die HTML-Elemente mit getElementById, bzw. querySelector identifiziert und Variablen zugewiesen werden. Sollten die benötigten Elemente nicht gefunden werden, so verweigert das Script jegliche weitere Ausführung.
Unterstützung für Internet Explorer
Im Internet Explorer sind manche der längst etablierten Möglichkeiten nicht nutzbar. So kann man die Array-Methode includes
nicht benutzen, da im Internet Explorer Array-Objekte diese Methode nicht besitzen. Auch haben im Internet Explorer sogenannte live NodeLists keine Methode forEach
, um über die Elemente der Liste mittels einer Callback-Funktion zu iterieren.
Abhilfe schafft hier ein sogenannter Polyfill:
if (!Array.prototype.includes) {
Array.prototype.includes = function includes(searchElement) {
if (this == null) {
throw new TypeError('this is null or undefined');
}
var object = Object(this),
length = object.length >>> 0;
if (length === 0) {
return false;
}
var start = arguments[1] >> 0,
key = start < 0 ? Math.max(start + length, 0) : start,
currentElement;
while (key < length) {
currentElement = object[key];
if (searchElement === currentElement
|| (
searchElement !== searchElement
&& currentElement !== currentElement
)
) {
return true
}
key ++;
}
return false;
};
}
if (!NodeList.prototype.forEach) NodeList.prototype.forEach = Array.prototype.forEach;
Die erste if-Verzweigung testet, ob ein Array die Methode includes
kennt. Ein solches Vorgehen nennt man feature detection. Ist sie nicht vorhanden, wird dem Prototypen von Array-Objekten eine solche Methode "beigebracht". Ähnlich verhält es sich bei der zweiten if-Verzweigung mit NodeList-Objekten, denen die Funktionalität dadurch gegeben wird, indem man die in Array-Objekten vorhandene forEach
-Methode verwendet.
Absenden des Formulares verhindern
Da der Taschenrechner ein einzeiliges Formularfeld enthält, kann dieses Formular jederzeit mit Betätigung der Eingabetaste abgesendet werden. Ein Absenden ist im Zusammenhang mit dem Taschenrechner jedoch unerwünscht. Deshalb wird mit addEventListener eine Funktion an das submit
-Event des <form>
-Elements gebunden, die verhindert, dass der Browser das Standardverhalten beim Absenden eines Formulars ausführt:
form.addEventListener("submit", function (ev) {
// prevent form submission and page reload
ev.preventDefault();
ev.stopPropagation();
return false;
});
Aus historischen Gründen kann es schon genügen, wenn die Handlerfunktion den Wert false
zurück gibt. Um aber sicher zu verhindern, dass andere Elemente auf das Submit-Event reagieren, wird mit stopPropagation
verhindert, dass die Bekanntgabe an diese stattfindet. Das Abschicken des Formulars, also das Standardverhalten im Ereignisfall von submit
wird mit der Funktion preventDefault
verhindert.
Klickfunktionalität
Innerhalb der DOMContentLoaded-Handlerfunktion befindet sich ab Zeile 175 ein Anweisungsblock, der den Buttons dynamisch eine Funktionalität zuweist.
// button functionalities
document.querySelectorAll("#calc button").forEach(function (b) {
var c = b.textContent;
switch (c) {
case "9":
case "8":
case "7":
case "6":
case "5":
case "4":
case "3":
case "2":
case "1":
case "0":
case ".":
b.addEventListener("click", function () {
input(c);
});
break;
case "+":
case "-":
case "×":
case "÷":
b.addEventListener("click", function () {
operator(c);
});
break;
case "√":
case "x²":
case "ln":
b.addEventListener("click", function () {
extra(c);
});
break;
case "=":
b.addEventListener("click", result);
break;
case "C":
b.addEventListener("click", clear);
break;
}
Mit b.textContent wird der Textknoten der geklickten Buttons ausgelesen und in einer switch-Fallunterscheidung verarbeitet.
Falls es sich um Zahlen handelt, wird die Eingabe c
, also der Textinhalt des jeweiligen Buttons, der Funktion input
zur weiteren Verarbeitung übergeben.
Auch die arithmetischen Operatoren +-/*
werden zur weiteren Verarbeitung einer Funktion (hier operator
) übergeben.
Die mathematischen Sonderfunktionen √
, x²
und ln
werden über die Funktion extra aufgerufen.
Ein Klick auf =
ruft die Funktion result auf.
Tastatureingabe
Um Tastatureingaben zu erhalten, lauscht unser Script auf das Ereignis keypress
, indem wir mit addEventListener eine passende Funktion einrichten, die mögliche Tastatureingaben auswertet.
document.addEventListener("keypress", function (ev) {
// decimal point
if ([44, 46].includes(ev.charCode)) {
// , .
input(".");
}
// digits
if ([48, 49, 50, 51, 52, 53, 54, 55, 56, 57].includes(ev.charCode)) {
// 0-9
input(ev.charCode - 48);
}
// operators
if ([42, 43, 45, 47].includes(ev.charCode)) {
// * + - /
operator(
["×", "+", "-", "÷"][
[42, 43, 45, 47].indexOf(ev.charCode)
]
);
}
// result
if (ev.charCode == 61) {
// =
result();
}
// clear
if ([67, 99].includes(ev.charCode)) {
// C, c
clear();
}
// logarithm
if ([76, 108].includes(ev.charCode)) {
// L, l
extra("ln");
}
// root
if ([82, 114].includes(ev.charCode)) {
// R, r
extra("√");
}
// square
if ([83, 115].includes(ev.charCode)) {
// S, s
extra("x²");
}
// additional clear and result keys
switch (ev.code) {
// <delete> and <backspace> to clear display
case "Backspace":
case "Delete":
clear();
break;
// both <enter> keys to display result
case "Enter":
case "NumpadEnter":
result();
break;
}
});
Das Ereignis-Objekt wird in der Variablen ev
in unserer Funktion ausgewertet, denn es enthält Eigenschaften, die Aufschluss darüber geben, welche Tasten benutzt wurden.
Bei den Zifferntasten ist es sinnvoll, ihren ASCII-Wert in der Eigenschaft charCode
des Ereignis-Objekts zu verarbeiten. Gleiches gilt für die Tasten, die eine erweiterte Berechnung (Logarithmus, Wurzel oder Quadrat) auslösen sollen, oder die einen Dezimalpunkt bedeuten können (hier unterstützen wir ,
und .
), sowie für das Istgleichzeichen.
Um eine Eingabetaste (Enter-Taste) zu ermitteln, nützt uns die Eigenschaft charCode
nichts, da sie kein darstellbares Zeichen erzeugt. Hier braucht es die Eigenschaft code
, in der sprichwörtlich der interne Name der Taste gespeichert ist. Um nun sowohl die Enter-Taste bei den Buchstaben, als auch die Enter-Taste auf dem Ziffernblock zu unterstützen, müssen beide Namen (hier Enter
und NumpadEnter
) verarbeitet werden. Ähnliches gilt auch für die Lösch-Tasten, mit denen unsere Anzeige wieder auf Null zurück gesetzt werden können soll.
Wenn man testen will, ob ein Array einen bestimmten Wert enthält (z.B. ob eine Zifferntaste gedrückt wurde), benutzt man die Methode includes
, will man aber wissen, an welcher Stelle dieser Wert im Array steht (z.B. welche der Operatoren-Tasten benutzt wurde), verwendet man indexOf
.
mathematische Sonderfunktionen
In der Funktion extra()
werden „kompliziertere“ Berechnungen angestellt, die, wie Sie sehen werden, aber durch Methoden des Math-Objekts in jeweils nur einer Zeile erledigt sind.
function extra (type) {
switch (type) {
case "√":
out.textContent = Math.sqrt(result(true));
break;
case "x²":
out.textContent = Math.pow(result(true), 2);
break;
case "ln":
out.textContent = Math.log(result(true));
break;
}
overwrite = true;
}
Für Wurzelberechnungen stellt das Math-Objekt die Funktion Math.sqrt() zu Verfügung.
Das Quadrat einer Zahl erreichen Sie mit einer Multiplikation mit sich selbst oder der Funktion Math.pow(result(true), 2).
Den Logarithmus ln
berechnen Sie mit Math.log(result(true)).
Ergebnisberechnung
Bei einem Klick auf =
(oder bei passender Benutzung der Tastatur) berechnet die Funktion result(noDisplay)
das Ergebnis.
function result (noDisplay) {
var input = out.textContent,
r = 0;
// replace × with * and ÷ with / for eval()
input = input.replace(/×/g, "*").replace(/÷/g, "/");
// remove anything else that is not allowed here
input = input.replace(/[^0-9. +\-*\/]/g, "");
try {
r = eval(input);
} catch (e) {
r = 0;
}
if (noDisplay !== true) {
out.textContent = r;
overwrite = true;
}
return r;
}
Mit einem RegEx werden nur Ziffern, der Dezimalpunkt und die vier Operatoren als gültige Zeichen für die Berechnung zugelassen (inklusive möglicher Leerzeichen). Dabei werden die üblichen Tastenbeschriftungen für Multiplikation und Division durch ihre JavaScript-Entsprechungen ersetzt.
Das eigentliche Rechenergebnis wird mit der window
-Methode eval
berechnet.