Articolo originale: JavaScript DOM Tutorial – How to Build a Calculator App in JS

Usando JavaScript, trascorrerai molto tempo lavorando sulle pagine web. In effetti, mentre si utilizza JavaScript, la pagina web è il posto in cui si svolgono tutte le cose eccitanti e importanti.

Per uno sviluppatore JavaScript una pagina web è un grande documento, poiché ogni elemento della pagina è connesso (come una grande famiglia). È tutto composto da genitori (parentNodes) e figli (childNodes). Il Modello a Oggetto del Documento è il nome di questa famiglia (DOM).

In questo articolo, imparerai a conoscere il DOM insieme a cicli ed eventi costruendo una semplice app calcolatrice iOS.

Sommario

  1. Prerequisiti
  2. Cosa è il DOM?
  3. Come selezionare elementi nel DOM
  4. Come creare e aggiungere elementi nel DOM
  5. Come modificare elementi nel DOM
  6. Come rimuovere elementi dal DOM
  7. Cicli e iterazioni
  8. Eventi del DOM
  9. Gestione degli eventi di Javascript
  10. Come costruire l'app Calcolatrice
  11. Come aggiungere funzionalità all'app Calcolatrice
  12. Conclusione

Prerequisiti

Cosa ti servirà per sfruttare al meglio questo tutorial?

Ti servirà un browser, un editor di testo (VScode), e qualche conoscenza di base di JavaScript. Per avvantaggiarti, ti consiglio di dare un'occhiata a uno di questi ottimi corsi introduttivi (risorse in inglese).

Puoi anche leggere questo ottimo articolo sui fondamenti di JavaScript.

Se hai già letto l'articolo e hai partecipato alla sfida, ecco la mia semplice soluzione.

Questo articolo presuppone anche che tu abbia una comprensione di base di HTML e CSS, nonché di come funziona il web.

Detto questo, iniziamo.

Cosa è il DOM?

Il Modello a Oggetto del Documento (DOM) è una struttura logica che definisce come gli elementi in un documento possono essere manipolati e modificati. Poiché il DOM è strutturato in questo modo, è comunemente indicato come un albero in cui tutto è collegato.

Ecco un esempio:

F2-Cq8eWERd3pCUseD4CprU2HNN1pg7Bp-znXM5KF5XjB60ADt4HVggy9bmpGzo_1rcrrqjfGbNWywdpoaONDU-3asY4FlNdqjscx6y_X4h-iTgIu60E11IYE26zmKTC2nXcQi0k1OfkkHR3t5snxtI

È possibile accedere, modificare, eliminare o aggiungere nuovi elementi o contenuto a un documento utilizzando il DOM. In sintesi, il DOM ti consente di manipolare quasi tutto su una pagina web.

Rimboccati le maniche e iniziamo subito a lavorare ora che abbiamo una migliore comprensione di cosa sia il DOM.

Come Selezionare Elementi nel DOM

Una delle prime cose che farai lavorando con il DOM è selezionare elementi nel documento.

In effetti, devi prima imparare come accedere agli elementi nel DOM, affinché tu possa manipolarli.

È possibile selezionare (o accedere a) elementi in alcuni modi diversi. Di seguito, esamineremo quei pochi che ti serviranno maggiormente:

Come Selezionare Elementi DOM per ID

Con getElementById(), puoi selezionare qualsiasi elemento su una pagina web che abbia l'attributo id. Tutto quello che ti serve è passare il valore di id dell'elemento che desideri selezionare.

...
 <body>
  <form>
    <input type="text" />
    <button id="btn">Submit</button>
  </form>
...

Ecco il codice JavaScript:

const button = document.getElementById("btn");

console.log(button)

È importante ricordare che il metodo getElementById seleziona solo il primo elemento nella pagina, qualora ci fossero diversi elementi con lo stesso id.

Nota che gli id dovrebbero essere univoci e non ci dovrebbero mai essere più elementi con lo stesso id in una pagina.

...
 <body>
  <form>
    <input type="text" />
    <button id="btn">one</button>
    <button id="btn">two</button>
    <button id="btn">three</button>
     ...
  </form>
...

Ecco il codice JavaScript:

const button = document.getElementById("button");

console.log(button); // viene selezionato solo il primo elemento button

Come Selezionare Elementi DOM per Nome di Classe

