从 chrome-extension-popup 的无序列表中删除一个项目也会删除该项目之后的项目

Removing one item from an unordered list from chrome-extension-popup also removes the item(s) that comes after that

简介

假设我有一个包含 3 个项目的列表(这些项目是用户在 chrome 扩展弹出窗口中单击按钮时添加的 URL,请参见图片)。当我单击项目编号 3 的 'X' 按钮时,它只删除了该项目。之后,当我单击 'X' 项目编号 2 时,第二个项目被删除。最后,当单击第一个项目的 'X' 时,它也会被删除。一切都按预期工作。当我尝试相反的操作时,出现了问题。

问题

我们有相同的 3 项列表。现在,我决定先删除第 1 项,而不是先删除第 3 项。当我这样做时,所有 3 项都立即消失了。然后我再次重新加载这 3 个项目并尝试从删除项目编号 2 开始,这次只删除项目 2 和项目 3,剩下项目 1。这是一个奇怪的错误,因为似乎每当我单击 'X' 按钮时,连续的项目与我要删除的项目一起被删除。

在 Google 点击 'X' 之前:

点击 'X' 后只剩下 Facebook:


然后我开始使用调试器。

调试器

所以我觉得这很奇怪,我试图用调试器解决这个问题。当仅按 Google 按下 'X' 按钮时,我收到以下错误(与上图相同):

然后我去了 popup.js 并查看了包含这些行的特定代码行:

// the specific URL to delete
list.removeChild(items[j]);

代码部分我post代码部分有更详细的介绍。我搜索了 Google 上的错误,发现了这个:removeChild。这里错误描述如下:

"If the child was in fact a child of element and so existing on the DOM, but was removed".

现在这让我想知道,我再次查看了我的代码,但据我所知,我没有删除 Google 之后的 URL,只有 Google本身。我试图在纸上解决它,但不幸的是徒劳无功。我唯一能想到的就是我应该从 "addToDom" 函数中删除 "createButtonEvents"。但如果我这样做,我将无法再删除项目编号 1(但我可以删除项目 2 而无需删除项目 3)。我认为这个问题比我最初想象的要复杂得多,所以我无法自己解决这个问题,这就是我在这里问这个问题的原因。现在是 'code' 部分。

代码

任何想要查看完整代码(所有行和文件)的人,请前往此页面:GitHub

对于那些只关心发生这种情况的特定代码的人:

我的 popup.js 文件的一部分:

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
        var n = urlList.length;
        for (var i = 0; i < n; i++) {
            addToDom(urlList[i], titleList[i]);
        }
        // 'X' button handlers only when all URL's are in DOM
        createButtonEvents();
    }); 
}
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 newItem = document.createElement('li');
    var newLink = document.createElement('a');
    var thisButton = document.createElement('button');
    
    newLink.textContent = title;                            
    thisButton.textContent = 'X';                          

    thisButton.setAttribute('class', 'buttons');            
    thisButton.setAttribute('tabindex', -1);                
    newItem.setAttribute('class', 'items');                 
                                
    newLink.setAttribute('href', url);                      
    newLink.setAttribute('target', '_blank');               
    newLink.setAttribute('tabindex', -1);                   
    newLink.setAttribute('id', 'item');                     
    
    newItem.appendChild(thisButton);                        
    newItem.appendChild(newLink);                           
    document.getElementById('list').appendChild(newItem);   
    createButtonEvents();
}

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 removeMe(j) {
    // remove it from the DOM
    var items = document.getElementsByClassName('items');
    var list = document.getElementById('list');
    // the specific URL to delete
    list.removeChild(items[j]);
    
    // 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();
    }); 
}

编辑

以防万一有人没有读过它,我将在这里再次重复调试器部分中的句子:

我唯一能想到的就是我应该从"addToDom"函数中删除"createButtonEvents"。但如果我这样做,我将无法再删除项目编号 1(但我可以删除项目 2 而无需删除项目 3)。我认为问题比我最初想象的要复杂得多

我知道 'buttons class' 上的多个事件处理程序可能同时工作,但我不知道如何以其他方式格式化代码。

控制台中的错误告诉您需要将节点传递给 removeChild,看起来您没有传递 DOM 元素。

document.getElementsByClassName 创建一个 live 元素列表。

createButtonEvents(); 被多次调用,并添加了多个使用 j 作为实时集合索引的事件侦听器。因为侦听器使用每个添加事件侦听器调用唯一的匿名函数,所以它们不被认为是相同的并且不会被丢弃。

因此删除最后一个元素是有效的,因为在第一次删除后没有更多的“第 j 个”元素要删除。但是当您删除第一个元素时,因为列表是活动的,之后还有另一个要删除的第一个元素,因为有多个事件侦听器...

我尝试删除 addToDom 中的 createButtonEvents() 调用,这修复了控制台错误消息,因为代码不再尝试删除不存在的节点。

引入了一个新错误:删除第一个按钮有效,因为它在实时集合中的索引为 0。但是删除会更改活动列表中剩余项目的索引。现在删除第二项会删除第三项 link 因为它的索引已更改为 1,而索引为 1 的内容现在为 0。


更新示例解决方案:

  1. 删除createButtonEvents函数。嵌套 listenJ 和捕获匿名函数 j,索引实时 HTML 集合,是错误的根本原因。

    删除 restore

    中对 createButtonEvents 的调用

    addToDom中的调用createButtonEvents替换为

        thisButton.addEventListener('click', removeMe);
    

    以便列表中的所有按钮都获得相同的点击处理程序,无论按钮是从存储中恢复还是稍后添加。

  2. removeMe中找到点击的按钮,找到它在列表中的当前动态位置并将其删除。找到的位置与项目在存储数据数组中的位置相匹配:

    function removeMe() {
        // remove list item (parent of button clicked) from the DOM
        var list = document.getElementById('list');
    
        // find position of list element with button clicked:
        for( var j = 0, child = list.firstChild; 
                child;
                    child = child.nextSibling, ++j) {
    
             if (child == this.parentNode) {
                  break;
             }
    
        }    
        list.removeChild(this.parentNode); // this is button, parent is <li>
    
        // return the DOM to original state
        if (!list.firstChild) {
        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 from Chrome storage using position in j
    
        //    urlList = data.urlList;
        //    titleList = data.titleList;
        //    urlList.splice(j, 1);
        //    titleList.splice(j, 1);
    
    
    }
    

urlListtitleList 的更新已使用 Chrome 之外的测试值进行了测试:Chrome 需要恢复存储更新。