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>
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
//...
});
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;
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;
});
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;
}
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.
=
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;
}
});
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.
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;
}
Das Quadrat einer Zahl erreichen Sie mit einer Multiplikation mit sich selbst oder der Funktion Math.pow(result(true), 2).
Den Logarithmusln
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;
}
window
-Methode eval
berechnet.