Puoi usare il metodo getElementsByClassName per selezionare un qualsiasi elemento che abbia una classe (l'attributo class):

...
<body>
    <form>
      <input type="text" />
      <button class="btn">Submit</button>
    </form>
  </body>
...

Il codice JavaScript:

const button = document.getElementsByClassName("btn");

console.log(button);

Ricorda che il nome inglese della funzione è al plurale (getElements). Il che significa che tutti gli elementi che hanno una classe btn saranno identificati dal selettore e saranno aggiunti a una interfaccia di tipo HTMLCollection (un array). Ricordi cos'è un array, vero?

...
 <body>
  <form>
    <input type="text" />
    <button class="btn">one</button>
    <button class="btn">two</button>
    <button class="btn">three</button>
     ...
  </form>
...

Il codice JavaScript:

const buttons = document.getElementsByClassName("btn");

// ritorna un array di tipo HTMLCollection 
// che contiene tutti gli elementi con classe "btn"
console.log(buttons); 

Come Selezionare Elementi DOM per Nome del Tag

getElementsByTagName funziona in modo simile a getElementsByClassName: restituisce un array di tipo HTMLCollection contenente tutti gli elementi che hanno un certo nome di tag trovati nel documento, gli elementi paragrafo (p) nell'esempio che segue:

...
  <body>
    <article class="article">
      <p>This is a paragraph</p>
      <p>This is the second paragraph</p>
      <p>This is the third paragraph</p>
      <p>This is fourth paragraph</p>
    </article>
  </body>
...

Il codice JavaScript:

const p_tag = document.getElementsByTagName("p");

console.log(p_tag);

Come Selezionare Elementi DOM usando i Selettori CSS

Ecco i miei selettori preferiti: querySelector() e querySelectorAll(). Con questi selettori, puoi selezionare qualsiasi elemento nel DOM nello stesso modo nel quale li selezioneresti con CSS:

const img = document.querySelector("img"); // seleziona elemento per nome tag
const input = document.querySelector("input[type='text']");
const last_div = document.querySelector("form > *:last-child");
const button = document.querySelector(".btn") // seleziona elemento per classe
const button = document.querySelector("#btn") // seleziona elemento per id

Per selezionare un elemento singolo si usa querySelector(). Se il selettore trova corrispondenza con più elementi, verrà restituito solo il primo.

...
  <body>
    <article class="article">
      <p>This is a paragraph</p>
      <p>This is the second paragraph</p>
      <p>This is the third paragraph</p>
      <p>This is fourth paragraph</p>
    </article>
  </body>
...

Il codice JavaScript:

const p_tag = document.querySelector("p")

console.log(p_tag)

querySelectorAll(), d'altro canto, selezionerà tutti gli elementi nel documento che trovano corrispondenza con il selettore, conservandoli in un oggetto NodeList (un array) simile a quelli visti in precedenza.

const p_tags = document.querySelectorAll("p");

console.log(p_tags);

Vale la pena notare che quando utilizzi document.querySelector(), stai cercando l'elemento nell'intero documento. Ma quando esegui elemento.querySelector(), stai solo cercando nella parte di DOM contenuta nell'elemento selezionato.

Considera l'esempio seguente con querySelector(): il selettore cercherà corrispondenza solo all'interno dell'elemento.

...
 <body>
    <form>
      <input type="text" />
      <button class="btn">one</button>
      <button class="btn">two</button>
      <button class="btn">two</button>
    </form>
    <button class="btn">button one outside the form</button>
    <button class="btn">button two outside the form</button>
  </body>
...

Il codice JavaScript:

const form = document.querySelector("form")

const form_btns = form.querySelectorAll(".btn")

console.log(form_btns); // vengono selezionati soli i pulsanti all'interno dell'elemento form

form.querySelectorAll() selezionerà solo gli elementi con classe "btn" all'interno dell'elemento form. Questo vale per tutti gli elementi e i selettori.

Come Creare e Aggiungere Elementi nel DOM

Dopo avere imparato come si selezionano gli elementi HTML già presenti nel DOM, proviamo a crearne dei nostri usando JavaScript.

Ci sono alcuni passi da compiere per aggiungere elementi al DOM tramite JavaScript. Esamineremo ciascuno di essi di seguito.

Come Creare un Elemento DOM

JavaScript richiede che qualsiasi elemento sia creato prima di poterlo aggiungere al DOM. Per fare questo usiamo il metodo document.createElement().

const new_div = document.createElement("div");
const new_paragraph = document.createElement("p");
const new_link = document.createElement("a");
const new_image = document.createElement("img");

Nel codice qui sopra abbiamo creato alcuni elementi in JavaScript e non li abbiamo ancora aggiunti al DOM. Tuttavia, sono ancora semplicemente elementi senza attributi o contenuto di testo, quindi correggiamolo subito.

Come Impostare gli Attributi degli Elementi

Per impostare gli attributi, come aggiungere una classe, modificare un ID o modificare l'attributo src, usiamo semplicemente il metodo setAttribute() sull'elemento.

Il metodo setAttribute("attributo", "valore") riceve due parametri, il nome dell'attributo e il valore da assegnare.

const new_div = document.createElement("div");
const new_paragraph = document.createElement("p");
const new_link = document.createElement("a");
const new_image = document.createElement("img");

//impostazione degli attributi
new_div.setAttribute("class", "my_div"); // impostazione di un attributo class
new_paragraph.setAttribute("id", "my_paragraph"); // impostazione di un attributo id
new_link.setAttribute("href", "https://example.com"); // impostazione di un attributo href
new_image.setAttribute("src", "https://image-link.png"); // impostazione di un attributo src

Come Aggiungere un Contenuto di Testo

Alcuni degli elementi appena creati devono essere dotati di testo affinché possano essere usati nel documento, anche dopo l'aggiunta degli attributi.

Per creare del testo e aggiungerlo ai nuovi elementi creati, usa il metodo createTextNode().

const new_div = document.createElement("div");
const new_paragraph = document.createElement("p");
const new_link = document.createElement("a");
const new_image = document.createElement("img");

...

//crea gli oggetti textNode
const new_div_text = document.createTextNode("Hello world");
const new_paragraph_text = document.createTextNode("This is a paragraph");
const new_link_text = document.createTextNode("Click to visit link");

//aggiunge gli oggetti textNode agli elementi
new_div.append(new_div_text);
new_paragraph.append(new_paragraph_text);
new_link.append(new_link_text);

console.log(new_div, new_paragraph, new_link, new_image);

Adesso possiamo effettivamente aggiungere i nuovi elementi creati al DOM.

Come Aggiungere Elementi al DOM

L'unico modo per aggiungere  nuovi elementi al DOM è di inserirli all'interno di un elemento esistente.

...
 <body>
    <div class="container">
      
    </div>
  </body>
...

Il codice JavaScript:

const new_div = document.createElement("div");
const new_paragraph = document.createElement("p");
const new_link = document.createElement("a");
const new_image = document.createElement("img");

...

// aggiunge elementi al DOM

// seleziona l'elemento genitore
const container = document.querySelector(".container");

container.appendChild(new_div);
container.appendChild(new_paragraph);
container.appendChild(new_link);
container.appendChild(new_image);

Il nuovo elemento sarà aggiunto come figlio di quello esistente selezionato usando il metodo appendChild().

Naturalmente ci sono più modi per aggiungere nuovi elementi al DOM, ma ti lascio a scoprirli da solo.

Come Modificare Elementi nel DOM

Oltre a creare e aggiungere elementi al DOM, JavaScript ci consente anche di modificare elementi DOM già esistenti. Possiamo cambiare il loro contenuto, aggiungere o rimuovere attributi o persino cambiare i loro stili.

Come Modificare il Testo

Usa textContent o innerText per modificare il testo di qualsiasi elemento. Vedi l'esempio qui sotto:

...
  <body>
    <article class="article">
      <p>This is a paragraph</p>
    </article>
  </body>
...

Il codice JavaScript:

const p_tag = document.querySelector("article p");

// modifica il testo contenuto usando textContent
p_tag.textContent = "Overide existing text";

// modifica il testo contenuto usando innerText
p_tag.innerText = "Overide existing text using innerText";

Oltre al testo possiamo anche modificare gli  attributi.

Come Modificare gli Attributi

Puoi usare il metodo setAttribute() per modificare qualsiasi attributo che un elemento possa avere, oltre ad aggiungerne dei nuovi, come abbiamo già visto in una sezione precedente.

...
<body>
    <article class="article">
      <p class="my_paragraph">This is a paragraph</p>
    </article>
  </body>
...

Il codice JavaScript:

const p_tag = document.querySelector("article p");

p_tag.setAttribute("class", "new_paragraph");

console.log(p_tag);

Dato che stiamo parlando di modifiche agli attributi, possiamo anche aggiungere, eliminare, e alternare tra aggiungere o eliminare un attributo di classe per un elemento usando rispettivamente i metodi classList.Add(), classList.remove() e classList.toggle() .

const p_tag = document.querySelector("article p");

p_tag.classList.add("active") // aggiunge la classe "active" all'elemento
p_tag.classList.remove("active") // elimina la classe "active" dall'elemento
p_tag.classList.toggle("active") // se l'elemento ha già una classe "active" la rimuove, altrimenti la aggiunge

Come Modificare lo Stile degli Elementi

Puoi cambiare lo stile degli elementi direttamente in JavaScript. Seleziona semplicemente l'elemento e aggiungi la proprietà style seguita dallo stile CSS che vuoi usare.

...
<body>
    <div class="container">
      <p>This is a paragraph</p>
    </div>
  </body>
...

Il codice JavaScript:

const container = document.querySelector(".container");

container.style.display = "none";

Puoi aggiungere un qualunque stile CSS. La differenza principale è che i nomi delle proprietà CSS possono esssere separati da trattini (-), per esempio background-color. In Javascript, tuttavia, le corrispondenti proprietà CSS sono scritte usando la notazione camelCase, backgroundColor.

container.style.backgroundColor = "red";

Come Rimuovere Elementi dal DOM

Tutto quello che si può creare si può anche rimuovere, compresi gli elementi del DOM. Per esempio, puoi rimuovere un figlio da un elemento genitore usando removeChild(). Allo stesso modo, è possibile rimuovere l'elemento p nel div nell'esempio seguente utilizzando removeChild():

...
<body>
    <div class="container">
      <p>This is a paragraph</p>
    </div>
  </body>
...

Il codice JavaScript:

const parent_element = document.querySelector(".container");
const child_element = document.querySelector(".container p");

parent_element.removeChild(child_element);

Qui sopra abbiamo selezionato l'elemento genitore, quindi rimosso il figlio. Puoi anche rimuovere l'elemento p direttamente, prima selezionandolo, quindi usando remove().

const child_element = document.querySelector(".container p");

child_element.remove();

Spetta a te la scelta sul come eliminare un elemento.

Cicli e Iterazioni

I cicli ci consentono di eseguire compiti ripetuti, come stampare un numero diverse volte o iterare attraverso un array.

Con i cicli, possiamo accedere a ogni elemento in un array, come si vede nell'esempio che segue, in cui stampiamo tutti gli elementi in un array.

const my_array = [1, 3, 5, "hello", 55, "60", "JavaScript"];

for (let i = 0; i < my_array.length; i++) {
  const item = my_array[i];

  console.log(item);
}

Quando esegui il codice qui sopra, vengono stampati uno a uno tutti gli elementi dell'array.

Se volessimo invece fermarci a un certo punto? Possiamo usare l'istruzione break per uscire dal ciclo. Per esempio potremmo decidere di interrompere la stampa degli elementi in quell'array quando raggiungiamo il quinto elemento.

const my_array = [1, 3, 5, "hello", 55, "60", "JavaScript"];

for (let i = 0; i < my_array.length; i++) {
  const item = my_array[i];

  if (i == 5) {
    break;
  }

  console.log(item);
}

L'istruzione break interrompe l'esecuzione del ciclo.

Molti anni fa, il ciclo for (quello qui sopra) era un gran metodo per iterare sugli elementi di un array. Tuttavia il mondo e JavaScript si sono evoluti, e ora abbiamo sistemi più fantasiosi e veloci per iterare sugli array. Esploreremo i più comuni.

Come Usare i Metodi forEach() e map()

Il metodo forEach() è uno dei nuovi modi fantasiosi per iterare sugli array. Il metodo forEach() eseguirà una funzione che tu definisci per ogni elemento di un array.

const my_array = [1, 3, 5, "hello", 55, "60", "JavaScript"];

my_array.forEach(function (item) {
  console.log(item);
});

Quando usi le funzioni freccia è anche più conciso:

const my_array = [1, 3, 5, "hello", 55, "60", "JavaScript"];

my_array.forEach((item) => console.log(item));

La funzione che fornisci deve ricevere almeno un parametro che è l'elemento corrente nell'array. Gli altri due parametri sono facoltativi: l'indice (l'indice dell'elemento corrente, che è un numero) e l'ultimo parametro è l'array originale sul quale si sta iterando.

my_array.forEach(funzione_callback, indice, array_originale)

L'unica differenza tra Array.map() e Array.forEach() è che Array.map() restituirà un nuovo array dopo l'esecuzione dalla funzione di callback, mentre Array.foreach() modifica sul posto l'array sul quale lavora e non restituisce nulla. Array.map() farà esattamente ciò che farà Array.foreach(), vale a dire eseguire una funzione per ogni elemento nell'array.

Diversamente dal ciclo for, l'istruzione break non ha efficacia nei metodi Array.forEach() e Array.map().

Il ciclo for...of

Il ciclo for...of, che accetta una variabile e l'array sul quale vuoi iterare, è uno dei nuovi fantasiosi metodi con i quali puoi eseguire un ciclo su qualsiasi elemento iterabile.

const my_array = [1, 3, 5, "hello", 55, "60", "JavaScript"];

for (const item of my_array) {
 console.log(item);
}

L'istruzione break può essere usata in un ciclo for...of , proprio come in un normale ciclo for.

const my_array = [1, 3, 5, "hello", 55, "60", "JavaScript"];
for (const item of my_array) {
  console.log(item);
  if (item >= 6) {
    break;
  }
}

Eventi del DOM

Gli utenti eseguiranno una varietà di azioni utilizzando la tua applicazione, come cliccare sui pulsanti, passare con il mouse su elementi sullo schermo, inviare dei moduli, aggiornare le pagine e altre attività che gli utenti possono effettuare.

In JavaScript, tutte queste interazioni utente sono note come Eventi.

Ci sono molti eventi in JavaScript, pertanto  il linguaggio contiene ascoltatori di eventi (event listener) che possono essere usati per rispondere a ciascuno. In questo post, tuttavia, parleremo solo dei più comuni.

EVENTO DESCRIZIONE
click Viene attivato quando un utente fa click su un elemento.
mouseover Viene attivato quando si passa con il mouse su un elemento del DOM.
input Viene attivato quando cambia il valore di un elemento input o select.
submit Viene attivato quando viene inviato un form.
keydown Viene attivato quando viene premuto un tasto della tastiera.
keyup Viene attivato quando viene rilasciato un tasto precedentemente premuto. È il contrario di keydown.
DOMContentLoaded Viene attivato dopo che il DOM è stato completamente caricato ed elaborato, e prima che sia scaricata una qualsiasi risorsa esterna (come immagini o css).
load Al contrario di DOMContentLoaded, questo evento non si attiva prima che siano stati caricati tutti gli elementi del DOM e le risorse esterne.

Ci sono molti altri eventi in JavaScript, ma ci fermeremo qui. Puoi visualizzare ulteriori eventi nel riferimento agli eventi del DOM di MDN (risorsa in inglese).

Ora che abbiamo visto diversi tipi di eventi, vediamo come potremmo rispondere agli stessi.

Gestione degli Eventi di JavaScript

Il metodo addEventListener è consigliato per gestire gli eventi in JavaScript. Questo metodo ti consente di definire una funzione che verrà eseguita ogniqualvolta venga attivato l'evento che hai specificato.

const input = document.querySelector("input[type='text']");

const handle_input = (e) => {
  console.log(e.target.value);
};

input.addEventListener("input", handle_input);

La funzione che passi al metodo addEventListener() accetta un argomento: un riferimento all'oggetto Event, che ha un insieme di proprietà che descrivono l'evento appena verificatosi.

Puoi anche rimuovere un event listener da un elemento usando removeEventListener(). Questo metodo deve ricevere la stessa identica funzione che hai passato al metodo addEventListener().

const input = document.querySelector("input[type='text']");

const handle_input = (e) => {console.log(e.target.value)};

const handle_input2 = (e) => {
console.log(e.target.value);
console.log(e);
};

input.addEventListener("input", handle_input);
input.removeEventListener("input", handle_input); // questo funziona

input.removeEventListener("input", handle_input2); // passando una funzione differente non funzionerà

Per ora è tutto per eventi e gestione degli eventi. Basta parlare, mettiamo in pratica tutto ciò che abbiamo imparato finora.

Come Costruire l'App Calcolatrice

Costruiremo la calcolatrice di base di iPhone per mettere alla prova le nostre nuove conoscenze di JavaScript. Per iniziare, crea un file HTML, un file CSS e quindi un file JavaScript.

Ormai suppongo che tu sappia come impostare un ambiente di base usando questi tre strumenti. Puoi utilizzare un editor di codice online come codepen, che è quello che farò in questa lezione, se non hai accesso a un PC nel quale puoi generare quei file.

Inizieremo costruendo le basi della nostra applicazione. Apri il file HTML che hai preparato e incolla il seguente codice al suo interno.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>iPhone Calculator</title>
    <script defer src="script.js"></script>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <main id="main">
      <form id="calc_form">
        <header class="calc_header">
          <input
            type="text"
            disabled
            id="output"
            class="calc_output"
            value="0"
          />
        </header>
        <footer class="calc_footer">
          <div class="btn_group">
            <button
              data-type="clear"
              type="reset"
              value="clear"
              class="btn btn-grey"
            >
              AC
            </button>
            <button data-type="operator" value="invert" class="btn btn-grey">
              +/-
            </button>
            <button data-type="operator" value="%" class="btn btn-grey">
              %
            </button>
            <button data-type="operator" value="/" class="btn btn-orange">
              ÷
            </button>
          </div>
          <div class="btn_group">
            <button
              data-type="operand"
              value="7"
              class="btn btn-dark-grey"
              id="7"
            >
              7
            </button>
            <button data-type="operand" value="8" class="btn btn-dark-grey">
              8
            </button>
            <button data-type="operand" value="9" class="btn btn-dark-grey">
              9
            </button>
            <button data-type="operator" value="*" class="btn btn-orange">
              x
            </button>
          </div>
          <div class="btn_group">
            <button data-type="operand" value="4" class="btn btn-dark-grey">
              4
            </button>
            <button data-type="operand" value="5" class="btn btn-dark-grey">
              5
            </button>
            <button data-type="operand" value="6" class="btn btn-dark-grey">
              6
            </button>
            <button data-type="operator" value="-" class="btn btn-orange">
              -
            </button>
          </div>
          <div class="btn_group">
            <button data-type="operand" value="1" class="btn btn-dark-grey">
              1
            </button>
            <button data-type="operand" value="2" class="btn btn-dark-grey">
              2
            </button>
            <button data-type="operand" value="3" class="btn btn-dark-grey">
              3
            </button>
            <button data-type="operator" value="+" class="btn btn-orange">
              +
            </button>
          </div>
          <div class="btn_group">
            <button
              data-type="operand"
              value="0"
              class="btn btn-grow btn-dark-grey"
            >
              0
            </button>
            <button data-type="operand" value="." class="btn btn-dark-grey">
              .
            </button>
            <button value="=" data-type="operator" class="btn btn-orange">
              =
            </button>
          </div>
        </footer>
      </form>
    </main>
  </body>
</html>

Come puoi vedere sopra, la nostra calcolatrice è fondamentalmente un form HTML con molti pulsanti e un campo di input. Applichiamo uno stile aggiungendo CSS in modo che assomigli alla calcolatrice su un iPhone. Apri il file CSS che hai creato e aggiungi il seguente codice:

*,
*::after,
*::before {
  padding: 0px;
  margin: 0px;
  font-family: inherit;
}

body {
  background-color: #333333;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
}
body,
html {
  height: 100vh;
  font-family: sans-serif;
}
button {
  cursor: pointer;
  border: 0px;
}
input[type="text"] {
  background-color: transparent;
  text-align: end;
  width: 100%;
  color: #d2d2d2;
  border: 0px;
  font-size: 4rem;
}
#main {
  border: 2px solid #ededed;
  border-radius: 50px;
  width: 100%;
  max-width: 280px;
  background-color: #000000;
  padding: 2rem 1rem;
}
.calc_header {
  margin-top: 90px;
  padding: 12px;
}

