Search code examples
javascriptmutation-observers

mutationobserver firing when no change and method not found issue


I've placed an observer on an element using MutationObserver. In a one use case it works exactly as expected and fires after a change but in another user action where the element has not changed it appears to be firing - but in doing so it comes up with a method not found error, which doesn't appear in the first use case using the same observer.

The observer watches for an update within an element which gets updated with an image as a user selects an image.

In the working case a user selects an image from a list of images, it then updates and the observer fires - all great. In the non-working case a user uploads an image - at this point though no update has happened to the target element (which is in view but below a colorbox.(not sure if that's relevant).

The firing itself would not normally be a problem but within the observer callback it calls a method which in the second case it says is not defined. So in the first instance there are no errors but in the second instance:

I get an error _this.buttons is not a function at MutationObserver.callback

The code is being compiled with webpack

  1. 1. Why is the observer firing when the doesn't appear to the type of change being observed?
  2. Why does this error occur in this scenario - when the method appears to exist and works as expected when there is a change?

Any help appreciated

here's the code - this class manages the actions for a page - I've removed some code to try and keep it brief (but still a bit lengthy - refactoring to be done):

First, here's the code of the observer:

const callback = (mutationsList, observer) =>{
            // Use traditional 'for loops' for IE 11
            for(let mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    console.log('A child node has been added or removed.');
                    module.buttons();
                    module.initialiseControls();
                }
                else if (mutation.type === 'attributes') {
                    console.log('The ' + mutation.attributeName + ' attribute was modified.');
                }
            }
        };

And here's the class in which the observer method is contained.

export let fileImageWidgetControls = class {

    constructor({
                    previewBtn = '.preview-image',
                    addBtn = '#add-image',
                    replaceBtn = '#replace-image',
                    removeBtn = '#remove-image'
                } = {}) {
        this.options = {
            previewBtn: previewBtn,
            addBtn: addBtn,
            replaceBtn: replaceBtn,
            removeBtn: removeBtn
        }

        this.filemanager = new filemanagerHandler; //file selector class
        this.imageWidget = new updateWidget; //handles updating the image
        this.initialiseControls(); //sets up the page controls
        this.observer(); //sets up the observer


    }



    openFileManager =  () =>{
        //open colbox (which opens filemanager page
        //When filemanager loaded then initialise filemanager
        $(document).bind('cbox_complete', ()=>{
            console.log('Colbox complete');
            this.filemanager.init();
        });
        //handle colbox closing and update image in widget (if needed)
        $(document).bind('cbox_closed', ()=>{
            let selectedAsset = this.filemanager.getSelectedAsset();
            if(selectedAsset) {

                this.imageWidget.update(selectedAsset.filename);

            }

        });
        colBox.init({
            href: this.options.serverURL
        });
        colBox.colorbox()

    }

    remove = ()=> {
        //clear file and update visible buttons

        this.buttons();

    }
    /**
     * preview the image in a colorbox
     * @param filename
     */
    preview = function () {
        //open image in preview

    }
    /**
     * select image via filemanager
     */
    select =  () =>{

        console.log('select');
        this.openFileManager();

    }

    replace =  () => {
    //    image already exists in widget but needs replacing
        console.log('replace');
        this.openFileManager();
    }

    initialiseControls = () => {
        console.log('init controls');
        //preview button
        $(this.options.previewBtn).on('click', (e) => {

            e.preventDefault();
            this.preview();

        }).attr('disabled', false);

        $('#img-preview-link').on('click', (e)=> {
            e.preventDefault();
            this.preview();
        });

        // add button
        $(this.options.addBtn).on('click', (e) => {

            e.preventDefault();
            this.select();

        }).attr('disabled', false);

        //replace button
        $(this.options.replaceBtn).on('click', (e) => {

            e.preventDefault();
            this.replace();

        }).attr('disabled', false);

        //remove button
        $(this.options.removeBtn).on('click', (e) => {

            e.preventDefault();
            this.remove();

        }).attr('disabled', false);

        this.buttons();

    }
    //set an observer to watch preview image for changes
    observer= ()=> {
        const module = this;
        const targetNode = document.getElementById('image-preview-panel');
        const config = { attributes: true, childList: true, subtree: true };
        const callback = (mutationsList, observer) =>{
            // Use traditional 'for loops' for IE 11
            for(let mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    console.log('A child node has been added or removed.');
                    module.buttons();
                    module.initialiseControls();
                }
                else if (mutation.type === 'attributes') {
                    console.log('The ' + mutation.attributeName + ' attribute was modified.');
                }
            }
        };
        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
    }

    buttons = function() {

        let imagePreview = $('#image-preview');

        if(imagePreview.data('updated')=== true && imagePreview.data('updated') !== "false") {
            console.log('image present');
            $(this.options.addBtn).fadeOut().attr('disabled', true);
            $(this.options.removeBtn).fadeIn().attr('disabled', false);
            $(this.options.replaceBtn).fadeIn().attr('disabled', false);
            $(this.options.previewBtn).fadeIn().attr('disabled', false);
        } else {
            console.log('image not present', imagePreview.data());
            console.log('image element:', imagePreview);
            $(this.options.addBtn).fadeIn().attr('disabled', false);
            $(this.options.removeBtn).fadeOut().attr('disabled', true);
            $(this.options.replaceBtn).fadeOut().attr('disabled', true);
            $(this.options.previewBtn).fadeOut().attr('disabled', true);
        }
    }
}

I copied code from a tutorial hence some of the comments until I refactor


Solution

  • Added const module = this; within the method and referenced that within the nested function and now pointing to the correct this