0

Appreciation, can be skipped

I have read this awesome post here: How do I return the response from an asynchronous call?. Thanks to the amazing community here I've come really far with this project but there is just a final bug.

How the program should be functioning

I have a program whereby it uses a chrome-extension (browser-action). When the user clicks an icon he/she can add the link of the current website, delete all of the links at once, or delete just one link with a 'X' button next to the link's title.

The code

This is from popup.js, the problem lies within the 'flow' of these functions.

document.addEventListener('DOMContentLoaded', function() {
    restore();
    document.getElementById('add').addEventListener('click', fetchUrl);
    document.getElementById('clear').addEventListener('click', clearAll);
  });

function restore() {
    // get the tab link and title
    chrome.storage.local.get({urlList:[], titleList:[]}, function(data) {
        urlList = data.urlList;
        titleList = data.titleList;
     
        // add the titles and url's to the DOM
        for (var i = 0, n = urlList.length; i < n; i++) {
            addToDom(urlList[i], titleList[i]);
        }
        
        // create event listeners for all the 'X' buttons next to list items
        // after the 'addToDom' function has been executed
        var allButtons = document.getElementsByClassName('buttons');
        for (var j = 0, k = allButtons.length; j < k; j++) {
            listenJ(j);
        } 
        function listenJ(j) {
            allButtons[j].addEventListener('click', () => removeMe(j));
        }   
    }); 
}

function removeMe(j) {
    // remove it from the DOM
    var items = document.getElementsByClassName('items');
    var list = document.getElementById('list');
    // the specific URL to delete
    var item = items[j];
    list.removeChild(item);
    
    // return the DOM to original state
    if (items.length === 0) {
    document.getElementById('list').innerHTML = '';
    document.getElementById('div').innerHTML = '<h3>No content yet! Click "add link" to add the link of the current website!</h3>';
    }
    
    // remove it from chrome-storage
    chrome.storage.local.get({urlList:[], titleList:[]}, function(data) {
        urlList = data.urlList;
        titleList = data.titleList;
        urlList.splice(j, 1);
        titleList.splice(j, 1);

        // update chrome storage
        saveList();
    }); 
}

function addToDom(url, title){
    // change the (greeting) text message
    document.getElementById("div").innerHTML = "<h2 id='title'>Saved Pages</h2>";
  
    // Build the new DOM elements programmatically
    var newLine = document.createElement('li');
    var newLink = document.createElement('a');
    var button = document.createElement('button');
    newLink.textContent = title;
    newLine.appendChild(button);
    button.setAttribute('class', 'buttons');
    button.textContent = 'delete';
    newLine.setAttribute('class', 'items');
    newLink.setAttribute('href', url);
    newLink.setAttribute('target', '_blank');   // opens link in new tab
    newLink.setAttribute('tabindex', -1);       // remove focus from links in popup-window
    newLink.setAttribute('id', 'item');
    newLine.appendChild(newLink);
    document.getElementById('list').appendChild(newLine);
}

For extra information:

popup.html: gist.github.com/kobrajunior/1c26691734c19391c62dc336ed2e1791

manifest.json: gist.github.com/kobrajunior/78acda830c2d1c384333542422f1494d

The bug

Whenever I press 'add link' inside the popup-window, the link and the 'X' button gets shown. However, when I press that button it doesn't work immediately. I first have to close the popup-window, open it again and then pressing the 'X' button works. I know something is wrong with the order of asynchronous calls that I have but I can't put my finger on it.

Edit

I've added the answer of Andrew but there seems to be the following problem: every time I try to remove the last link it works fine without problems. Only when I have a link before the last one (or 2 places before or even the very first link) and try to delete that link through the 'X' button, it deletes all of the links under the deleted link including the link to be deleted. Please see this pictures:

Before: I haven't clicked that button, but I'm about to: https://i.stack.imgur.com/IFZc6.png

After: after I have clicked that button: https://i.stack.imgur.com/cafqr.png

Also, the debugger gives me this error whenever I try to delete one link: https://i.stack.imgur.com/nStFr.png

The location of that error is inside my removeMe function and this specific code:

 list.removeChild(item);
Community
  • 1
  • 1
Kobrajunior
  • 65
  • 1
  • 6
  • do not link to code. show us a *mininal* code sample that reproduces the issue. also show your attempts using the debugger. – Zig Mandel Dec 28 '16 at 12:01
  • Right sir, sorry. I have pasted all of the code where the problem lies and added my attempts at debugging it. Thank you for your advice. – Kobrajunior Dec 28 '16 at 12:22
  • google how to debug the popup and try to debug it first. – Zig Mandel Dec 28 '16 at 13:32
  • @Zig Mandel I'm sorry if I don't understand u but I don't know how debugging this specific problem with the 'inspect Pop-up' works. It is different than what is described here: https://developer.chrome.com/extensions/tut_debugging. The problem is that the button doesn't do anything at all the first time but the second time the popup opens it does, I don't know how to use a debugger in that specific situation. I've edited the post with a picture just to be certain that this is the debugger you mean? – Kobrajunior Dec 28 '16 at 14:03
  • you debug where the code is (.js) which I can see from your picture is not in the pupup.html but likely in popup.js. – Zig Mandel Dec 28 '16 at 15:02
  • It does not like you are binding to the element that is being created. – Andrew Ice Dec 28 '16 at 15:09
  • @Zig Mandel, I'm sorry it took me so long to understand what and how the debugger works (I'm still beginner). I now have used the debugger correctly because I got the error of the specific code (as seen in the edit) :). Thanks again. – Kobrajunior Dec 28 '16 at 18:22
  • your update does not show (in text) the specific error you get. Also show that code context (usually enough code so it can run by itself) – Zig Mandel Dec 28 '16 at 18:24
  • @Kobrajunior I feel like this link may be beneficial to you. I feel like you are abstractly trying to remove elements from your page. https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work – Andrew Ice Dec 28 '16 at 18:31

