Pregunta Fuerce el reinicio de todos (scripts / funciones) en un contenido ajaxed


tl; dr

Uso ajax para buscar contenido nuevo. El contenido se busca y se agrega a la página. Sin embargo, las secuencias de comandos no "vuelven a activarse" porque sus llamadas están fuera del ajaxed div.

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


Relacionado:

  1. Forzar secuencia de comandos para ejecutar en la página cargada AJAX - Se relaciona con un script específico. Quiero arreglar (refinar) todos los scripts incluidos los dinámicos de Aplicaciones Cloudflare
  2. Uso de guiones jQuery con Ajax en Wordpress - Igual que el anterior, esto se relaciona solo con un script específico
  3. contenido cargado de ajax, el script no se está ejecutando

Introducción:

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

El complemento te 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 con estilo, pero hay un problema. Algunos de mi página scripts y llamadas a funciones están fuera del div que uso ajax. Tengo dos ejemplos

1- La mampostería se carga y se 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 a 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, tales scripts no se volverán a llamar ya que lo único que se recarga en la página es <main id="ajax">. Mientras el nuevo contenido dentro <main> esta ahí, algunos de los scripts necesarios para representarlo correctamente no se vuelven a llamar y, por lo tanto, termino con elementos faltantes / diseño roto.


No soy el único que ha enfrentado este problema; una mirada rápida al plugin 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 volver a cargar / volver a generar 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 disparar cuando se recupera el contenido ajax, incluso si masonry.js se carga fuera del ajaxed div 

Te remito a la archivos de plugin en github para mayor claridad sobre lo que realmente hace la corrección (no puedo entender lo que sucede cuando $.getScript se usa)


En resumen

La solución oficial funciona bien si tiene entre 3 y 4 scripts que deben volver a activarse en un contenido ajax.

Esto no funciona para mi objetivo porque

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

Una posible solución podría implicar agregar un evento que imita el desencadenador que hace que los guiones se dispare en la parte inferior del ajaxed div 

Pregunta:

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


Notas:

  1. Estoy tratando de evitar llamar guiones uno por uno, ya que eso requeriría conocimiento de sus llamadas de antemano. Probablemente soy hablando por encima de mi cabeza pero...

    estoy tratando de imitar la carga de la página y / o eventos preparados para documentos, en los cuales más las secuencias de comandos condicionales se disparan (Corrígeme si estoy equivocado) - al final de <main> en mi html cuando se agrega nuevo contenido ajax 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 del evento en la página mientras el plugin está apagado. Sé que hay cosas allí que no tendré que disparar. Acabo de agregar esto para 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


32
2017-08-11 03:24


origen


Respuestas:


La solución que elija aquí 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 la carga del script. En este caso, el script podría verse más o menos así:

    (function() {
         console.log('Running script...');
    })();
    
  2. Las acciones del script se evocan en respuesta a algún evento (como documentos listos (JQuery) o eventos de carga de ventanas (JavaScript)). En este caso, el script podría verse más o menos 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 desencadenar desde el DOM y luego volver a conectarlos. Con JQuery, podrías hacer

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

Por supuesto, si el fragmento de arriba está en sí mismo en una <script> etiqueta, entonces creará un bucle infinito de separación y reconexión constante de todos los scripts. Además, puede haber algunos scripts que no desea volver a ejecutar. En este caso, puede agregar clases a los scripts para seleccionarlos positiva o negativamente. 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 la finalización de AJAX. El siguiente ejemplo utiliza 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 en el entorno limitado de StackOverflow, 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, se obtuvieron 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 todo <script>s que operan en el contenido cargado con AJAX y los cargan / ejecutan a través de algo como JQuery's getScript() función desde un controlador de evento de finalización AJAX. De esta forma, solo cargará / ejecutará los scripts una vez en el momento apropiado (es decir, después de cargar el contenido de AJAX):

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

Un posible problema con ambas variantes de este enfoque es que la carga asincrónica 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 en la carga de la ventana, entonces puede activar este evento:

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

Desafortunadamente, si los propios guiones utilizan el evento listo para documentos de JQuery, entonces no estoy al tanto de una manera fácil de activarlo manualmente. También es posible que los scripts se ejecuten en respuesta a algún otro evento.

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


15
2017-08-13 17:01



Esto es lo que puedes probar:

La mayoría de los guiones, como mampostería o Google Map, están configurados para volver a iniciarse en el cambio de 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 disparar esos scripts automáticamente.

Pruebe el siguiente código -

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

Esto obligará a los scripts a volver a iniciarse una vez que se haya completado ajax, ya que ahora activará el evento de cambio de tamaño una vez que se haya cargado el contenido.


5
2017-08-13 13:58



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 a la 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, sino que se inicializan en la carga de scripts, creo que no habrá un método listo para usar que funcione para todos los scripts.


4
2017-08-11 06:07



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

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

Luego, 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 oyente de eventos y tener el mismo código de recarga.

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

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

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

O posiblemente una combinación de edición del complemento para activar un oyente y agregar a su base de código para volver a ejecutar cualquier cosa que considere necesario para volver a ejecutar ese oyente, en lugar de volver a cargar nada.

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

4
2017-08-15 04:28



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
      ;
    })
  ;

}

4
2017-08-13 17:19



Tuve este problema exacto al intentar usar ajax para volver a cargar una página con los estados del navegador y history.js, en wordpress. Encaré history.js directamente, en lugar de usar un complemento para hacer eso por mí.

Tenía toneladas de JS que necesitaban "volver a ejecutar" 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 verse más o menos así:

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

Mi javascript que encueto 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 enganché en el JS que se ejecutaba cada vez que una página se cargaba con history.js y se llamaba global_scripts ();

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

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

Mi código es un poco más complicado que lo que simplemente se pegó arriba. De hecho, compruebo el evento para determinar qué contenido se está cargando y cargo funciones específicas de JS basadas en eso. Permite un mayor control sobre lo que se vuelve a cargar.

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

nota2: me doy cuenta de que esta respuesta requiere saber exactamente qué se debe volver a cargar, por lo que ignora su última nota, que solicita que esto no requiera tal conocimiento. Sin embargo, puede ayudarte a encontrar tu respuesta.


4
2017-08-15 04:56



Respuesta corta: Esto no es posible de una manera genérica. ¿Cómo debe saber su script qué eventos necesitan ser activados?

Respuesta más larga: Es más como 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. Necesita crear una instancia de mampostería por su cuenta (lo que probablemente se haga en domready en su plugin de Wordpress). Si habilita la opción de cambio de tamaño, agregará un detector al evento de cambio de tamaño. Pero lo que realmente quiere es escuchar al "cambio de contenido DOM" (ver https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver para una posible solución). Como 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 plugin 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 realizarse para cada secuencia de comandos que desee escuchar en los cambios DOM invocados por AJAX.


3
2017-08-13 11:05