.calc_footer > * + * {
  margin-top: 1rem;
}
.calc_footer > *:last-child {
  gap: 1rem;
}
.btn_group {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.3rem;
}

.btn {
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
  width: 60px;
  height: 60px;
  color: #ffffff;
  font-size: 20px;
  transition: all 0.3s cubic-bezier(0.19, 1, 0.22, 1);
}
.btn.btn-grow {
  flex-grow: 1;
  border-radius: 40px;
}
.btn:hover {
  transform: translate(-2px, -3px);
}
.btn.active {
  background-color: #ffffff;
  color: #f69906;
}

.btn-grey {
  background-color: #9f9f9f;
  color: #000;
}
.btn-grey:hover {
  background-color: #ededed;
}

.btn-dark-grey {
  background-color: #313131;
}
.btn-dark-grey:hover {
  background-color: #999999;
}
.btn-orange {
  background-color: #f69906;
}
.btn-orange:hover {
  background-color: orange;
}

Ora abbiamo una semplice applicazione di calcolatrice, in questo momento senza funzionalità. Ecco come appare l'anteprima dal vivo:

Nell'esempio sopra, ogni volta che si fa clic su un pulsante, il form viene inviato e la pagina viene ricaricata. Ma non vogliamo che ciò accada.

Se ricordi dalla sezione Eventi, abbiamo discusso dell'evento di invio che viene attivato ogni volta che inviamo un form. Possiamo usare quell'evento per impedire che il nostro form sia inviato ogni volta che facciamo clic su un pulsante.

