强制重新触发 ajaxed 内容上的所有(脚本/函数)

Posted

技术标签:

【中文标题】强制重新触发 ajaxed 内容上的所有(脚本/函数)【英文标题】:Force re-firing of all (scripts / functions) on ajaxed content 【发布时间】:2018-01-19 10:57:10 【问题描述】:

tl;dr

我使用 ajax 来获取新内容。内容被提取并添加到页面。但是,脚本不会“重新触发”,因为它们的调用在 ajaxed div 之外。

脚本在初始页面加载时加载和触发没有任何问题,但当我通过 ajax 添加新内容时则没有。我没有收到控制台错误,如果我直接访问 URL 也没有问题。


相关:

    Forcing Script To Run In AJAX Loaded Page - 与一个特定的脚本有关。我想修复(重新启动)所有脚本,包括来自Cloudflare apps 的动态脚本 Using jQuery script with Ajax in Wordpress - 同上,这只涉及一个特定的脚本 ajax loaded content, script is not executing

简介:

我在 WordPress 网站上使用 Ajaxify WordPress Site(AWS)。

该插件允许您通过其 id 选择 div,然后使用 ajax 获取该 div 中的新内容

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>

问题

该插件有效,我加载了新内容并设置了样式,但存在问题。我的一些页面 脚本和函数调用 在我使用 ajax 的 div 之外。我有两个例子

1- Masonry 已加载并在 &lt;footer&gt; 中调用

<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- 谷歌地图电话在&lt;head&gt;

<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>

这只是两个示例。其他地方还有其他人。如您所知,此类脚本不会被重新调用,因为页面上重新加载的唯一内容是 &lt;main id="ajax"&gt;。虽然&lt;main&gt; 中的新内容在那里,但正确呈现它所需的一些脚本没有被重新调用,因此我最终会丢失元素/破坏布局。


我不是唯一一个遇到这个问题的人;快速浏览一下插件的support forum on wordpress.org 就会发现这个问题很常见。

注意:如果插件有很多其他问题,我不会尝试解决这个问题。它对我有用,我只需要重新启动脚本。

官方回应是可以通过在插件的 php 文件中添加以下内容来重新加载/重新触发脚本:

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

哪个有效。它运作良好。例如,如果我添加

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

然后,即使 masonry.js 在 ajaxed div 之外加载,当获取 ajaxed 内容时,masonry 函数调用也会重新触发

我建议您参考plugin's files on github,以便更清楚地了解修复的实际作用(我无法理解使用$.getScript 时会发生什么)


总结

如果您有 3-4 个脚本需要在 ajaxed 内容上重新触发,则官方修复可以正常工作。

这对我的目标不起作用,因为

    它太死板和硬编码 部分脚本通过Cloudflare apps动态添加到页面

一个可能的解决方案可能涉及添加一个事件来模拟触发器,该触发器导致脚本在 ajaxed div 的底部触发

问题:

当仅通过 ajax 重新加载页面的特定部分时,如何强制所有脚本 - 包括动态添加的脚本 - 重新触发?


注意事项:

    我试图避免一一调用脚本,因为这需要事先了解它们的调用。我可能在我头上说话但是......

    我正在尝试模仿页面加载和/或文档就绪事件 - 大多数条件脚本被触发(如果我错了,请纠正我) - 在我的 html 中的 &lt;main&gt; 末尾添加新的 ajaxed 内容但不影响通过直接使用 url 加载页面时的文档...或任何其他类似的方法。

    只是为了一点上下文,这里列出了插件关闭时页面上的一些事件侦听器。我知道里面有些东西我不需要触发。我只是添加了这个以供参考。另外,请注意,这是从其中一页中获取的样本。其他页面可能会有所不同。

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

【问题讨论】:

您的所有插件都可能具有允许它们在页面加载时执行其在页面加载时执行的操作的方法,即使在页面加载后也是如此。例如,石工(我个人不知道)在 jQuery 的对象上公开了一个 masonry() 方法。所以你可以使用这些方法而不是可疑的黑客攻击。 您的问题中的“tl;dr”是什么意思?最重要的问题... @DonkeyKing acronymfinder.com/TLDR.html => “太长;不要阅读”(不要阅读所有内容,如果需要,请跳到底部/摘要/结论) @VivekAthalye 谢谢 :-) 我认为它仅用于 Stack Exchange 网络... 【参考方案1】:

您在此处选择的解决方案必须取决于脚本的初始化方式。有几种常见的可能性:

    脚本的动作在脚本加载后立即被调用。在这种情况下,脚本可能看起来像这样:

    (function() 
         console.log('Running script...');
    )();
    

    脚本的动作是为了响应某些事件而触发的(例如文档就绪 (JQuery) 或窗口加载 (javascript) 事件)。在这种情况下,脚本可能如下所示:

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

下面考虑了这两种可能性的一些选项。

对于在加载时立即运行的脚本

一种选择是从 DOM 中删除要触发的 &lt;script&gt; 元素,然后重新附加它们。使用 JQuery,您可以做到

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

当然,如果上面的 sn-p 本身在一个&lt;script&gt; 标记中,那么您将创建一个不断分离和重新附加所有脚本的无限循环。此外,可能还有一些您不想重新运行的脚本。在这种情况下,您可以将类添加到脚本以正面或负面地选择它们。例如,

// 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')

在您的情况下,您可以将上述 sn-ps 之一放在事件处理程序中以完成 AJAX。以下示例使用按钮单击代替 AJAX 完成事件,但概念相同(请注意,此示例在 *** 沙箱中效果不佳,因此请尝试将其作为单独页面加载以获得最佳结果) :

