Efficient way to check for changes within element

Hi there,

I’m building out a script which adds some messaging to a listing of products depending on some conditions (e.g. whether they have messaging applied already and if they’re not on an excluded list of URLs stored within an array).

The code I have put together runs on the first load but then fails to run again when the user changes the page number or uses the filtering options. I previously had multiple ways to check for this type of change which meant I could watch the page and act accordingly (i.e. re-fire the test). However, due to a change in the provider for the feed, these functions no longer work for whatever reason (any thoughts as to why?).

The main changes I would like to look for are the URL being amended (due to a user using the filtering options) or when the user navigates to another page of results which is loaded via Ajax I believe. Both of these changes modify the URL within the address bar and also add a result in the browser history so maybe if I can sniff that, I can reapply the messaging to the new set of products.

Option 1

  watchPLP = cb => {
    services.onMutate({
      mutatedNode: document.querySelector("#productlist"),
      settings: {
        attributes: true,
        childList: true
      },
      cb: function () {
        setTimeout(function () {
          if (dom.get(".label_wrapper") === null) {
            cb();
          }
        }, 500);
      }
    });

    console.log("watching");
  };

}

Option 2

	watchPLP = (cb) => {
		window.addEventListener('popstate', function (event) {
			setTimeout(() => {
				if (document.querySelector('.plp-productbadge') === null) {
					cb();
				}
			}, 500);
		});
	};

One further option which I have explored is MutationObserver, as follows which works as it returns the number of changes via a console log. Something which I could build in is if the number of changes is equal or greater than 1 (or something like that) and then fire the function.

However, the other issue is that I cannot seem to fire the messaging function again as I get a “is not defined” error when I try to call it from within the watchPLP function.

One other problem is that I cannot use “observer.disconnect();” as it stops the observe altogether, so the observe just continues to run which is surely causing a drain on performance?

Option 3

	watchPLP = (cb) => {
		// select the target node
		var target = document.querySelector('#productlist');

		// create an observer instance
		var observer = new MutationObserver(function (mutations) {
			mutations.forEach(function (mutation) {
				console.log(mutation.type);
			});
		});

		// configuration of the observer:
		var config = { childList: true, subtree: true }

		// pass in the target node, as well as the observer options
		observer.observe(target, config);
	};

Thanks in advance - let me know if any further information would be helpful!

2 Likes

When they filter or paginate, you are not reloading the page at all right? It is all Ajax correct? If so, I would think that your mutation observer is the way to go. I am assuming you are calling watchPLP just ONCE (at page load) and that sets up the observer one time and all changes will then be seen by the observer. You wouldn’t need to call disconnect in that case and it can just sit there observing and shouldn’t be a performance bottleneck. I mean, that is what it is for, to sit there and observe.

Ajax would then modify the productlist, which would trigger the mutations on observer and you can act accordingly. As long as productlist itself is not removed from the dom, all should be good.

Hopefully I am understanding the issue correctly.

That is correct - everything is loaded into the page without a refresh so I assume that it is all Ajax-related.
Yes, watchPLP is called once at the start when the page loads.

I’m not aware if the productlist element is removed - going by the DevTools Elements screen, only the internal parts of the productlist element are modified - the target element itself doesn’t change.

I can see that the following console log gets called for every change so it appears to be working. The issue now is that I would need to limit the number of function calls, otherwise it’s going to attempt to call it several hundred times based on the number of changes being observed.

console.log(mutation.type);

There is also the problem that I cannot seem to call the function successfully - it simply says that it isn’t defined? Below is the start of the function I’m trying to call which works when called via an external file via “Exp.addPlpProductTileBadge();” but doesn’t when I try to call it internally for some reason.

	watchPLP = (cb) => {
		// select the target node
		var target = document.querySelector('#productlist');

		// create an observer instance
		var observer = new MutationObserver(function (mutations) {
			mutations.forEach(function (mutation) {
				console.log(mutation.type);
				addPlpProductTileBadge();
			});
		});

		// configuration of the observer:
		var config = { childList: true, subtree: true }

		// pass in the target node, as well as the observer options
		observer.observe(target, config);
	};

Thanks!

The reason for that issue is that the place from where you are calling it internally, occurs somewhere before the function is being defined.

Move the function up in the code to a place before where it is being called, and that should resolve the problem.

If we can take a look at more of the code, we will be able to offer better advice other than:
“function isn’t defined”? => Define the function before that location.

It sounds like throttle might be a solution here.

Here is a quick experiment using lodash throttle and the mutation observer.

const watch = (selector, callback, throttleDelay = 1000) => {
  // handler that will be called on mutation
  const handler = (mutations) => { mutations.forEach(callback); }
  
  // throttle the handler calls e.g. 1000ms
  const observer = new MutationObserver(_.throttle(handler, throttleDelay));
  
  // select the target node
  const target = document.querySelector(selector);
  
  // configuration of the observer:
  const config = { attributes: true, childList: true, subtree: true }
  
  // pass in the target node, as well as the observer options
  observer.observe(target, config);
};

watch('#productList', (mutation) => {
  console.log('toggled', mutation.target)
})

Here is a test. Using setInterval the first product in a list is toggled every 100ms. In the console changes to the product are logged every 1000ms.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.