Categorie
Javascript Programmazione

{JS} DOM API: L’oggetto new MutationObserver

L’oggetto MutationObserver

L’oggetto in questione è un oggetto nativo, built-in, reso disponibile grazie alla DOM API.
Tecnicamente si tratta di un oggetto che “resta in ascolto” in attesa che qualcosa muti all’interno del nostro documento o che qualche tipo di attività, come il caricamento stesso della nostra pagina, sia terminata.

Potremmo ad esempio voler sapere se è stato aggiunto, eliminato, modificato un elemento, il suo attributo o il suo contenuto.

new MutationObserver

Un observer è un oggetto che monitora elementi del DOM ed esegue una funzione di callback in corrispondenza di una o più modifiche della loro struttura o dei contenuti.
La parola “new” è la maiuscola all’inizio del nome dell’oggetto ci fa capire che si tratta di una classe/costruttore e che è possibile creare, istanziare, più observer ovvero “osservatori”.

var observer = new MutationObserver(callback);

La prima operazione da effettuare è l’associazione della funzione di callback che dovrà essere eseguita non appena il cambiamento si sarà verificiato.

var observer = new MutationObserver(function(MutationRecords){
... });

//oppure

var callback = function(MutationRecords){
... }
var observer = new MutationObserver(callback);

La funzione di callback dovrà necessariamente esserci come argomento un array chiamato “MutationRecords” che servirà a contenere le informazioni sulle variazioni avvenute.

L’Array di oggetti MutationRecords

MutetionRecords Array (argomento di funzione) è un array di oggetti passato alla funzione di callback;
L’oggetto conterrà una serie di informazioni delle mutazioni, all’interno di proprietà, che hanno interessato determinati elementi del DOM.

mutationRecords = [
{
  type: "childList",
  target: <div#elem>,
  removedNodes: [<b>],
  nextSibling: <text node>,
  previousSibling: <text node>,
  attributeName: " ",
  oldValue: " ",
}, 
{
  type: "childList",
  target: <span#elem>,
  removedNodes: [<b>],
  nextSibling: <text node>,
  previousSibling: <text node>,
  attributeName: " ",
  oldValue: " ", 
}];

L’Array conterrà oggetti per ogni elemento di cui indicheremo la volontà osservare la mutazione.
Nel nostro esempio, è’ possibile individuare i distinti elementi all’interno del nostro array di oggetti osservando la proprietà “target”.

Proprietà di MutationRecords

Gli oggetti all’interno dell’array potranno contenere numerose informazioni all’interno di proprietà.

Oggetti di mutationRecords

Fondamentalmente le proprietà rilevanti e primarie sono: type e target;

ProprietàDescrizione
typeContiene il tipo di variazione rilevata.
targetL’elemento su cui è stata rilevata la variazione

La proprietà target indica l’elemento che subisce la mutazione;
La proprietà type è una proprietà-pilota in quanto a seconda della tipologia di variazione rilevata sarà possibile valorizzare altre proprietà;
I possibili valori di tipe sono:

  • attributes (mutazioni degli attribuiti di un elemento)
  • characterData  (mutazioni del contenuto di un elemento)
  • childList (mutazioni della struttura di un elemento)
type:attributes
attributeNameIl nome dell’attributo modificato
oldValueIl valore dell’attributo precedente alla variazione
type:characterData
oldValueIl valore del contenuto precedente alla variazione
type:childList
addedNodesUn array di nodi aggiunti al DOM
removedNodesUn array di nodi rimossi dal DOM
nextSiblingL’eventuale elemento successivo all’elemento che è stato aggiunto o rimosso
previousSiblingL’eventuale elemento precedente all’elemento che è stato aggiunto o rimosso

Non tutte le proprietà di un oggetto saranno valorizzate ciò significa che potrebbero esserci proprietà vuote senza quindi un valore; a determinare quali saranno valorizzate e quali no saranno le indicazioni del tipo di monitoraggio (contenuto, struttura, attributi) indicate dallo sviluppatore.