<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>

请注意,如果脚本不是内联的(例如,通过 src 属性获取),那么它们将从服务器重新加载或从浏览器缓存中检索,具体取决于浏览器和设置。在这种情况下,最好的方法可能是删除所有在 AJAX 加载的内容上运行的 &lt;script&gt;s,并通过 AJAX 完成事件处理程序中的 JQuery 的 getScript() 函数来加载/运行它们。这样,您将只在适当的时间(即在加载 AJAX 内容之后)加载/运行一次脚本:

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

这种方法的两种变体的一个潜在问题是脚本的异步加载受到跨域限制。因此,如果脚本托管在不同的域中并且不允许跨域请求,它将无法正常工作。

对于响应事件而运行的脚本

如果脚本是在窗口加载时触发的,那么你可以触发这个事件:

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

不幸的是,如果脚本本身使用 JQuery 的文档就绪事件,那么我不知道手动触发它的简单方法。脚本也有可能响应其他事件而运行。

显然,您可以根据需要组合上述方法。而且,正如其他人所提到的,如果脚本中有一些您可以调用的初始化函数,那么这是最干净的方法。

【讨论】:

【参考方案2】:

如果您可以在外部脚本中识别全局初始化函数或代码块,则可以查看“ajaxComplete”事件。您可以将此代码放在页面头中,并将初始化函数调用或代码块放在 ajaxComplete 回调中。

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

当您谈论的脚本没有如此易于使用的公开初始化函数,而是在 scriptload 时自行初始化时,我认为不会有适用于所有脚本的开箱即用方法。

【讨论】:

【参考方案3】:

这是你可以尝试的 -

大多数脚本(如 masonry 或 Google Map)都设置为在调整窗口大小时重新初始化。因此,如果您在 ajax 完成后触发 resize 事件,将有助于自动重新触发这些脚本。

试试下面的代码 -

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

这将强制脚本在 ajax 完成后重新初始化,因为它现在将在加载内容后触发调整大小事件。

【讨论】:

【参考方案4】:

也许有风险,但您应该可以在您的 &lt;main&gt; 元素上使用 DOMSubtreeModified 来实现此目的。

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

然后你应该能够在你的重新加载区域再次附加所有脚本

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


另一种选择是创建您自己的事件侦听器并在其中包含相同的重新加载代码。

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

然后您可以在内容更新后触发插件中的事件。

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

或者可能是编辑插件以触发侦听器并添加到您的代码库以重新运行您认为需要重新运行该侦听器的任何内容的组合,而不是重新加载任何内容。

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

【讨论】:

【参考方案5】:

解决方案可能是复制所有脚本...

$(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
      ;
    )
  ;


【讨论】:

【参考方案6】:

在 wordpress 中尝试使用 ajax 重新加载包含浏览器状态和 history.js 的页面时,我遇到了这个确切的问题。我直接将 history.js 加入队列,而不是使用插件为我做这件事。

每当单击新页面时,我都有大量需要“重新运行”的 JS。为此,我在我的主 javascript 文件中创建了一个全局函数,名为 global_scripts();

首先,确保此 JS 文件排在其他所有内容之后,在您的 footer.php 中。

可能看起来像这样:

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

我排入队列的 javascript 在下面。

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

);

然后我连接到每当页面加载 history.js 并调用 global_scripts() 时运行的 JS;

您使用的插件似乎也使用了 history.js。我没有专门用你的插件测试过,但你可以试试我在下面发布的(这是我在我的应用程序中使用的)。 这将放在上面的同一个 JS 文件中。

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

我的代码比上面简单粘贴的要复杂一些。我实际上检查了事件以确定正在加载的内容,并基于此加载特定的 JS 函数。它允许对重新加载的内容进行更多控制。

注意:我在声明函数时使用 window.global_scripts,因为我有单独的 JS 文件来保存此代码。如果它们都相同,您可以选择删除它。

note2:我意识到这个答案需要确切地知道需要重新加载什么,所以它忽略了你的最后一个注释,它要求这不需要这些知识。不过,它仍然可以帮助您找到答案。

【讨论】:

【参考方案7】:

简短回答: 这是不可能的通用方式。您的脚本应该如何知道需要触发哪些事件?

更长的答案: 它更像是一个结构性问题,而不是一个程序性问题。谁负责所需的功能?让我们以砖石为例:

Masonry.js 本身不监听任何事件。您需要自己创建一个砌体实例(这很可能在您的 Wordpress 插件中的 domready 上完成)。如果启用调整大小选项,它将为调整大小事件添加一个侦听器。但您真正想要的是一些关于“DOM 内容更改”的监听器(请参阅https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver 以获取可能的解决方案)。由于 masonry.js 不提供这样的功能,您有以下选择:

    在 masonry.js 中等待实现(或自己动手) 等待 masonry Wordpress 插件中的实现(或自己做)。 在其他地方添加功能(您的模板、您的 ajax 插件等) 4.

正如您所看到的,每个选项都包含一些需要完成的工作,并且需要为您想要监听 AJAX 调用的 DOM 更改的每个脚本完成这项工作。

【讨论】:

以上是关于强制重新触发 ajaxed 内容上的所有(脚本/函数)的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 struts 中的 ajax 更新 jsp 页面上的内容时出现问题(触发 onChange 事件时)

防止Ajax触发jQuery脚本

WordPress 和 Ajax - 重新加载短代码内容

当用户单击刷新时处理 ajax 错误

普通 javascript 触发 css 淡出,ajax 加载内容,使用 CSS 淡入

我可以使用 SignalR 在所有客户端上触发 AJAX 请求吗?