Forzar la reactivación de todos (scripts/funciones) en contenido ajaxado

15 minutos de lectura

avatar de usuario
Yo haz kode

tl; dr

Uso ajax para buscar contenido nuevo. El contenido se obtiene y se agrega a la página. Sin embargo, los scripts no se “vuelven a disparar” porque sus llamadas están fuera del ajax. div.

Los scripts se cargan y activan sin ningún problema en la carga de la página inicial, pero no cuando agrego contenido nuevo a través de ajax. No obtengo errores de consola y no hay problemas si visito la URL directamente.


Relacionado:

  1. Forzar la ejecución de la secuencia de comandos en la página cargada de AJAX: se relaciona con una secuencia de comandos específica. Quiero corregir (reactivar) todos los scripts, incluidos los dinámicos de Aplicaciones de Cloudflare
  2. Uso del script jQuery con Ajax en WordPress: igual que el anterior, esto se relaciona solo con un script específico
  3. ajax cargó contenido, el script no se está ejecutando

Introducción:

yo suelo Sitio de Ajaxify WordPress (AWS) en un sitio web de WordPress.

El complemento le permite seleccionar un div por su id y luego obtiene nuevo contenido dentro de ese div usando ajax

marcado html

<html>

<head></head> 

<body>
  <div id="page"> 
    <header></header>
    <main id="ajax"> <!-- This is what I want to ajaxify -->
      <div class="container">
        main page content
      </div> 
    </main> <!-- end of ajaxfied content  -->
    <footer></footer>
  </div> <!-- #page -->
</body>

</html>

Problema

El complemento funciona y obtengo contenido nuevo cargado y diseñado, pero hay un problema. Algo de mi pagina scripts y llamadas a funciones están fuera del div en el que uso ajax. tengo dos ejemplos

1- La albañilería se carga y llama en el <footer>

<html>

<head></head> 

<body>
  <div id="page"> 
    <header></header>
    <main id="ajax"> <!-- This is what I want to ajaxify -->
      <div class="container">
        main page content
      </div>  
    </main>   <!-- end of ajaxfied content  -->
    <footer></footer> <!-- Masonry script is loaded and called here -->
  </div> <!-- #page -->
</body>

</html>

2- La llamada de Google Maps está en el <head>

<html>

<head></head>  <!-- Google maps is called here -->

<body>
  <div id="page"> 
    <header></header>
    <main id="ajax"> <!-- This is what I want to ajaxify -->
      <div class="container">
        main page content
      </div>  
    </main>  <!-- end of ajaxfied content  -->
    <footer></footer> 
  </div> <!-- #page -->
</body>

</html>

estos son solo dos ejemplos. Hay otros en otros lugares. Como puede ver, dichos scripts no se volverán a llamar ya que lo único que se recarga en la página es <main id="ajax">. Tiempo el nuevo contenido dentro <main> esta ahíalgunos de los scripts necesarios para representarlo correctamente no se recuperan, por lo que termino con elementos faltantes/diseño roto.


No soy el único que se ha enfrentado a este problema; un vistazo rápido a los complementos foro de soporte en wordpress.org muestra que este problema es común.

Nota: no intentaría solucionar esto si el complemento tuviera muchos otros problemas. Funciona para mí, solo necesito los scripts para volver a disparar.

La respuesta oficial es que es posible recargar/reactivar scripts agregando lo siguiente en los archivos php del complemento:

$.getScript(rootUrl + 'PATH TO SCRIPT');

que funciona Funciona bien. por ejemplo si agrego

$.getScript(rootUrl + '/Assets/masonry.js');

Luego, las llamadas a la función de mampostería se vuelven a activar cuando se obtiene el contenido ajaxado, incluso si masonry.js se carga fuera del ajaxed. div

te remito a la archivos del complemento en github para obtener más claridad sobre lo que realmente hace la solución (no puedo entender qué sucede cuando $.getScript se usa)


En resumen

La solución oficial funciona bien si tiene 3 o 4 scripts que deben volver a ejecutarse en contenido ajax.

Esto no funciona para mi objetivo porque

  1. es demasiado rígido y codificado
  2. Algunos de los scripts se agregan a la página dinámicamente a través de Aplicaciones de Cloudflare

Una posible solución podría implicar agregar un evento que imite el disparador que hace que los scripts se disparen en la parte inferior del ajaxed. div

Pregunta:

¿Cómo fuerzo todos los scripts? incluyendo los agregados dinámicamente – para volver a disparar cuando solo una cierta parte de la página se ha recargado a través de ajax?


Notas:

  1. Estoy tratando de evitar llamar a los scripts uno por uno, ya que eso requeriría el conocimiento de sus llamadas de antemano. probablemente soy hablando muy por encima de mi cabeza pero…

    estoy tratando de imitar la carga de la pagina y/o eventos listos para documentos – en los que la mayoría se disparan scripts condicionales (corrígeme si me equivoco) – al final de <main> en mi html cuando se agrega nuevo contenido ajaxado pero sin afectar el documento cuando la página se carga mediante el uso de la URL directamente… o cualquier otro enfoque similar.

  2. Solo por un poco de contexto, aquí hay una lista de algunos de los oyentes de eventos en la página mientras el complemento está desactivado. Sé que hay cosas allí que no tendré que activar. Acabo de agregar esto como referencia. Además, tenga en cuenta que esta es una muestra tomada de una de las páginas. otras páginas pueden diferir.

DOMContentLoaded
beforeunload
blur
click
focus
focusin
focusout
hashchange
keydown
keyup
load
message
mousedown
mousemove
mouseout
mouseover
mouseup
mousewheel
orientationchange
readystatechange
resize
scroll
submit
touchscreen
unload

  • Todos sus complementos pueden tener métodos que les permitan hacer lo que hacen en la carga de la página, incluso después de que la página se haya cargado. por ejemplo, la mampostería (que no conozco personalmente) expone una masonry() método en los objetos de jQuery. Entonces puede usar estos métodos en lugar de trucos dudosos.

    – Kaiido

    15 de agosto de 2017 a las 5:00


  • ¿Cuál es el significado de ‘tl; dr’ de su pregunta? Parte superior de la pregunta…

    – Soy la persona más estúpida

    15 de agosto de 2017 a las 5:01

  • @DonkeyKing acronymfinder.com/TLDR.html => “Demasiado largo; no leer” (no lea todo y salte al final/resumen/conclusión si lo desea)

    – Vivek Athalye

    16 de agosto de 2017 a las 4:44

  • @VivekAthalye Gracias 🙂 Pensé que se usa solo en la red Stack Exchange …

    – Soy la persona más estúpida

    16 de agosto de 2017 a las 4:48

avatar de usuario
cjg

La solución que elija aquí tendrá que depender de cómo se inicialicen los scripts. Hay un par de posibilidades comunes:

  1. Las acciones del script se evocan inmediatamente después de cargar el script. En este caso, el script podría verse así:

    (function() {
         console.log('Running script...');
    })();
    
  2. Las acciones del guión se evocan en respuesta a algún evento. (como eventos de documento listo (JQuery) o ventana onload (JavaScript)). En este caso, el script podría verse así:

    $(window).on('load', function() {
        console.log('Running script...');
    });
    

Algunas opciones para estas dos posibilidades se consideran a continuación.

Para scripts que se ejecutan inmediatamente al cargar

Una opción sería simplemente eliminar el <script> elementos que desea activar desde el DOM y luego vuelva a adjuntarlos. Con JQuery, podrías hacer

$('script').remove().appendTo('html');

Por supuesto, si el fragmento anterior está en sí mismo en un <script> etiqueta, entonces creará un ciclo infinito de constantemente desconectar y volver a adjuntar todos los scripts. Además, puede haber algunos scripts que no desee volver a ejecutar. En este caso, puede agregar clases a los scripts para seleccionarlos de manera positiva o negativa. Por ejemplo,

// Positively select scripts with class 'reload-on-ajax'
$('script.reload-on-ajax').remove().appendTo('html');

// Negatively select scripts with class 'no-reload'
$('script').not('no-reload').remove().appendTo('html')

En su caso, colocaría uno de los fragmentos anteriores en el controlador de eventos para completar AJAX. El siguiente ejemplo usa un clic de botón en lugar de un evento de finalización de AJAX, pero el concepto es el mismo (tenga en cuenta que este ejemplo no funciona bien dentro de la zona de pruebas de StackOverflow, así que intente cargarlo como una página separada para obtener el mejor resultado) :