La proprietà “oldValue” è presente ed è la stessa sia nel tipo di variazione degli attributi (type:attribute) sia nel tipo di variazioni del contenuto (type:characterData). Il valore di questa proprietà, a secondo del tipo di variazione, sarà valorizzato o con il nome dell’attribuito-precedente o con il contenuto-precedente.

Metodi di MutationObserver

Tutte le istanze della classe MutationObserver ereditano i seguenti metodi:

.observe()serve ad indicazioni di quale elemento e quali informazioni monitorare
.disconnect()chiusura del monitoraggio attivo

observer.observe()

Il metodo .observe() serve per iniziare a ricevere notifiche di modifiche di un determinato elemento del DOM; configura quale elemento osservare e quali tipi di informazioni monitorare.

Il metodo prevede due paramenti:

  • Il primo è l’elemento da osservare
  • Il secondo è un oggetto che fornisce le opzioni che descrivono quali mutazioni DOM devono essere riportate
var observer = new MutationObserver(callback);
observer.observe(element, {childList: true, attributes: true} );
L’oggetto “options”

Le possibili variazioni del DOM che possiamo osservare sono riassunte dalle seguenti coppie proprietà-valore:

ProprietàDescrizione
attributes: trueValore booleano per l’osservazione di variazioni degli attributi dell’elemento
attributeOldValue: trueValore booleano che consente di inserire nei Mutation record il vecchio valore di unattributo (solo se attributes è true)
characterData: trueValore booleano per l’osservazione di variazioni del contenuto dell’elemento
characterDataOldValue: trueValore booleano che consente di inserire nei Mutation record il vecchio valore del contenuto dell’elemento (solo se characterData ètrue)
childList: trueValore booleano che consente l’osservazione di variazioni dei figli dell’elemento
subtree: trueValore booleano che prevede l’osservazione di variazioni di tutti i discendenti dell’elemento
attributeFilter: trueArray di nomi di attributi da teneresotto osservazione (es.: [“class”, “src”])

Tutte queste proprietà possono essere inserite nel secondo paramento, paramento di tipo oggetto, del metodo .observe(); questo oggetto, indica a quali informazioni siamo interessati ed indirettamente quali proprietà dello oggetto generato, contenuto nell’array mutationRecords, valorizzare.

Un best-pratice di sviluppo è quella di separare gli elementi del codice in porzioni differenti:

var element = document.querySelector('div.classe');
var callback = function(){...};
var options = {childList: true, attributes: true};

var observer = new MutationObserver(callback);
observer.observe(element, options);

Quando un elemento monitorato del DOM subisce delle modifiche, le proprietà presenti nel relativo oggetto presente nell’array “mutationRecords” saranno valorizzate;
Queste proprietà saranno richiamabili all’interno della callback:

var callback = function(MutationRecord){
MutationRecord.forEach(function(mutation){
 switch(mutation.type) {
   case 'childList':
console.log(`Elementi aggiunti: n. ${mutation.addedNodes.legth} ${mutation.addedNodes.join()};
Elementi rimossi: n. ${mutation.removedNodes.legth} ${mutation.removedNodes.join()}`);
     break;
   case 'attributes':
console.log(`Modificato attributo ${mutation.attributeName} dell elemento ${mutation.target}. Il precedente valore era: ${mutation.oldValue}`);
     break;
   case 'characterData':
console.log(`Modificato contenuto dell elemento ${mutation.target}. Il precedente contenuto era: ${mutation.oldValue}`);
     break;
    }
  });
}

var options = {childList: true, attributes: true, attributeOldValue: true, characterData: true, characterData: true};
var observer = new MutationObserver(callback);
var element = document.querySelector('div.classe');
observer.observe(element, options);

Trattandosi di un array di oggetti e potendo esso contenere molteplici mutazioni dovremo provvedere a ciclarlo/iterarlo operando una filtratura mediante la proprietà .type.

Note: MutationObserver può tenere sotto controllo le variazioni della struttura di un sottolabero, la modifica del contenuto o degli attributi di un elemento. Esso non è però in grado di rilevare lo stato interno di un elemento, come ad esempio l'attributo value o checked di un elemento <input>