1 Answers1

1

I believe the issue here is that you are creating an element, as seen in:

function addToDom(url, title){
    // change the (greeting) text message
    document.getElementById("div").innerHTML = "<h2 id='title'>Saved Pages</h2>";

    // Build the new DOM elements programmatically
    var newLine = document.createElement('li');
    var newLink = document.createElement('a');
    var button = document.createElement('button');
    newLink.textContent = title;
    newLine.appendChild(button);
    button.setAttribute('class', 'buttons');
    button.textContent = 'delete';
    newLine.setAttribute('class', 'items');
    newLink.setAttribute('href', url);
    newLink.setAttribute('target', '_blank');   // opens link in new tab
    newLink.setAttribute('tabindex', -1);       // remove focus from links in popup-window
    newLink.setAttribute('id', 'item');
    newLine.appendChild(newLink);
    document.getElementById('list').appendChild(newLine);
}

The very last line creates and appends a child to the page. However, since this child is created after the event binding has been attached, there is no event binding attached to this specific element.

function restore() {
    // get the tab link and title
    chrome.storage.local.get({urlList:[], titleList:[]}, function(data) {
        urlList = data.urlList;
        titleList = data.titleList;

        // add the titles and url's to the DOM
        for (var i = 0, n = urlList.length; i < n; i++) {
            addToDom(urlList[i], titleList[i]);
        }

        // create event listeners for all the 'X' buttons next to list items
        // after the 'addToDom' function has been executed
        var allButtons = document.getElementsByClassName('buttons');
        for (var j = 0, k = allButtons.length; j < k; j++) {
            listenJ(j);
        } 
        function listenJ(j) {
            allButtons[j].addEventListener('click', () => removeMe(j));
        }   
    }); 
}

Imagine if 8 people showed up for a pie eating contest. Those people get the pies at the beginning and can sit down, and eat the pies.

If one guy shows up late. He doesn't have any pie, because they've already been handed out. So he's not able to compete in the contest.

The code above does that. It assigns the events(pie) before this guy that shows up later gets to have one.

So just move the function out.

function createButtonEvents() {
    // create event listeners for all the 'X' buttons next to list items
    // after the 'addToDom' function has been executed
    var allButtons = document.getElementsByClassName('buttons');
    for (var j = 0, k = allButtons.length; j < k; j++) {
        listenJ(j);
    } 
    function listenJ(j) {
        allButtons[j].addEventListener('click', () => removeMe(j));
    }   
}

function restore() {
    // get the tab link and title
    chrome.storage.local.get({urlList:[], titleList:[]}, function(data) {
        urlList = data.urlList;
        titleList = data.titleList;

        // add the titles and url's to the DOM
        for (var i = 0, n = urlList.length; i < n; i++) {
            addToDom(urlList[i], titleList[i]);
        }
        createButtonEvents();
    }); 
}

And then call it in our creation function, after the button has been created.

function addToDom(url, title){
    // change the (greeting) text message
    document.getElementById("div").innerHTML = "<h2 id='title'>Saved Pages</h2>";

    // Build the new DOM elements programmatically
    var newLine = document.createElement('li');
    var newLink = document.createElement('a');
    var button = document.createElement('button');
    newLink.textContent = title;
    newLine.appendChild(button);
    button.setAttribute('class', 'buttons');
    button.textContent = 'delete';
    newLine.setAttribute('class', 'items');
    newLink.setAttribute('href', url);
    newLink.setAttribute('target', '_blank');   // opens link in new tab
    newLink.setAttribute('tabindex', -1);       // remove focus from links in popup-window
    newLink.setAttribute('id', 'item');
    newLine.appendChild(newLink);
    document.getElementById('list').appendChild(newLine);
    createButtonEvents();
}
Andrew Ice
  • 831
  • 4
  • 16
  • I think I got the problem but not the solution: only when I try the execute this code the popup reacts weird: list.removeChild(items[j]); whereby 'j' could be any number and the debugger yells at me. For all numbers greater than '0' it gives me that uncaught typeError i mentioned above but it works. If j === 0 then the debugger-console throws the _same_ error and it deletes all of the links.... – Kobrajunior Dec 28 '16 at 18:04
  • @Kobrajunior You should check out what the term help vampire means, and compare that with what you're currently doing. The original question was about why your events weren't being bound correctly, not why your code isn't working. – AlbertEngelB Dec 28 '16 at 18:24
  • @Kobrajunior To be honest after re-reading, I'm not 100% sure you're in HV territory or not. Either way, what I would suggest would be to set an event on the container where the button elements are, and using the `event.target` to remove the entry. Example: http://jsbin.com/vivovunusu/edit?html,js,output Docs entry: https://developer.mozilla.org/en-US/docs/Web/API/Event/target – AlbertEngelB Dec 28 '16 at 18:49