59 Eventi del DOM

Gli eventi del DOM sono azioni che interagiscono con la pagina web, tipo il click del mouse, l’inserimento di caratteri della tastiera.

Passando al pratico, abbiamo il seguente classico form HTML:

<form class="col-md-6 push-md-3">
    <h2>Registration form</h2>
    <div class="form-group">
        <label class="col-form-label" for="name">Nome</label>
        <input class="form-control" id="name" name="name">
    </div>
    <div  class="form-group">
        <label  class="col-form-label"  for="lastname">CogNome</label>
        <input  class="form-control" id="lastname" name="lastname">
    </div>
    <div  class="form-group">
        <button class="btn btn-success btn-outline-success">Submit</button>
    </div>
</form>

Il vecchio modo per gestire l’evento era metterlo inline, ovvero nella stessa riga del tag HTML, quindi esaminiamo il tag <input> del campo nome

<input class="form-control" id="name" name="name">

alla vecchia maniera aggiungiamo un evento onclick che esegue un azione al click col mouse

<input class="form-control" onclick="alert('mi hai cliccato!')" id="name" name="name">

possiamo anche utilizzare l’input ricevuto da mostrare nell’ alert, utilizzando l’operatore this che viene riferito a quell’elemento nel quale è inserito

<input class="form-control" onclick="alert(this.value)" id="name" name="name">

con this.value riceverò l’input digitato nel campo nome nel popup.

Vediamo un altro esempio passando una funzione all’evento onclick

<input class="form-control" onclick="handleClick(this)" id="name" name="name">

facciamo la funzione che stampa lo stesso alert

function handleClick(ele) {
  alert(ele.value);
}

Quando utilizziamo una funzione inline e utilizziamo il this come parametro della funzione, il this fa riferimento a quell’elemento in cui è stato inserito.

Per esempio possiamo applicarlo al form aggiungendo l’evento onsubmit che si applica al click del bottone submit

onsubmit="(handleForm(this));"

vediamo la funzione handleForm

function handleForm(f) {
    alert(f.name.value);
    return false;
}

in questo caso il parametro f corrisponde ai risultati del form, quindi per richiamare il valore del campo nome chiederò f.name.value. I dati del form vengono inviati tramite url e poi il form viene ricaricato vuoto. Se per esempio nel campo name inserisco pippo la mia url sarà:

https://bloccoappunti.it/onclick.html?name=pippo&lastname=

per farsi che il form non venga ricaricato e riazzerato, sempre alla vecchia maniera dovevo far restituire false alla funzione con return false

function handleForm(f) {
    alert(f.name.value);
    return false;
}

All’evento onsubmit devo aggiungere la restituzione della funzione col return

<form class="col-md-6 push-md-3" onsubmit="return (handleForm(this));

In questo modo i dati del form non vengono ricaricati e persi.

Un altro evento utilizzato è onkeyup che viene usato quando voglio far eseguire un’azione ogni volta che scrivo

<input class="form-control" onkeyup="handleClick(this)" id="name" name="name">

per esempio vogliamo trasformare il testo in maiuscolo tramite il metodo toUpperCase

function handleClick(ele) {
  //alert(ele.value);
  ele.value = ele.value.toUpperCase();
}

mentre si inseriscono caratteri nel campo nome, questi vengono trasformati in maiuscolo e potrei per esempio, tramite una chiamata AJAX potrei prelevare il dato e in tempo reale avvertire l’utente che il nome è già presente.

Metodo più moderno per gestire gli eventi

Intanto sfruttiamo il nostro listener DOMContentLoaded che già conosciamo per attendere il caricamento della pagina

