Search code examples
javascriptgreasemonkeyinnerhtml

Why does changing the innerHTML, of my box, make my button stop working in Greasemonkey?


So, I wrote a script that loads images one at a time in a new tab... Got a stop button to work thanks to Brock over HERE...

Then I broke it adding in the counter that I had written in while waiting for an answer to that question.

I can't see any reason that should have broke it... help please?

My Script:

var box = document.createElement ('div');
box.id = 'mySelectBox';
GM_addStyle (
    ' #mySelectBox {             ' +
    '    background: white;     ' +
    '    border: 2px solid red; ' +
    '    padding: 4px;          ' +
    //'    position: absolute;    ' +
    '    position: fixed;       ' +
    '    top: 8px; left: 8px;   ' +
    '    max-width: 400px;      ' +
    ' } '
);
document.body.appendChild (box);
box.innerHTML = '';

var searchButton = document.createElement ('div');
searchButton.className = 'mySearchButton';
searchButton.textContent = 'Open';

GM_addStyle (
    ' .mySearchButton {           ' +
    '    background: #aaa;       ' +
    '    border: 1px solid #777; ' +
    '    padding: 1px;           ' +
    '    margin-left: 8px;       ' +
    '    float: right;           ' +
    '    cursor: pointer;        ' +
    ' } '
);
box.insertBefore (searchButton, box.nextSibling);

var stopButton = document.createElement ('div');
stopButton.className = 'myStopButton';
stopButton.textContent = 'Stop';

GM_addStyle (
    ' .myStopButton {           ' +
    '    background: #aaa;       ' +
    '    border: 1px solid #777; ' +
    '    padding: 1px;           ' +
    '    margin-left: 8px;       ' +
    '    float: right;           ' +
    '    cursor: pointer;        ' +
    ' } '
);
box.insertBefore (stopButton, box.nextSibling);

var closeButton = document.createElement ('div');
closeButton.className = 'myCloseButton';
closeButton.textContent = 'X';

GM_addStyle (
    ' .myCloseButton {           ' +
    '    background: #aaa;       ' +
    '    border: 1px solid #777; ' +
    '    padding: 1px;           ' +
    '    margin-left: 8px;       ' +
    '    float: right;           ' +
    '    cursor: pointer;        ' +
    ' } '
);

box.insertBefore (closeButton, box.firstChild);
closeButton.addEventListener ('click', function () {
    box.parentNode.removeChild (box);
}, true);

var mytable = document.getElementById

    ('lair-sort-pets').getElementsByTagName ('img');

var linksToOpen = [];
var mywin2 = null;

var okayToOpenLinks = true;

searchButton.addEventListener ('click', openpics);
stopButton.addEventListener ('click', stopLinkSequence);

function openpics () {
    okayToOpenLinks = true;

    if (linksToOpen.length === 0) {
        for (var J = 0, L = mytable.length; J < L; J++) {

            linksToOpen.push (mytable[J].src); //-- Add URL to list
        }
    }

    openLinksInSequence ();
};

original = box.innerHTML

function stopLinkSequence () {
    okayToOpenLinks = false;
}

function openLinksInSequence () {
    k = linksToOpen.length - 1

    if (mywin2) {
        mywin2.close ();
        mywin2 = null;
    }

    if (okayToOpenLinks && linksToOpen.length) {
        var link = linksToOpen.shift ();
        mywin2 = window.open (link, "my_win2");

        box.innerHTML = '<center>Links left</center><br>' + '<center>' + k + '</center><br>' + original;

        mywin2.addEventListener ('load', openLinksInSequence, false);
    }
}


If I comment out this line it works again (except no status counter):

box.innerHTML = '<center>Links left</center><br>'+'<center>'+k+'</center><br>'+original;


WHY?

HERE is a possible target page for the Greasemonkey script.


Solution

  • When you change an element's contents using innerHTML (or outerHTML), it trashes any event handlers of anything that element contained. It can also help cause performance problems and memory leaks. Don't use innerHTML in userscripts except maybe for the initial creation of brand new nodes.

    For a countdown/status display, create the HTML once, then just alter the data content and/or use CSS to alter the display. Doing that, and sanitizing things a bit, that code becomes:

    var box         = document.createElement ('div');
    document.body.appendChild (box);    // Must append before set outerHTML
    box.outerHTML   = multilineStr ( function () {/*!
        <div id="mySelectBox">
            <div id="myStatus">Links left<br>
                <span>86</span>
            </div>
            <div id="myCloseButton">X</div>
            <div id="mySearchButton">Open</div>
        </div>
    */} );
    
    document.getElementById ("myCloseButton").addEventListener ('click', function () {
        var box = this.parentNode;
        box.parentNode.removeChild (box);
    } );
    var startStopBtn    = document.getElementById ("mySearchButton");
    startStopBtn.addEventListener ('click', startStopPicsLoad);
    
    var mytable         = document.querySelectorAll ('#lair-sort-pets img');
    var linksToOpen     = [];
    var mywin2          = null;
    var okayToOpenLinks = true;
    
    function startStopPicsLoad () {
        if (typeof startStopPicsLoad.isStartBtn === "undefined") {
            startStopPicsLoad.isStartBtn    = true;
        }
        if (startStopPicsLoad.isStartBtn) {
            //-- Change start button to stop button
            startStopBtn.textContent        = "Stop";
            startStopPicsLoad.isStartBtn    = false;
    
            if (linksToOpen.length === 0) {
                for (var J = 0, L = mytable.length; J < L; J++) {
                    linksToOpen.push (mytable[J].src); //-- Add URL to list
                }
    
                //-- Display the status area.
                document.querySelector ('#myStatus').style.display = "block";
            }
    
            openLinksInSequence ();
        }
        else {
            //-- Change stop button to start button
            startStopBtn.textContent        = "Open";
            startStopPicsLoad.isStartBtn    = true;
    
            //-- Actually stop the sequence.
            okayToOpenLinks = false;
        }
    };
    
    function openLinksInSequence () {
        if (mywin2) {
            mywin2.close ();
            mywin2 = null;
        }
    
        if (okayToOpenLinks) {
            if (linksToOpen.length) {
                var k           = linksToOpen.length - 1;
                var statDisp    = document.querySelector ('#myStatus span');
                statDisp.textContent = k;
    
                var link        = linksToOpen.shift ();
                mywin2          = window.open (link, "my_win2");
                mywin2.addEventListener ('load', openLinksInSequence, false);
            }
        }
        else {
            //-- Sequence is now stopped, reset the state variable.
            okayToOpenLinks = true;
        }
    }
    
    GM_addStyle ( multilineStr ( function () {/*!
        #mySelectBox {
            background: white;
            border: 2px solid red;
            padding: 4px;
            //position: absolute;
            position: fixed;
            top: 8px; left: 8px;
            max-width: 400px;
        }
        #myStatus {
            text-align: center;
            display: none;
        }
        #myCloseButton {
            background: #aaa;
            border: 1px solid #777;
            padding: 1px;
            margin-left: 8px;
            float: right;
            cursor: pointer;
        }
        #mySearchButton {
            background: #aaa;
            border: 1px solid #777;
            padding: 1px;
            margin-left: 8px;
            float: right;
            cursor: pointer;
        }
    */} ) );
    
    function multilineStr (dummyFunc) {
        var str = dummyFunc.toString ();
        str     = str.replace (/^[^\/]+\/\*!?/, '') // Strip function() { /*!
                .replace (/\s*\*\/\s*\}\s*$/, '')   // Strip */ }
                .replace (/\/\/.+$/gm, '') // Double-slash comments wreck CSS. Strip them.
                ;
        return str;
    }