简体   繁体   中英

Make AJAX call via dynamically inserted DOM element, do I have to use a Mutation Observer?

In my current application, I have elements on my page that start out "empty," and load their display data from an AJAX call.

A simple way to implement this is to use a class, (say .ajax-loader ) and loop through each of those and load the AJAX data from data- attributes on the element itself.

So something like this ( jsfiddle ):

<div class="ajax-loader" data-url="/echo/html/" data-html="<p>hello world</p>" data-delay="1">Loading...</div>

<script>
  $('.ajax-loader').each(function() {
    var ele = $(this);
    var url = ele.data('url');
    var html = ele.data('html');
    var delay = ele.data('delay');

    return $.post({
      url: url,
      data: {
        html: html,
        delay: delay
      }
    }).done(function(d) {
      ele.html(d);
    });
  });
</script>

This works fine for now, however, the problem arises when I need to insert these .ajax-loader elements dynamically into the page. My .each() function only runs once, and so does not run again if a new .ajax-loader element is inserted into the page (you can see that in the previous jsfiddle by clicking the button).

So, I put in a Mutation Observer to detect DOM changes. Now my code looks like this ( jsfiddle ):

<button class="insert-ele">Click to insert new AJAX object</button>
<div id="container">
  <div class="ajax-loader" data-url="/echo/html/" data-html="<p>Hello World!</p>" data-delay="1">Loading...</div>
</div>

<script>
$(document).ready(function() {
  // Insert new ".ajax-loader" objects on button click
  $('.insert-ele').on('click', function(e) {
    $('#container').append('<div class="ajax-loader" data-url="/echo/html/" data-html="<p>I was inserted via JS.</p>" data-delay="1">Loading...</div>');
    e.preventDefault();
  });

  // Define our ajaxLoader function
  var ajaxLoader = function() {
    var ele = $(this);
    ele.addClass('ajax-load-started');

    var url = ele.data('url');
    var html = ele.data('html');
    var delay = ele.data('delay');

    return $.post({
        url: url,
      data: {
        html: html,
        delay: delay
      }
    }).done(function(d) {
        ele.html(d);
      ele.addClass('ajax-load-completed');
      ele.removeClass('ajax-load-started');
    });
  };

  // Loop through each .ajax-loader, and load their AJAX request.
  $('.ajax-loader:not(.ajax-load-completed):not(.ajax-load-started)').each(ajaxLoader);

  // Observe DOM changes, and load AJAX data when it does change
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  var observer = new MutationObserver(function(mutations) {
    $('.ajax-loader:not(.ajax-load-completed):not(.ajax-load-started)').each(ajaxLoader);
  });

  // Activate the observer
  var container = document.getElementById('container');
  observer.observe(container, {
    attributes: true, 
    childList: true, 
    subtree: true
  });
});
</script>

However, looking at the Browser Compatibility for for Mutation Observers , I see it is only supported in IE 11. I need to support IE 9, and possibly IE 8 although that isn't as important.

Is there a better way to accomplish this? Or simply another way that'll give me better browser compatibility?

I also realize that I can run the $('.ajax-loader').each() function within the $('.insert-ele').on('click') handler, but I'd like to avoid that solution if possible as well.

Mutation observing isn't a great fit here (regardless of how you'd implement it): checking for changes to the DOM (or any object) is kind of expensive, as it requires keeping a copy of the object and iterating through the entire thing, recursing into the object's properties to check for differences. In your case you don't need to be aware of any change, you want to know when a specific event has occurred (dom elements added to page).

You're right to want a 'one size fits all' solution, and it shouldn't be too hard to implement. Somewhere you have an AJAX callback that's responsible for adding those elements to the page. (If you have more than one function that's responsible for this, now is the time to consolidate them into one.) When this AJAX callback function has inserted the DOM elements, have it a) trigger a callback that you define, or b) trigger a custom event (probably via jQuery, but Backbone and other libraries have custom event utilities also) that your other code can listen to. However you approach it, you want the function inserting the DOM elements to do something that allows other code to respond to the action.

If the code responsible for inserting the DOM elements to the page is third-party, this might be trickier. (And a good time to consider whether you'd be better off writing your own implementation.) Look at the source for that code and double check that it doesn't support a callback or event or something that allows you to integrate with it. If not, you should still be able to respond to the DOM insertion by listening for the load event.

Every DOM element triggers a load event when it's loaded into the DOM. You can't attach an event listener to the elements that are being inserted because they don't exist yet, but you can use event delegation : attach an event listener on the parent element for the load event and it will fire when the elements have been inserted (because the events bubble up from the inserted element to its parent).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM