Evento jQuery: detecta cambios en el html / texto de un div

266

Tengo un div que tiene su contenido cambiando todo el tiempo, ya sea ajax requests, jquery functions, bluretc, etc.

¿Hay alguna manera de detectar cualquier cambio en mi div en cualquier momento?

No quiero usar ningún intervalo o valor predeterminado marcado.

Algo como esto haría

$('mydiv').contentchanged() {
 alert('changed')
}
BoqBoq
fuente
55
Posible duplicado: stackoverflow.com/questions/10328102/…
Rob
14
@Rob Eso vincula un keypressevento a un <div>elemento contento . No estoy seguro de que las soluciones allí se apliquen a esto. Definitivamente no recogerían ningún cambio programático en el contenido de un elemento.
Anthony Grist

Respuestas:

409

Si no desea usar el temporizador y verificar innerHTML, puede probar este evento

$('mydiv').bind('DOMSubtreeModified', function(){
  console.log('changed');
});

Más detalles y datos de soporte del navegador están aquí .

Atención: en las versiones más nuevas de jQuery, bind () está en desuso, por lo que debe usar on () en su lugar:

$('body').on('DOMSubtreeModified', 'mydiv', function(){
  console.log('changed');
});
iman
fuente
14
Tenga en cuenta que DOMSubtreeModified no es compatible con IE8 (y a continuación).
Gavin
56
este evento está en desuso w3.org/TR/DOM-Level-3-Events/#glossary-deprecated
Isaiyavan Babu Karan
3
Mozilla 33: falló en la recursividad para el elemento <body>. Necesitaba encontrar otro camino
Chaki_Black
17
Es grave NO use este evento, ya que bloqueará todo su trabajo porque se dispara todo el tiempo. En su lugar, use los eventos a continuación $ ('. MyDiv'). Bind ('DOMNodeInserted DOMNodeRemoved', function () {});
George SEDRA
19
¡Este metodo está obsoleto! En su lugar, use:$("body").on('DOMSubtreeModified', "mydiv", function() { });
Asif
55

Usando Javascript MutationObserver

  //More Details https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
 // select the target node