<html>
  <head></head>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

  <script class="reload-on-ajax">
    console.log('Script after head run.');
  </script>

  <body>
    <button id="reload-scripts-button">Reload Scripts</button>
  </body>

  <script class="reload-on-ajax">
    console.log('Script after body run.');
  </script>

  <script>
     $('#reload-scripts-button').click( () => $('script.reload-on-ajax').remove().appendTo('html') );
  </script>
</html>

Tenga en cuenta que si los scripts no están en línea (por ejemplo, obtenidos a través del src atributo), luego se volverán a cargar desde el servidor o se recuperarán de la memoria caché del navegador, según el navegador y la configuración. En este caso, el mejor enfoque es probablemente eliminar todos los <script>s que operan en el contenido cargado con AJAX y los cargan/ejecutan a través de algo como JQuery getScript() desde un controlador de eventos de finalización de AJAX. De esta manera, solo cargará/ejecutará los scripts una vez en el momento apropiado (es decir, después de cargar el contenido AJAX):

// In AJAX success event handler
$.getScript('script1.js');
$.getScript('script2.js');

Un problema potencial con ambas variantes de este enfoque es que la carga asíncrona del script está sujeta a restricciones de origen cruzado. Entonces, si los scripts están alojados en un dominio diferente y no se permiten las solicitudes de origen cruzado, no funcionará.

Para scripts que se ejecutan en respuesta a un evento

Si los scripts se activan al cargar la ventana, puede activar este evento:

   $(window).trigger('load');

Desafortunadamente, si los propios scripts usan el evento de preparación de documento de JQuery, entonces no conozco una manera fácil de activarlo manualmente. También es posible que los scripts se ejecuten en respuesta a algún otro evento.

Obviamente, puede combinar los enfoques anteriores según sea necesario. Y, como han mencionado otros, si hay algunas funciones de inicialización en los scripts a las que simplemente podría llamar, entonces esa es la forma más limpia.

avatar de usuario
beni

Si puede identificar una función de inicialización global o un bloque de código en sus scripts externos, puede echar un vistazo al evento ‘ajaxComplete’. Puede poner este código en el encabezado de su página y poner las llamadas de función de inicialización o los bloques de código dentro de la devolución de llamada ajaxComplete.

$(document).ajaxComplete(function(){
    module1.init();
    $('#my_id').module2_init({
        foo : 'bar',
        baz : 123
    });
});

Cuando los scripts de los que está hablando no tienen funciones de inicialización expuestas tan fáciles de usar, pero se inicializan en la carga del script, creo que no habrá un método listo para usar que funcione para todos los scripts.

Esto es lo que puedes probar:

La mayoría de los scripts como Masonry o Google Map están configurados para reiniciarse al cambiar el tamaño de la ventana. Por lo tanto, si activa el evento de cambio de tamaño después de completar ajax, ayudará a volver a activar esos scripts automáticamente.

Pruebe el siguiente código:

$( document ).ajaxComplete( function() {
    $( window ).trigger('resize');
} );

Esto obligará a los scripts a reiniciarse una vez que se complete ajax, ya que ahora activará el evento de cambio de tamaño después de que se cargue el contenido.

avatar de usuario
iscmaro

Tal vez arriesgado, pero debería poder usar DOMSubtreeModified en tu <main> elemento para esto.

$('#ajaxed').bind('DOMSubtreeModified', function(){
  //your reload code
});

Entonces debería poder agregar todos sus scripts nuevamente en su área de recarga

var scripts = document.getElementsByTagName('script');
for (var i=0;i<scripts.length;i++){
   var src = scripts[i].src;
   var sTag = document.createElement('script');
   sTag.type="text/javascript";
   sTag.src = src;
   $('head').append(sTag);

}

Otra opción podría ser crear su propio detector de eventos y tener el mismo código de recarga en él.