Per fare ciò, apri il file JavaScript creato e aggiungi il seguente codice:

const form = document.getElementById("calc_form");

form.addEventListener("submit", (e) => {
 e.preventDefault();
});

Se osservi attentamente il  codice HTML qui sopra, vedrai che ogni pulsante ha una proprietà value e un attributo data-type che può avere un valore di operator (operatore) o operand (operando):

<button data-type="operand" value="6" class="btn btn-dark-grey">
6
</button>
<button data-type="operator" value="-" class="btn btn-orange">
-
</button>

La ragione di ciò è che possiamo distinguere tra numeri (operandi) e operatori quando vengono selezionati i pulsanti in JavaScript.

Come Aggiungere Funzionalità all'App Calcolatrice

Ora che abbiamo fatto questo, possiamo iniziare ad aggiungere funzionalità alla nostra applicazione.

Per iniziare, visualizziamo i valori dei nostri operandi quando facciamo clic sui corrispondenti pulsanti. Aggiungi il seguente codice al tuo file JavaScript.

const output = document.getElementById("output");
const form = document.getElementById("calc_form");
const operand_btns = document.querySelectorAll("button[data-type=operand]");
...
let is_operator = false;
operand_btns.forEach((btn) => {
  btn.addEventListener("click", (e) => {
    if (output.value == "0") {
      output.value = e.target.value;
    } else if (is_operator) {
      is_operator = false;
      output.value = e.target.value;
    } else if (output.value.includes(".")) {
      output.value = output.value + "" + e.target.value.replace(".", "");
    } else {
      output.value = output.value + "" + e.target.value;
    }
  });
});

