0

I'm working on a Prestashop site project with a particularly fiddly megamenu setup.

The MutationObserver does a pretty good job of pushing the menu back when the cart overlay is open, but the issue if the menu is dynamically pulled in by another JavaScript library that is obfusticated beyond any rescue and as such the MutationObserver gets unlinked from the node each time, causing everything to be drawn incorrectly if anything is added to the cart after page load.

The MutationObserver is currently linked directly to the main page container object (category).

//  CREATE A MUTATION OBSERVER OBJECT TO MONITOR ANY STATE CHANGES ON THE JS NODE
    let observer = new MutationObserver(blockCartOpened);

    function blockCartOpened(mutationRecords) {
    //  PROCESS THE LIST OF MUTATION THAT HAVE OCCURED
        mutationRecords.forEach (function (mutation) {
        //  GET THE MUTATION TARGET AND SEE IF THE DISPLAY STYLE ATTRIBUTE IS SET OR NOT
        //  AND REACT ACCORDINGLY
            var target = $(mutation.target);
            if(target.hasClass("cart_block")) {
                if(target.css("display") == "block") {
                    $(".ets_mm_megamenu").css("z-index", "-1");
                } else {
                    $(".ets_mm_megamenu").css("z-index", "2");
                }
            }
            mutation.addedNodes.forEach(function(node) {
            //  Nothing to be here yet...
            });
        });
    }

//  SET THE TARGETNODE AS WELL AS THE SETTINGS FOR WHAT TO TRIGGER THE OBSERVER ON, IN THIS CASE
//  TRIGGER THE OBSERVER ON ANY CHANGES TO THE STYLE ATTRIBURES OF THE NODE
    var targetNodes = document.getElementById("category");
    var observerOptions = {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["style"]
    }

//  SINCE THIS IS A CLASS, A EACH LOOP IS REQUIRED EVEN IF THERE IS ONLY ONE RESULT
    observer.observe(targetNodes, observerOptions);

Is there a way to monitor for the attribute changes on created child nodes inside the parent, or is this just a pipe dream?

  • Your observer config is already observing `style` attribute in all child nodes so I don't understand your last paragraph. If the observer is unlinked it means only one thing: the targetNodes or its ancestor is removed from DOM (and replaced with the new one). You can observe on a higher ancestor, maybe even `document` itself. – wOxxOm May 29 '20 at 09:50
  • The `#comment` ID is the page wrapper object, so it's about as high as it gets without observing the `body` itself, but yes, the `cart_block` children are removed and readded to the DOM dynamically by another library which I cannot directly modif for framework stability reasons, thus when the block in question is regerated, the `MutationObserver` no longer responds to any attribute mutations on those particular children, which is the issue I need to address. –  May 29 '20 at 10:00
  • Well, a removed node cannot be observed, it's garbage-collected after a while and poof, gone forever. The only way is to attach higher. You can have two observers: a lightweight one for the document.body (childList:true, without subtree:true) to detect the moment when the container is replaced, then attach the observer to the new container. – wOxxOm May 29 '20 at 10:05
  • I was open to the idea of having two Observers, but at first I was hesitant due to potential overhead issues, but I'll give the two observers suggestion a try, just making sure to keep it clean. I'll revert with an update if it works as expected. –  May 29 '20 at 10:11
  • Your current observer can be optimized, primarily by ditching jQuery, see [Performance of MutationObserver to detect nodes in entire DOM](https://stackoverflow.com/a/39332340) – wOxxOm May 29 '20 at 10:13
  • I can substantially narrow the search area, but sadly jQuery is baked into the entire framework, so at this point ditching jQuery wouldn't go off without it's own set of issues. I can't make any large or overreaching changes to the core framework sadly (Prestashop's glorious compiled JS files and such), so I'm a bit hamstrung with my choices, but the second observer plan sounds like a sound idea and I'll definitely try that. –  May 29 '20 at 10:23
  • Also, I really appreciate all the help! Sadly, being a backend developer, playing JS dev doesn't always go the way I expect. I'm giving the link as read and implementing as much of the advice therein as possible. Thanks again! –  May 29 '20 at 10:33
  • I meant ditching jQuery in the observer, see the linked answer for more info. – wOxxOm May 29 '20 at 11:35
  • I understood that later after I read your link, sorry, I'm a bit slow today. I'm yet to implement any changes due to being swamped with other work at the moment, but will revert with what I did if it works for future reference –  May 29 '20 at 14:59
  • I've made an attempt at implementing the second observer, but I'm getting a very strange outcome in that, even debugging in the browser console, the observer never fires again even if disconnect and reconnect it or even create a new observer on the same node the target just totally stops creating observation events entirely, I'm yet to do any code clean-up and such, but I find it very strange that even a new observer doesn't pick up on the recreated nodes after the fact. –  Jun 01 '20 at 10:44

1 Answers1

0

Well in the end I didn't have to use a MutationObserver, since the issue proved to be unsuited for that sort of solution. So I took the path of least resistence, i.e the solution isn't elegant but it works PERFECTLY:

//  BPK: CODE ADDITIONS
    $(document).on("click", function(event) {
        var target = $(".blockcart-header");
        if(target.hasClass("open")) {
            $(".ets_mm_megamenu").css("z-index", "-1");
        } else {
            $(".ets_mm_megamenu").css("z-index", "2");
        }
    });

    $(document).on("click", ".blockcart-header a", function(event) {
        var target = $(".blockcart-header");
        if(target.hasClass("open")) {
            $(".ets_mm_megamenu").css("z-index", "-1");
        } else {
            $(".ets_mm_megamenu").css("z-index", "2");
        }
    });
//  END BPK

Using a document wide click event to monitor for "closing" clicks and a click event on the trigger button for the cart display just to be sure solved the problem for me.

Lesson: Don't try to go too complicated to solve a problem...