observer.disconnect()

Se decidiamo di non osservare più le variazioni legate ad un elemento del DOM possiamo utilizzare il metodo disconnect() dell’observer:

observer.disconnect();
Dimostrazione

Codice HTML

<div class="myContainer">
<div><a href="#" id="bottone">+</a></div>
<div id="bloccoUno"></div>
<div id="bloccoDue" style="display:none;"></div>
<div><p id="notifica">Spazio notifica</p></div>
</div>

<style>
#bloccoUno{  background: white; margin: 0.2%; padding:0.2%; border: 1px solid blue; }
#bloccoDue{  background: white; margin: 0.2%; padding:0.2%; border: 1px solid blue; }
#bottone{  background: white; color:blue margin: 0.2%; padding: 0.2%; border: 1px solid blue;}
#notifica{ background: white; color: blue;}
.myContainer { display: flex; flex-direction: column; border: 1px solid;
}
</style>

Codice Javascript

var myContainer = document.querySelector('div.myContainer');
var bottone = document.getElementById('bottone');
var bloccoUno = document.getElementById('bloccoUno');
var bloccoDue = document.getElementById('bloccoDue');
var notifica = document.getElementById('notifica');

bottone.onclick=function(event){
event.preventDefault();
bloccoDue.style.display="block";
};

var aggiunta = function(mutationRecords){
mutationRecords.forEach(function(mutation){
if(mutation.type === 'attributes'){
notifica.innerHTML=`All'elemento con ID: ${mutation.target.id} è stato modificato l'attributo ${mutation.attributeName}; vecchio valore ${mutation.oldValue}`;
}
});
}

var opzioni = {attributes: true, attributeOldValue: true};

var osservatore = new MutationObserver(aggiunta);
osservatore.observe(bloccoDue, opzioni);

Risultato

BLOCCO UNO

Spazio notifica

Alcune precisazioni

Spesso nello sviluppo web mostriamo elementi nascosti “display:none;” all’evento click (es. menu mobile); questo potrebbe indurci in errore; di fatto, non viene aggiunto un elemento quindi non dobbiamo monitorare l’aggiunta di un elemento ma il cambio del suo attributo “display”;

L’oggetto “mutationRecords” è un array-di-oggetti che viene creato e subito dopo distrutto; quindi, terminata la callback non ci sarà possibile accedere.

Altra precisazione sull’oggetto “mutationRecords”; per poter accedere alle sue proprietà, trattandosi di un array di oggetti che può contenere differenti mutazioni (struttura, attributi e contenti), dovremo ciclarlo/iterarlo e prevedere una verifica sulla proprietà “type”.
Per farlo possiamo adottare diverse soluzioni:

mutationRecords.forEach(function(mutation){
if(mutationRecords.type == "attributes"{
...}
};
//oppure
for (const mutation of mutationRecords){
    if (mutation.type === 'attributes'){
...}
}
//oppure
for(i=0; i < mutationRecords.length; i++) {
		if (mutationRecords[i].type == "attributes") {
//oppure
MutationRecord.forEach(function(mutation){
 switch(mutation.type) {
   case 'childList':
...
     break;
   case 'attributes':
...
     break;
   case 'characterData':
...
     break;
    }
  });
}

Mutation Event deprecati/obsoleti

Prima della sua introduzione javascript aveva già introdotto una serie di eventi con scopi similari

DOMSubtreeModifiedE’ stata apportata una modifica al documento
DOMNodeInsertedIl nodo è stato inserito come figlio diretto di un altro nodo
DOMNodeRemovedIl nodo è stato eliminato da un altro nodo
DOMNodeInsertedIntoDocumentIl nodo è stato inserito come discendente di un altro nodo
DOMNodeRemovedFormDocumentIl nodo è stato eliminato come discendente di un altro nodo
Avviso Mozilla

Questo set di eventi risultano attualmente obsoleti e deprecati il che non ci garantisce una compatibilità cross-browser.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *