Evento jQuery "on create" para elementos creados dinámicamente


81

Necesito poder crear <select>elementos dinámicamente y convertirlos en jQuery .combobox(). Este debería ser un evento de creación de elementos, a diferencia de algún evento de "clic", en cuyo caso podría usar jQuery .on().

Entonces, ¿existe algo como esto?

$(document).on("create", "select", function() {
    $(this).combobox();
}

Soy reacio a usar livequery porque está muy desactualizado.

ACTUALIZAR La selección / combobox mencionada se carga a través de ajax en un jQuery colorbox (ventana modal), por lo tanto, el problema: solo puedo iniciar el combobox usando el colorbox onComplete, sin embargo, al cambiar un combobox, otro select / combobox debe crearse dinámicamente, por lo tanto, necesito un forma más genérica de detectar la creación de un elemento ( selecten este caso).

ACTUALIZACIÓN2 Para tratar de explicar el problema más a fondo, tengo select/comboboxelementos creados de forma recursiva, también hay mucho código de inicio en el interior .combobox(), por lo tanto, si usara un enfoque clásico, como en la respuesta de @ bipen , mi código se inflaría a niveles insanos. Espero que esto explique mejor el problema.

ACTUALIZACIÓN3 Gracias a todos, ahora entiendo que desde la desaprobación de DOMNodeInserted, queda un vacío en la mutación DOM y no hay solución para este problema. Solo tendré que repensar mi aplicación.


1
¿Puede estar con el evento listo? $(select).ready(function() { });
Pablo Banderas

@PabloBanderas readyse usa para: "Especificar una función para ejecutar cuando el DOM esté completamente cargado". (de jquery.com)
Caballero

1
¿Por qué no aplicar el combobox()método en el punto de creación / inserción, en lugar de hacerlo después?
David dice reinstalar a Monica

@DavidThomas Traté de explicar la razón en mi segunda actualización
Caballero

Así $(document).on('onComplete', 'select', function(){ // bind here? });? (Sin documentembargo, obviamente use un padre más cercano que el )
David dice reinstalar a Monica

Respuestas:


59

Puede que onel DOMNodeInsertedevento obtenga un evento para cuando se agregue al documento mediante su código.

$('body').on('DOMNodeInserted', 'select', function () {
      //$(this).combobox();
});

$('<select>').appendTo('body');
$('<select>').appendTo('body');

Jugado aquí: http://jsfiddle.net/Codesleuth/qLAB2/3/

EDITAR: después de leer, solo necesito verificar dos veces DOMNodeInsertedque no causará problemas en los navegadores. Esta pregunta de 2010 sugiere que IE no es compatible con el evento, así que pruébelo si puede.

Vea aquí: [enlace] ¡Advertencia! el tipo de evento DOMNodeInserted se define en esta especificación para referencia y completitud, pero esta especificación desaprueba el uso de este tipo de evento.


no está .delegateobsoleto de la versión 1.7 de jQuery y reemplazado por .on?
Caballero

1
Sí, tienes razón. Modificaré esta respuesta, pero honestamente no estoy seguro de si debería usar esto, ya DOMNodeInsertedque ahora está en desuso.
Codesleuth

Lo intenté on('DOMNodeInserted'antes de publicar esta pregunta. Obviamente no funcionó.
Caballero

20
Dado que no está en su pregunta que lo haya intentado, diría que es un voto injusto. En cualquier caso, no puedo ayudarte aquí.
Codesleuth


20

Como se mencionó en varias otras respuestas, los eventos de mutación se han desaprobado, por lo que debería usar MutationObserver en su lugar. Como nadie ha dado detalles al respecto todavía, aquí va ...

API de JavaScript básica

La API de MutationObserver es bastante simple. No es tan simple como los eventos de mutación, pero aún está bien.

function callback(records) {
  records.forEach(function (record) {
    var list = record.addedNodes;
    var i = list.length - 1;
    
	for ( ; i > -1; i-- ) {
	  if (list[i].nodeName === 'SELECT') {
	    // Insert code here...
	    console.log(list[i]);
	  }
	}
  });
}

var observer = new MutationObserver(callback);

var targetNode = document.body;

observer.observe(targetNode, { childList: true, subtree: true });
<script>
  // For testing
  setTimeout(function() {
    var $el = document.createElement('select');
    document.body.appendChild($el);
  }, 500);
</script>

Analicemos eso.

var observer = new MutationObserver(callback);

Esto crea al observador. El observador aún no está mirando nada; aquí es donde se adjunta el detector de eventos.

observer.observe(targetNode, { childList: true, subtree: true });

Esto hace que el observador se ponga en marcha. El primer argumento es el nodo en el que el observador observará los cambios. El segundo argumento son las opciones de qué estar atento .

  • childList significa que quiero ver si se agregan o eliminan elementos secundarios.
  • subtreees un modificador que se extiende childList para observar cambios en cualquier parte del subárbol de este elemento (de lo contrario, solo miraría los cambios directamente dentro targetNode).

Las otras dos opciones principales además childListson attributesy characterData, lo que significa cómo suenan. Debes usar uno de esos tres.

function callback(records) {
  records.forEach(function (record) {

Las cosas se ponen un poco complicadas dentro de la devolución de llamada. La devolución de llamada recibe una matriz de MutationRecord s. Cada MutationRecord puede describir varios cambios de un tipo ( childList, attributeso characterData). Como solo le dije al observador que estuviera atento childList, no me molestaré en verificar el tipo.

var list = record.addedNodes;

Aquí mismo tomo una NodeList de todos los nodos secundarios que se agregaron. Esto estará vacío para todos los registros donde no se agregan nodos (y puede haber muchos registros de este tipo).

A partir de ahí, recorro los nodos agregados y encuentro los que son <select>elementos.

Nada realmente complejo aquí.

jQuery

... pero pediste jQuery. Multa.

(function($) {

  var observers = [];

  $.event.special.domNodeInserted = {

    setup: function setup(data, namespaces) {
      var observer = new MutationObserver(checkObservers);

      observers.push([this, observer, []]);
    },

    teardown: function teardown(namespaces) {
      var obs = getObserverData(this);

      obs[1].disconnect();

      observers = $.grep(observers, function(item) {
        return item !== obs;
      });
    },

    remove: function remove(handleObj) {
      var obs = getObserverData(this);

      obs[2] = obs[2].filter(function(event) {
        return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
      });
    },

    add: function add(handleObj) {
      var obs = getObserverData(this);

      var opts = $.extend({}, {
        childList: true,
        subtree: true
      }, handleObj.data);

      obs[1].observe(this, opts);

      obs[2].push([handleObj.selector, handleObj.handler]);
    }
  };

  function getObserverData(element) {
    var $el = $(element);

    return $.grep(observers, function(item) {
      return $el.is(item[0]);
    })[0];
  }

  function checkObservers(records, observer) {
    var obs = $.grep(observers, function(item) {
      return item[1] === observer;
    })[0];

    var triggers = obs[2];

    var changes = [];

    records.forEach(function(record) {
      if (record.type === 'attributes') {
        if (changes.indexOf(record.target) === -1) {
          changes.push(record.target);
        }

        return;
      }

      $(record.addedNodes).toArray().forEach(function(el) {
        if (changes.indexOf(el) === -1) {
          changes.push(el);
        }
      })
    });

    triggers.forEach(function checkTrigger(item) {
      changes.forEach(function(el) {
        var $el = $(el);

        if ($el.is(item[0])) {
          $el.trigger('domNodeInserted');
        }
      });
    });
  }

})(jQuery);

Esto crea un nuevo evento llamado domNodeInserted, usando la API de eventos especiales de jQuery . Puedes usarlo así:

$(document).on("domNodeInserted", "select", function () {
  $(this).combobox();
});

Personalmente, sugeriría buscar una clase porque algunas bibliotecas crearán selectelementos con fines de prueba.

Naturalmente, también puede utilizar .off("domNodeInserted", ...)o ajustar la visualización pasando datos como este:

$(document.body).on("domNodeInserted", "select.test", {
  attributes: true,
  subtree: false
}, function () {
  $(this).combobox();
});

Esto activaría la comprobación de la apariencia de un select.testelemento cada vez que cambiaran los atributos de los elementos directamente dentro del cuerpo.

Puede verlo en vivo a continuación o en jsFiddle .


Nota

Este código jQuery es una implementación bastante básica. No se activa en los casos en que las modificaciones en otros lugares hacen que su selector sea válido.

Por ejemplo, suponga que su selector es .test selecty el documento ya tiene una extensión <select>. Agregar la clase testa <body>hará que el selector sea válido, pero debido a que solo verifico record.targety record.addedNodes, el evento no se activará. El cambio tiene que ocurrir en el elemento que desea seleccionar.

Esto podría evitarse consultando el selector siempre que ocurran mutaciones. Elegí no hacer eso para evitar causar eventos duplicados para elementos que ya habían sido manejados. Tratar adecuadamente con combinadores hermanos adyacentes o generales complicaría aún más las cosas.

Para obtener una solución más completa, consulte https://github.com/pie6k/jquery.initialize , como se menciona en la respuesta de Damien Ó Ceallaigh . Sin embargo, el autor de esa biblioteca ha anunciado que la biblioteca es antigua y sugiere que no debería usar jQuery para esto.


9

Se me ocurrió esta solución que parece resolver todos mis problemas de ajax.

Para eventos listos, ahora uso esto:

function loaded(selector, callback){
    //trigger after page load.
    $(function () {
        callback($(selector));
    });
    //trigger after page update eg ajax event or jquery insert.
    $(document).on('DOMNodeInserted', selector, function () {
        callback($(this));
    });
}

loaded('.foo', function(el){
    //some action
    el.css('background', 'black');
});

Y para eventos desencadenantes normales ahora uso esto:

$(document).on('click', '.foo', function () {
    //some action
    $(this).css('background', 'pink');
});


4

Esto se podría hacer con, DOM4 MutationObserverspero solo funcionará en Firefox 14 + / Chrome 18+ (por ahora).

Sin embargo, hay un " truco épico " (¡las palabras del autor no son mías!) Que funciona en todos los navegadores que admiten animaciones CSS3 que son: IE10, Firefox 5+, Chrome 3+, Opera 12, Android 2.0+, Safari 4+. Vea la demostración del blog. El truco consiste en utilizar un evento de animación CSS3 con un nombre dado que se observa y se actúa en JavaScript.


4

Una forma, que parece confiable (aunque probada solo en Firefox y Chrome) es usar JavaScript para escuchar el animationendevento (o su camelCased, y prefijo, hermano animationEnd), y aplicar una animación de corta duración (en la demostración de 0.01 segundos) a el tipo de elemento que planea agregar. Esto, por supuesto, no es un onCreateevento, pero se aproxima (en navegadores compatibles ) a un onInsertiontipo de evento; la siguiente es una prueba de concepto:

$(document).on('webkitAnimationEnd animationend MSAnimationEnd oanimationend', function(e){
    var eTarget = e.target;
    console.log(eTarget.tagName.toLowerCase() + ' added to ' + eTarget.parentNode.tagName.toLowerCase());
    $(eTarget).draggable(); // or whatever other method you'd prefer
});

Con el siguiente HTML:

<div class="wrapper">
    <button class="add">add a div element</button>
</div>

Y (abreviado, prefijo-versiones-eliminado aunque presente en Fiddle, a continuación) CSS:

/* vendor-prefixed alternatives removed for brevity */
@keyframes added {
    0% {
        color: #fff;
    }
}

div {
    color: #000;
    /* vendor-prefixed properties removed for brevity */
    animation: added 0.01s linear;
    animation-iteration-count: 1;
}

Demostración de JS Fiddle .

Obviamente, el CSS se puede ajustar para adaptarse a la ubicación de los elementos relevantes, así como al selector utilizado en jQuery (realmente debería estar lo más cerca posible del punto de inserción).

Documentación de los nombres de los eventos:

Mozilla   |  animationend
Microsoft |  MSAnimationEnd
Opera     |  oanimationend
Webkit    |  webkitAnimationEnd
W3C       |  animationend

Referencias:


4

Para mí, atarme al cuerpo no funciona. La vinculación al documento mediante jQuery.bind () sí lo hace.

$(document).bind('DOMNodeInserted',function(e){
             var target = e.target;
         });

1

Creo que vale la pena mencionar que, en algunos casos, esto funcionaría:

$( document ).ajaxComplete(function() {
// Do Stuff
});

1

en vez de...

$(".class").click( function() {
    // do something
});

Puedes escribir...

$('body').on('click', '.class', function() {
    // do something
});

0

crea un <select>con id, añádelo al documento .. y llama.combobox

  var dynamicScript='<select id="selectid"><option value="1">...</option>.....</select>'
  $('body').append(dynamicScript); //append this to the place your wanted.
  $('#selectid').combobox();  //get the id and add .combobox();

esto debería hacer el truco ... puedes ocultar la selección si quieres y luego .comboboxmostrarla ... o usar buscar ...

 $(document).find('select').combobox() //though this is not good performancewise

-1

si está utilizando angularjs, puede escribir su propia directiva. Tuve el mismo problema con bootstrapSwitch. Tengo que llamar $("[name='my-checkbox']").bootstrapSwitch(); a javascript pero mi objeto de entrada html no se creó en ese momento. Entonces escribo una directiva propia y creo el elemento de entrada con <input type="checkbox" checkbox-switch>

En la directiva, compilo el elemento para obtener acceso a través de javascript y ejecutar el comando jquery (como su .combobox()comando). Muy importante es eliminar el atributo. De lo contrario, esta directiva se llamará a sí misma y habrá creado un bucle

app.directive("checkboxSwitch", function($compile) {
return {
    link: function($scope, element) {
        var input = element[0];
        input.removeAttribute("checkbox-switch");
        var inputCompiled = $compile(input)($scope.$parent);
        inputCompiled.bootstrapSwitch();
    }
}
});

La pregunta estaba relacionada solo con jQuery, no con AngularJS. La solución debe limitarse a los complementos jQuery y jQuery.
James Dunne
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.