$(document).on('ajaxContentLoaded', function(){//same reload code});

Luego, podría desencadenar un evento en el complemento una vez que se haya actualizado el contenido.

$(document).trigger('ajaxContentLoaded');

O posiblemente una combinación de editar el complemento para activar un oyente y agregar a su base de código para volver a ejecutar cualquier cosa que sienta que necesita volver a ejecutar ese oyente, en lugar de recargar nada.

$(document).on('ajaxContentLoaded', function(){
   //Javascript object re-initialization
   myObj.init();
   //Just a portion of my Javascript?
   myObj.somePortion();
});

avatar de usuario
Hitmands

Una solución podría ser duplicar todos los scripts…

$(document).ajaxSuccess((event, xhr, settings) => {
  // check if the request is your reload content
  if(settings.url !== "myReloadedContentCall") {
    return;
  }

  return window
    .setTimeout(rejs, 100)
  ;
});

function rejs() {
  const scripts = $('script[src]');
  // or, maybe alls but not child of #ajax
  // const scripts = $('*:not(#ajax) script[src]');

  Array
    .prototype
    .forEach
    .call(scripts, (script, index) => {
      const $script = $(script);

      const s = $('<script />', {
        src: $script.attr('src')
      });  
      // const s = $script.clone(); // should also work

      return s
        .insertAfter($script)
        .promise()
        .then(() => $script.remove()) // finally remove it
      ;
    })
  ;

}

Tuve exactamente este problema cuando intenté usar ajax para recargar una página con estados del navegador e history.js, en wordpress. Puse en cola history.js directamente, en lugar de usar un complemento para hacer eso por mí.

Tenía toneladas de JS que necesitaban “volver a ejecutarse” cada vez que se hacía clic en una nueva página. Para hacer esto, creé una función global en mi archivo javascript principal llamado global_scripts();

En primer lugar, asegúrese de que este archivo JS esté en cola después de todo lo demás, en su footer.php.

Eso podría ser algo como esto:

wp_enqueue_script('ajax_js', 'url/to/file.js', 'google-maps', 1, true);

Mi javascript que pongo en cola está debajo.

jQuery(document).ready(function($) {

    // scripts that need to be called on every page load
    window.global_scripts = function(reload) {


       // Below are samples of the javascript I ran that I needed to re run.
       // This includes lazy image loading, paragraph truncation.

       /* I would put your masonry and google maps code in here. */

        bLazy = new Blazy({
            selector: '.featured-image:not(.no-blazy), .blazy', // all images
            offset: 100
        });


        /* truncate meeeee */
        $('.post-wrapper .post-info .dotdotdot').dotdotdot({
            watch: 'window'
        });

    }

    // call the method on initial page load (optional)
    //global_scripts();

});

Luego me conecté al JS que se ejecutaba cada vez que se cargaba una página con history.js y llamaba a global_scripts();

Parece que el complemento que está utilizando también usa history.js. No lo he probado específicamente con su complemento, pero puede probar lo que publiqué a continuación (que es lo que uso en mi aplicación). Esto iría en el mismo archivo JS anterior.

History.Adapter.bind(window, 'statechange', function() { 
     global_scripts();
}

Mi código es un poco más complicado que lo que simplemente se pega arriba. De hecho, verifico el evento para determinar qué contenido se está cargando y cargo funciones JS específicas en función de eso. Permite un mayor control sobre lo que se recarga.

nota: uso window.global_scripts cuando declaro la función porque tengo archivos JS separados que contienen este código. Puede optar por eliminar esto si todos están en el mismo.

nota 2: me doy cuenta de que esta respuesta requiere saber exactamente qué se debe recargar, por lo que ignora su última nota, que solicita que esto no requiera dicho conocimiento. Sin embargo, aún puede ayudarlo a encontrar su respuesta.

avatar de usuario
nils ruckmann

Respuesta corta: No es posible hacer esto de forma genérica. ¿Cómo debe saber su secuencia de comandos qué eventos deben activarse?

Respuesta más larga: es más una pregunta estructural que programática. ¿Quién es responsable de la funcionalidad deseada? Tomemos la albañilería como ejemplo:

Masonry.js en sí mismo no escucha ningún evento. Debe crear una instancia de mampostería por su cuenta (lo que probablemente se haga en domready en su complemento de WordPress). Si habilita la opción de cambio de tamaño, agregará un oyente al evento de cambio de tamaño. Pero lo que realmente quiere es un oyente en “cambio de contenido DOM” (ver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver para una posible solución). Dado que masonry.js no proporciona dicha función, tiene las siguientes opciones:

  1. Espere la implementación (o hágalo usted mismo) en masonry.js
  2. Espere la implementación (o hágalo usted mismo) en el complemento de WordPress de mampostería.
  3. Agregue la funcionalidad en otro lugar (su plantilla, su complemento ajax, etc.)
  4. 4.

Como puede ver, cada opción incluye algo de trabajo por hacer y este trabajo debe hacerse para cada secuencia de comandos que desee escuchar los cambios de DOM invocados por AJAX.

¿Ha sido útil esta solución?