Invece di selezionare tutti i pulsanti uno dopo l'altro (che, a proposito, è piuttosto noioso) abbiamo usato querySelectorAll(). Questo selezionerà tutti i pulsanti in base al criterio che abbiamo specificato e li metterà in un NodeList (un array con elementi di tipo Node).

const operand_btns = document.querySelectorAll("button[data-type=operand]");

Se ricordi quanto trattato nella sezione Cicli e Iterazioni, non puoi accedere ad alcuno di questi pulsanti selezionati fino a quando non iteri sull'array usando uno dei metodi di ciclo esaminati in quella sezione.

operand_btns.forEach((btn) => {
 // qui puoi accedere a ciascun pulsante
});

Infine abbiamo aggiunto un listener per l'evento click a ciascun bottone in questo modo:

btn.addEventListener("click", (e) => {
 // gestisce quello che succede quando il bottone viene cliccato
});

Ora, ogni volta che facciamo click su un qualsiasi pulsante di operando, il valore (un numero) associato viene visualizzato nella calcolatrice.

Nell'istruzione else..if verifichiamo se esiste un separatore decimale nel nostro valore in uscita. Se esiste, semplicemente evitiamo di aggiungere altri separatori decimali sostituendoli con una stringa vuota.

output.value = output.value + "" + e.target.value.replace(".", "");