var target = document.querySelector('mydiv')
// create an observer instance
var observer = new MutationObserver(function(mutations) {
  console.log($('mydiv').text());   
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
PPB
fuente
66
Esta es la respuesta correcta, ya que ahora es más favorable que usar DOMSubtreeModified
Joel Davey el
3
Recibo un error con esto, a pesar de que he dado el selector correcto. "VM21504: 819 Tipo de error no capturado: Error al ejecutar 'observar' en 'MutationObserver': el parámetro 1 no es del tipo 'Nodo'".
samir
54

Como $("#selector").bind()está en desuso , debe usar:

$("body").on('DOMSubtreeModified', "#selector", function() {
    // code here
});
Petar Nikov
fuente
1
Este fue el factor diferenciador, el adicional #para indicar que el símbolo debe ir antes que el selector.
Chris - Jr
42

Puedes probar esto

$('.myDiv').bind('DOMNodeInserted DOMNodeRemoved', function() {

});

pero esto podría no funcionar en Internet Explorer, no lo he probado

usuario1790464
fuente
3
¡NOTA !: si usa esto para un disparador y se están realizando muchos cambios en la página, ejecutará su función X veces (tal vez incluso X = 1,000 o más), lo que podría ser muy ineficiente. Una solución simple es definir una var booleana "en ejecución", que será ... if (running == true) {return} ... sin ejecutar su código si ya se está ejecutando. Establezca running = true justo después de su lógica if, y running = false antes de que su función salga. También puede usar un temporizador para limitar su función para que solo pueda ejecutarse cada X segundos. corriendo = verdadero; setTimeout (function () {running = false}, 5000); (o algo mejor)
JxAxMxIxN
Utilicé esto en un cuadro de selección que tenía opciones que se agregaban y eliminaban. Funcionó muy bien cuando se agregaron elementos, pero la eliminación parecía estar 1 elemento detrás. Cuando se eliminó la última opción, no se dispararía.
CodeMonkey
2
@JxAxMxIxN También puede activar el temporizador de tiempo de espera borrando y configurando el tiempo de espera nuevamente:clearTimeout(window.something); window.something = setTimeout(...);
Ctrl-C
de acuerdo - tu camino es el camino a seguir - desde que aprendí Python he aclarado muchas de mis malas prácticas de codificación en varios idiomas (no todas, solo muchas;)
JxAxMxIxN
33

Estás buscando MutationObserver o Mutation Events . Tampoco son compatibles en todas partes ni son vistos con demasiado cariño por el mundo desarrollador.

Si sabe (y puede asegurarse de que) el tamaño del div cambiará, es posible que pueda usar el evento de cambio de tamaño de crossbrowser .

rodneyrehm
fuente
1
Éste es el indicado. Específicamente, DOMSubtreeModified . Puede encontrar útil la biblioteca de resumen de mutaciones y esta lista de eventos del árbol DOM .
BenjaminRH
1
este evento está en desuso developer.mozilla.org/en-US/docs/Web/Guide/Events/…
Adrien Be
9
En caso de que alguien más haya tenido que intentar leer todo en todas partes, esta es la respuesta correcta. Mutation Events fue compatible con navegadores anteriores, Mutation Observer es lo que se admitirá en los navegadores modernos y se admitirá en el futuro. Ver enlace para soporte: CANIUSE Mutation Observer
Josh Mc
20

El siguiente código funciona para mí.

$("body").on('DOMSubtreeModified', "mydiv", function() {
    alert('changed');
});

Espero que ayude a alguien :)

Sanchit Gupta
fuente
Esta es la misma respuesta que la de @Artley
Negro
@Black Gracias! Acabo de comprobar la respuesta de Artley. Me ocuparé de esto la próxima vez.
Sanchit Gupta
17

No hay una solución incorporada a este problema, este es un problema con su diseño y patrón de codificación.

Puede usar el patrón editor / suscriptor. Para esto, puede usar eventos personalizados jQuery o su propio mecanismo de eventos.

Primero,

function changeHtml(selector, html) {
    var elem = $(selector);
    jQuery.event.trigger('htmlchanging', { elements: elem, content: { current: elem.html(), pending: html} });
    elem.html(html);
    jQuery.event.trigger('htmlchanged', { elements: elem, content: html });
}

Ahora puede suscribirse divhtmlchanging / divhtmlchanged events de la siguiente manera,

$(document).bind('htmlchanging', function (e, data) {
    //your before changing html, logic goes here
});

$(document).bind('htmlchanged', function (e, data) {
    //your after changed html, logic goes here
});

Ahora, tienes que cambiar tus cambios de contenido div a través de esto changeHtml() función. Por lo tanto, puede supervisar o hacer los cambios necesarios en consecuencia porque enlaza el argumento de datos de devolución de llamada que contiene la información.

Tienes que cambiar el html de tu div de esta manera;

changeHtml('#mydiv', '<p>test content</p>');

Y también, puede usar esto para cualquier elemento html excepto el elemento de entrada. De todos modos, puede modificar esto para usarlo con cualquier elemento (s).

Sameera Millavithanachchi
fuente
Para observar y actuar sobre los cambios en un elemento en particular, simplemente modifique la función changeHtml para usar 'elem.trigger (...)' en lugar de 'jQuery.event.trigger (...)', y luego enlace al elemento como $ ('# my_element_id'). on ('htmlchanged', function (e, data) {...}
KenB
8
"este es un problema con su diseño y patrón de codificación", ¿qué hacer si incluye scripts de terceros, por lo tanto no tiene control sobre su código fuente? pero necesitas detectar sus cambios en un div?
DrLightman
@DrLightman, una regla general es elegir una biblioteca de terceros con evento de devolución de llamada proporcionado
Marcel Djaman
8

Use MutationObserver como se ve en este fragmento proporcionado por Mozilla y adaptado de esta publicación de blog

Alternativamente, puede usar el ejemplo de JQuery que se ve en este enlace

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');

// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };

// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

// Later, you can stop observing
observer.disconnect();
Anthony Awuley
fuente
3

Puede almacenar el antiguo innerHTML del div en una variable. Establezca un intervalo para verificar si el contenido anterior coincide con el contenido actual. Cuando esto no sea cierto, haz algo.

codelove
fuente
1

Prueba el MutationObserver:

soporte del navegador: http://caniuse.com/#feat=mutationobserver

<html>
  <!-- example from Microsoft https://developer.microsoft.com/en-us/microsoft-edge/platform/documentation/dev-guide/dom/mutation-observers/ -->

  <head>
    </head>
  <body>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script type="text/javascript">
      // Inspect the array of MutationRecord objects to identify the nature of the change
function mutationObjectCallback(mutationRecordsList) {
  console.log("mutationObjectCallback invoked.");

  mutationRecordsList.forEach(function(mutationRecord) {
    console.log("Type of mutation: " + mutationRecord.type);
    if ("attributes" === mutationRecord.type) {
      console.log("Old attribute value: " + mutationRecord.oldValue);
    }
  });
}
      
// Create an observer object and assign a callback function
var observerObject = new MutationObserver(mutationObjectCallback);

      // the target to watch, this could be #yourUniqueDiv 
      // we use the body to watch for changes
var targetObject = document.body; 
      
// Register the target node to observe and specify which DOM changes to watch
      
      
observerObject.observe(targetObject, { 
  attributes: true,
  attributeFilter: ["id", "dir"],
  attributeOldValue: true,
  childList: true
});

// This will invoke the mutationObjectCallback function (but only after all script in this
// scope has run). For now, it simply queues a MutationRecord object with the change information
targetObject.appendChild(document.createElement('div'));

// Now a second MutationRecord object will be added, this time for an attribute change
targetObject.dir = 'rtl';


      </script>
    </body>
  </html>

juFo
fuente
0

Agregar algún contenido a un div, ya sea a través de jQuery o mediante DOM-API directamente, el valor predeterminado es la .appendChild()función. Lo que puede hacer es anular la .appendChild()función del objeto actual e implementar un observador en él. Ahora que .appendChild()hemos anulado nuestra función, necesitamos tomar prestada esa función de otro objeto para poder agregar el contenido. Por lo tanto, llamamos al .appendChild()de otro div para finalmente agregar el contenido. Por supuesto, esto también cuenta para el .removeChild().

var obj = document.getElementById("mydiv");
    obj.appendChild = function(node) {
        alert("changed!");

        // call the .appendChild() function of some other div
        // and pass the current (this) to let the function affect it.
        document.createElement("div").appendChild.call(this, node);
        }
    };

Aquí puedes encontrar un ejemplo ingenuo. Puede extenderlo usted mismo, supongo. http://jsfiddle.net/RKLmA/31/

Por cierto: esto muestra que JavaScript cumple con el principio OpenClosed. :)

Andries
fuente
No funciona con append child ... De hecho, modifico el html a través de otras funciones.
BoqBoq
Como removeChild () replaceChild () etc. Pero tienes razón en innerHTML. Deberías evitarlo de alguna manera.
Andries