document.addEventListener('DOMContentLoaded', function () {
  var doc = document;
}

aggiungiamo la selezione del form col metodo querySelector()

document.addEventListener('DOMContentLoaded', function () { 
  var doc = document;
  var f = doc.querySelector('form');
}

aggiungiamo un ID=”form” al <form> così specifichiamo l’elemento nel querySelector e proviamo a stampare un alert

document.addEventListener('DOMContentLoaded', function () {
  var doc = document;
  var f = doc.querySelector('#form');
  alert(f);
}

un altro vecchio modo per aggiungere un evento era quello di inserire il nome dell’elemento (nel nostro caso il form contenuto nella variabile f) seguito dal nome dell’evento, utilizziamo di nuovo onsubmit e una funzione anonima che riceve l’evento

f.onsubmit = function (e) {
    console.log(e);
    return false;
}

a questo punto abbiamo inviato l’evento alla console che registra, una volta premiamo submit , il contenuto del parametro e oltre all’accesso di tutto l’evento che ha scatenato l’invio del form. Ispezionando la console noteremo una miriade di proprietà, tra cui il target che indica l’elemento che ha provocato l’evento (nel nostro caso il form). All’interno del target troviamo tutti gli attributi e proprietà che compongono il form come i tag input e le lore classi e proprietà. Sempre nella console troviamo il type che nel nostro caso è di tipo submit .

Un metodo utile è preventDefault() che si traduce con non fare quello che avresti dovuto fare normalmente se non ti avessimo invocato .

f.onsubmit = function (e) {
   e.preventDefault();
};

a questo punto stampo un alert che entra nella proprietà target e mi restituisce il valore inserito nel compo nome, il tutto senza inviare il form

f.onsubmit = function (e) {
   e.preventDefault();
  alert( e.target.name.value);
};

Ho così la possibilità di manipolare e raccogliere i dati inseriti nel form, per esempio potrei restituire un messaggio di errore magari cambiando lo stile del form o dei suoi elementi

f.onsubmit = function (e) {
   e.preventDefault();
   var t = e.target;
     t.style.border ='1px solid red';
  alert( e.target.name.value);
};

Vediamo il metodo più moderno per fare la stessa cosa, utilizziamo addEventListener

f.addEventListener('submit')

ricordando che f indica il nostro form, lanciamo il metodo addEventListener() che ascolta l’evento specificato come primo parametro. Qual’è l’evento che dobbiamo ascoltare? la risposta e submit.

Ora mettiamo come secondo parametro la funzione che genera l’evento

f.addEventListener('submit' , function() {
} , useCapture );

il terzo parametro è lo useCapture che di default è uguale a false e che spieghiamo dopo. Eliminiamo il terzo parametro e andiamo a mettere nel secondo (la funzione) la registrazione in console,

f.addEventListener('submit' , function(evt) {
 evt.preventDefault();
 console.dir(evt)
} );

Ho interrotto l’invio dati del submit col preventDefault, adesso nella console ritroverò le stesse proprietà che avevo col metodo precedente e quindi posso ripetere le operazioni di cambio di stile di prima

f.addEventListener('submit' , function(evt) {
 evt.preventDefault();
 evt.target.style.border="2px solid green";
} );

la funzione passata come parametro è una funzione anonima, se noi la esterniamo dall’ addEventListener, possiamo richiamarla come secondo parametro specificando direttamente il suo nome

function handleForm(evt) {
    evt.preventDefault();
    evt.target.style.border="2px solid green";
}
f.addEventListener('submit' ,handleForm);

a questo punto se voglio far rinviare i dati al submit rimuovo addEventListener con removeEventListener che toglierà ogni tipo di listener ad un evento

 function handleForm(evt) {
     evt.preventDefault();
     evt.target.style.border="2px solid green";
 }
f.addEventListener('submit' ,handleForm);
f.removeEventListener('submit', handleForm)

Propagazione degli eventi

Ogni volta che un elemento HTML riceve un evento, questo viene propagato agli elementi genitori sino al tag <HTML>. Per esempio nel nostro caso quando si esegue un click sul campo <input> name, questo tag riceve il nostro evento click, il quale sarà inviato all’elemento superiore, il <div class=”form-group”>, che a sua volta lo invierà al <form>, che lo passerà al <body>, il quale infine lo propaga ad <html> seguendo tutta la catena degli eventi del DOM.

Avevamo accennato al terzo parametro del metodo addEventListened, che indica il modo in cui viene propagato l’evento, se dall’interno verso l’esterno ( bubble ) o viceversa ( capture ). Le possibilità possono essere 2 :

useCapture è la modalità in cui assegno l’evento ad un elemento genitore il quale dopo averlo ricevuto lo estende ai suoi discendenti. Quindi se assegnamo il click al <div> container e poi clicco all’interno dell’elemento, ma fuori dal form, il container riceverà l’evento click il quale sarà poi propagato ai suoi figli, quindi al form e ai suoi campi.

Di default il terzo elemento è impostato a useCapture=false quindi viene usato il bubble.

Vediamo prima l’esempio con il bubble, applichiamo l’evento click sia al container che al campo name e li memorizziamo in 2 variabili

let name =  doc.querySelector('#name');
 name.addEventListener('click',  handleName);

let container=  doc.querySelector('#container');
container.addEventListener('click', handleContainer);

vengono lanciate le 2 funzioni, handleName

function handleName(evt) {
   console.dir(evt);
   alert('name='+evt.target.id)
}

e handleContainer

function handleContainer(evt) { 
console.dir(evt);
alert('container='+evt.target.id)
}

Cliccando sul campo name verrà generato un alert che dirà “name=name” e poi un secondo alert con “container=name”. L’evento applicato al genitore ha la priorità su quello del figlio che viene caricato subito dopo.

Anche invertendo le funzioni e i listening nel codice, quindi mettendo prima quelli del name e poi i container, non cambierebbe nulla, nella modalità bubble l’evento applicato al tag ha la priorità su quello genitore..

Impostando il terzo parametro e quindi useCapture=true, l’evento del genitore viene scaturito prima di quello del figlio, quindi dall’elemento più esterno a quello più interno.

Questa proprietà risulta molto comoda se volessimo gestire con un’unica funzione l’evento applicato solo all’elemento padre, senza applicare tanti singoli eventi agli elementi figli. Pensate ad una tabella con 100 campi e dovessimo applicare 100 eventi e al lavoro esorbitante di calcolo che il browser dovrebbe fare.

Alla luce di ciò, il listener e la funzione applicati al campo name si possono eliminare, utilizzando solo il listener per il container che è possibile applicare su ogni elemento figlio intervenendo sulla relativa funzione.

Per esempio posso usare uno switch sull’elemento target.id in modo scaturire un alert che riporta il valore inserito nel name quando si clicca sul campo, il tutto con il listener applicato al container

function handleContainer(evt) {
    let ele = evt.target;

    switch (ele.id) {
        case 'name':
          alert('Hai inserito :' + ele.value)
          break;
     }
}

Se vogliamo gestire il submit, in primi mettimo un ID al pulsante

<div  class="form-group">
    <button id="send" class="btn btn-success btn-outline-success">Submit</button>
</div>

vado a modificare la funzione

function handleContainer(evt) {
    let ele = evt.target;

    switch (ele.id) {
        case 'send':
            alert('Hai fatto click su send :');
            evt.preventDefault();
            break;
    }
}

posso gestire ogni evento che voglio, per esempio posso disabilitare il bottone aggiungendo alla funzione

ele.disable = true;

posso togliere l’elemento dal DOM

ele.parentNode.removeChild(ele);

Per esempio avessi una tabella con molte righe, posso togliere quella riga

<table id="table" class="table-bordered" >
    <tr><td>test1</td><td><button class="btn btn-danger btn-sm">delete</button></td></tr>
    <tr><td>test2</td><td><button  class="btn  btn-danger btn-sm">delete</button></td></tr>
    <tr><td>test3</td><td><button  class="btn  btn-danger btn-sm">delete</button></td></tr>
    <tr><td>test4</td><td><button  class="btn  btn-danger btn-sm">delete</button></td></tr>
</table>

Ogni riga della tabella ha il suo bottone, applichiamo il listener su tutta la tabella e se clicco il pulsante, accedo con parentNode all’elemento e lo elimino.

Iniziamo col listener su table

let container=  doc.querySelector('#table');
container.addEventListener('click',  handleContainer, true);

ora nella funzione handleContainer metto come case dello switch il caso se è un bottone

switch (ele.nodeName.toLowerCase()) {
    case 'button':

quindi memorizzo in una variabile il parentNode di ele ( ele = est.target )

let tr = ele.parentNode.parentNode;

Il parentNode di ele è l’elemento <td>, quindi il parentNode di <td> è l’elemento <tr> della tabella. Applico il removeChild su tr

tr.parentNode.removeChild(tr)

Quindi con un unico listener sulla tabella ho applicato un evento ad un insieme di elementi con un’ unica regola, ho lavorato sul bottone che si trova dentro un <td> il quale è dentro un <tr>.

Con questa lezione abbiamo quindi capito come utilizzare i listener in una pagina e lavorare sulle diverse proprietà e applicare i giusti eventi in base alle esigenze.