Un'altra istruzione else..if verifica se in precedenza si è cliccato sul pulsante di un operatore. Se è stato fatto, e quindi si è fatto clic su un bottone di operando, imposteremo il valore di is_operator su false e assegneremo a output il nuovo valore.

else if (is_operator) {
 is_operator = false;
 output.value = e.target.value;
}

Ecco l'anteprima dal vivo dell'esempio sopra:

Ora selezioniamo anche i bottoni il cui valore di data-type è "operator" e specifichiamo cosa accadrà ogniqualvolta facciamo clic su uno qualsiasi di quei bottoni.

Aggiungi il seguente codice al tuo file JavaScript:

const operator_btns = document.querySelectorAll("button[data-type=operator]");

...
let equation = [];
operator_btns.forEach((btn) => {
  btn.addEventListener("click", (e) => {
    e.currentTarget.classList.add("active");

    switch (e.target.value) {
      case "%":
        output.value = parseFloat(output.value) / 100;
        break;
      case "invert":
        output.value = parseFloat(output.value) * -1;
        break;
      case "=":
        equation.push(output.value);
        output.value = eval(equation.join(""));
        equation = [];
        break;
      default:
        let last_item = equation[equation.length - 1];
        if (["/", "*", "+", "-"].includes(last_item) && is_operator) {
          equation.pop();
          equation.push(e.target.value);
        } else {
          equation.push(output.value);
          equation.push(e.target.value);
        }
        is_operator = true;
        break;
    }
  });
});

La prima cosa che facciamo nel codice qui sopra, dopo avere aggiunto un listener di evento "click" al nostro pulsante, è aggiungere la classe active a qualunque bottone di operatore sia stato cliccato. Abbiamo in precedenza definito lo stile per la classe active nel nostro CSS.

.btn.active {
 background-color: #ffffff;
 color: #f69906;
}

Vogliamo applicare questo stile sul pulsante che abbiamo appena cliccato, se si tratta di un pulsante di operatore.

Avremmo anche potuto usare un'istruzione if..else qui, ma chi dice che non possiamo provare cose nuove? L'istruzione switch che segue è una istruzione condizionale in JavaScript, simile all'istruzione if  che abbiamo visto.

L'istruzione switch accetta un valore (la condizione), in questo esempio il valore del pulsante cliccato. Per ciascun caso, il valore viene esaminato. Nel primo caso, %, viene semplicemente convertito il risultato visualizzato in valore percentuale.

case "%":
 output.value = parseFloat(output.value) / 100;
 break;

Se si tratta del pulsante invert (inverti) cambiamo segno al risultato moltiplicandolo per  "-1."

case "invert":
 output.value = parseFloat(output.value) * -1;
 break;

Nel caso di click sul pulsante = aggiungiamo l'ultimo valore di output al nostro array per il calcolo (equation), usando  eval() per calcolare velocemente il contenuto, quindi svuotiamo l'array equation .

case "=":
 equation.push(output.value);
 output.value = eval(equation.join(""));
 equation = [];
 break;

NOTA: eval() è una funzione pericolosa. Può eseguire come codice il testo che riceve come input, quindi un utente potrebbe utilizzarla per eseguire codice malevolo che può essere pericoloso. Usala solo quando ti fidi della sorgente dell'input che viene passato alla funzione.

Quando nessuna delle condizioni indicate nello switch trova corrispondenza, viene eseguito il codice nell'etichetta default, per prima cosa otteniamo l'ultimo elemento dell'array con questa istruzione:

let last_item = equation[equation.length - 1];

Poi, se il pulsante su cui è stato fatto click in precedenza era un pulsante di operatore, vale a dire uno dei seguenti: /, *, + o - , togliamo il suo valore dall'array di calcolo usando equation.pop(), quindi aggiungiamo il valore del pulsante attualmente cliccato con equation.push().

Se l'ultimo elemento del nostro array non era un operatore, aggiungiamo il valore di output e il valore del pulsante cliccato all'array di calcolo.

else {
 equation.push(output.value);
 equation.push(e.target.value);
}

Infine impostiamo il valore di is_operator su true ogni volta che facciamo clic sul pulsante di un operatore:

default:
 let last_item = equation[equation.length - 1];
 if (["/", "*", "+", "-"].includes(last_item) && is_operator)
 {
  equation.pop();
  equation.push(e.target.value);
 } else {
  equation.push(output.value);
  equation.push(e.target.value);
 }
 is_operator = true;
 break;

Avrai notato che terminiamo il codice all'interno delle etichette case con un'istruzione  break per uscire dallo switch.

E il gioco è fatto: un'applicazione di calcolatrice completamente funzionante! Ecco l'anteprima dal vivo:


Prima di finire, c'è un piccolo problema con la nostra calcolatrice: la classe active che aggiungiamo ai nostri pulsanti di operatore quando vengono cliccati rimane anche dopo aver fatto clic su un altro pulsante.

Rimediamo creando una funzione che rimuove la classe active da qualsiasi pulsante di operatore.

const remove_active = () => {
 operator_btns.forEach((btn) => {
  btn.classList.remove("active")
 ;});
};

Ora dobbiamo semplicemente chiamare questa funzione prima di aggiungere la classe active a qualsiasi pulsante.

operator_btns.forEach((btn) => {
 btn.addEventListener("click", (e) => {
  remove_active();
  e.currentTarget.classList.add("active");
  ...
 });
});

Dobbiamo anche rimuovere la classe active dai pulsanti di operatore ogni volta che il pulsante di un operando viene cliccato.

operand_btns.forEach((btn) => {
 btn.addEventListener("click", (e) => {
  remove_active();
  ...
 });
});

Ora è molto meglio. Puoi trovare il codice completo e l'anteprima live della nostra applicazione nel pen qui sotto:


Sfida JavaScript

Nonostante il fatto che la nostra app calcolatrice ora funzioni completamente, ci sono alcune funzionalità che non ho incluso e credo sarebbe una meravigliosa opportunità per esercitarti con le tue nuove abilità JavaScript.

  • Il pulsante di pulizia: Quando facciamo clic su un qualunque pulsante di un operando, il testo del pulsante dovrebbe cambiare da AC a C. Quando questo pulsante viene cliccato, puliamo il form e rimuoviamo qualsiasi classe active dai pulsanti di operatore.
  • La calcolatrice di un iPhone consente solo un massimo di nove numeri come operandi. Non puoi superare quel limite, e credo sia una utile funzionalità anche per la nostra calcolatrice.
  • I separatori decimali sono accodati automaticamente ai numeri nel risultato nella calcolatrice dell'iPhone, il nostro programma non ha attualmente questa capacità.
  • Se utilizziamo due operandi decimali, a volte il risultato potrebbe essere affetto da un errore di arrotondamento. La nostra app non gestisce alcun arrotondamento al momento. Dai un'occhiata a questo articolo se vuoi saperne di più sull'argomento.

Pensi di essere in grado di completare una qualsiasi di queste sfide? Buona fortuna!

Conclusione

Congratulazioni, mago di JavaScript! Sei arrivato così lontano. In questo tutorial, abbiamo imparato il DOM, i cicli, gli eventi JavaScript e come gestirli, e alla fine abbiamo costruito una semplice calcolatrice.

Se partecipi alla sfida in questo post, per favore condividi la tua soluzione online e taggami @sprucekhalifa su Twitter. Non dimenticare anche di seguirmi, mentre twitto su JavaScript.

Oh, e buona programmazione!