简体   繁体   English

重新排列 UL 列表而不在所有 LI 元素上使用 appendChild

[英]Rearranging UL list without using appendChild on all LI elements

I could use some help on the correct way to approach this.我可以在正确的方法上使用一些帮助来解决这个问题。 Effectively, I'm trying to regularly update various UL elements without using appendChild on the entire list as that will cause the browser to lose focus (if for example the user has something highlighted to copy and paste).实际上,我正在尝试定期更新各种 UL 元素,而不在整个列表上使用 appendChild,因为这会导致浏览器失去焦点(例如,如果用户要复制和粘贴突出显示的内容)。 I feel like I've overcomplicated my approach, but it's been stumping me and I'm not sure i can figure out something better.我觉得我的方法过于复杂了,但这一直困扰着我,我不确定我是否能找到更好的方法。 I have an ordered array from the data source, and I have my UL that may need new items or remove old items to match the ordered array.我有一个来自数据源的有序数组,并且我的 UL 可能需要新项目或删除旧项目以匹配有序数组。 By removing all LI elements then appending the valid list in order, I accomplish this.通过删除所有 LI 元素,然后按顺序附加有效列表,我完成了这一点。 Please note that the following is very minimal to represent the nature of the question as my original code has a lot more bloat.请注意,以下内容很少能代表问题的性质,因为我的原始代码更加臃肿。

<BODY>
    <UL id="list">
        <LI class="listItem" data-name="john">red</LI>
        <LI class="listItem" data-name="mark">green</LI>
        <LI class="listItem" data-name="jane">yellow</LI>
        <LI class="listItem" data-name="suzie">orange</LI>
    </UL>
</BODY>

The javascript I first used was similar to:我第一次使用的 javascript 类似于:

let updatedArray = [
    {name: "john", colour: "red"},
    {name: "phil", colour: "blue"},
    {name: "jane", colour: "orange"},
    {name: "suzie", colour: "orange"},
    {name: "casey", colour: "purple"},
];

let list = document.getElementById("list");
let currentItems = Array.from(list.querySelectorAll(".listItem"));
let orderedItems = [];

updatedArray.forEach( (updatedItem) => {
    let currentItem = currentItems.find( item => item.dataset.name == updatedItem.name);

    if(currentItem != null) {
        if(currentItem.textContent != updatedItem.colour) currentItem.textContent = updatedItem.colour;
    } else {
        currentItem = document.createElement("LI");
        currentItem.dataset.name = updatedItem.name;
        currentItem.textContent = updatedItem.colour;
    }

    orderedItems.push(currentItem);
});

for(let i=list.children.length; i > 0; i--) {
    list.removeChild(list.lastChild);
}

for(let i=0, n=orderedItems.length; i < n; i++) {
    list.appendChild(orderedItems[i]);
}

I think there are different ways to improve the above, but the crux of my problem is using appendChild to relist everything in the correct order, as that also removes focus.我认为有不同的方法可以改进上述内容,但我的问题的关键是使用 appendChild 以正确的顺序重新列出所有内容,因为这也会消除焦点。 What I've been trying to do instead is to compare the two arrays, but it takes a number of steps and seems like a poor approach (and not yet thoroughly tested):我一直在尝试做的是比较两个数组,但这需要一些步骤,而且似乎是一种糟糕的方法(尚未彻底测试):

let updatedArray = [
    {name: "john", colour: "red"},
    {name: "phil", colour: "blue"},
    {name: "jane", colour: "orange"},
    {name: "suzie", colour: "orange"},
    {name: "casey", colour: "purple"},
];

let list = document.getElementById("list");
let currentItems = Array.from(list.querySelectorAll(".listItem"));
let orderedItems = [];

updatedArray.forEach( (updatedItem) => {
    let index = currentItems.findIndex( item => item.dataset.name == updatedItem.name);

    if(index > -1) {
        if(currentItems[index].textContent != updatedItem.colour) currentItems[index].textContent = updatedItem.colour;

        // Remove found items to identify unmatched elements.
        currentItems.splice(index);
    } else {
        currentItem = document.createElement("LI");
        currentItem.dataset.name = updatedItem.name;
        currentItem.textContent = updatedItem.colour;
    }

    orderedItems.push(currentItem);
});

// currentItems array now represents LI elements to be delisted. These are removed.
currentItems.forEach( (delistedItem) => {
    delistedItem.parentNode.removeChild(delistedItem);
})

for(let i=0, n=orderedItems.length; i < n; i++) {
    if(orderedItems[i] != list.children[i]) {
        // Check if at the end of the current list already, or need to insert into list.
        if(i >= list.children.length-1) {
            list.appendChild(orderedItems[i]);
        } else {
            list.insertBefore(orderedItems[i], list.children[i+1]);
        }
    }
}

I didn't get much (or any) response to the question, so for now I've come up with a generic use function for my above needs.我没有得到太多(或任何)对这个问题的回应,所以现在我想出了一个通用的使用函数来满足我的上述需求。 Not sure if it's worth sharing, but I will nonetheless if it can help anyone else.不确定是否值得分享,但如果它可以帮助其他人,我会。

The function essentially works in three steps:该功能基本上分三个步骤工作:

  1. Form a list of updated list elements and elements to be delisted (items not found in the source).形成更新的列表元素和要删除的元素的列表(源中未找到的项目)。
  2. Iterate through delisted array and remove them from the list container.遍历 delisted 数组并将它们从列表容器中删除。
  3. Iterate through the updated array, and append new items or re-append items in the wrong position in the container at the correct position based on this array.遍历更新后的数组,并根据该数组在容器中的正确位置追加新项目或重新追加项目。

Since the source data is arbitrary, it's up to the user to identify how the source uniquely correlates to the list element by supplying functions for checking the identity and creating a new LI element with that identity.由于源数据是任意的,因此用户可以通过提供用于检查标识和创建具有该标识的新 LI 元素的函数来确定源如何与列表元素唯一相关。 For example, the function for funcs.funcNewItem could set the id attribute or a data-id attribute, and funcs.funcCheckIdentity can check they match.例如, funcs.funcNewItem的函数可以设置 id 属性或 data-id 属性, funcs.funcCheckIdentity可以检查它们是否匹配。

Advice, corrections, or criticisms welcome.欢迎建议、更正或批评。 This functionality solves my current dilemma, but I can appreciate any way to improve.这个功能解决了我目前的困境,但我可以欣赏任何改进的方法。

Full code using the example in the question:使用问题中的示例的完整代码:

 /** * @template T * @template {HTMLLIElement} LISTELEM * @template {HTMLUListElement|HTMLMenuElement} LISTCONTAINER * @param {Array<T>} sourceArray * @param {LISTCONTAINER} listContainer * @param {string | null} containerQuerySelector * @param {Object} funcs * @param {function(T, LISTELEM): Boolean} funcs.funcCheckIdentity * @param {function(T): LISTELEM} funcs.funcNewItem * @param {function(T, LISTELEM): Void | null} funcs.funcModifyItem * @param {function(LISTELEM): Void | null} funcs.funcDeleteItem * @return {Array<LISTELEM>} */ function SyncHTMLList(sourceArray, listContainer, containerQuerySelector, funcs) { if(!Array.isArray(sourceArray)) { throw new TypeError("SyncHTMLList : Bad argument for sourceArray"); } else if (listContainer == null || !(listContainer instanceof Element)) { throw new TypeError("SyncHTMLList : Bad argument for listContainer"); } else if ( funcs == null || typeof funcs.funcCheckIdentity !== "function" || typeof funcs.funcNewItem !== "function" ) { throw new TypeError("SyncHTMLList : Bad argument for funcs"); } // Apply correct method for retrieving current live list of list elements. let GetListElementsArray = (containerQuerySelector && typeof containerQuerySelector === "string") ? () => Array.from(listContainer.querySelectorAll(containerQuerySelector)) : () => Array.from(listContainer.children) ; /**@type {Array<LISTELEM>} Copy of existing list as potential delist array, and splice as items are found in source. */ let delistedItems = GetListElementsArray(); /**@type {Array<LISTELEM>} New list, updated from source array. */ let orderedItems = []; for(let i=0, n=sourceArray.length; i<n; i++) { // Look for associated LI element using comparison function "funcCheckIdentity" let itemIndex = delistedItems.findIndex( listElement => funcs.funcCheckIdentity(sourceArray[i], listElement) ); /**@type {LISTITEM} Current LI Element (whether found or created) */ let listElement = null; // If associated LI element found in container, associate to variable and remove from delist array. // Otherwise create element. if (itemIndex < 0) { listElement = funcs.funcNewItem(sourceArray[i]); } else { listElement = delistedItems[itemIndex]; delistedItems.splice(itemIndex,1); } // Make any checks/modifcations specified by user on item. if (funcs.funcModifyItem) funcs.funcModifyItem(sourceArray[i], listElement); // Add list element to final ordered list. orderedItems.push(listElement); } // Remove unfound LI elements from container, with user specified function if available. if (funcs.funcDeleteItem && typeof funcs.funcDeleteItem === "function") { delistedItems.forEach( listElement => funcs.funcDeleteItem(listElement) ); } else { delistedItems.forEach( listElement => listElement.parentNode.removeChild(listElement) ); } // Reorganize list in DOM according to sourceArray/orderedItems. for (let i=0, n=orderedItems.length; i < n; i++) { let liveList = GetListElementsArray(); if(orderedItems[i] != liveList[i]) { if(i >= liveList.length-1) { listContainer.appendChild(orderedItems[i]); } else { listContainer.insertBefore(orderedItems[i], liveList[i+1]); } } } // Return an array of the list elements. return orderedItems; } var updateButton = document.getElementById("btnUpdate"); updateButton.addEventListener( 'click' , (event) => { let updatedArray = [ {name: "john", colour: "red"}, {name: "phil", colour: "blue"}, {name: "jane", colour: "orange"}, {name: "suzie", colour: "orange"}, {name: "casey", colour: "purple"}, ]; let list = document.getElementById("list"); let funcs = {}; funcs.funcNewItem = (data) => { let el = document.createElement("LI"); el.dataset.name = data.name; el.className = "listitem"; el.textContent = data.colour; return el; } funcs.funcCheckIdentity = (data, element) => { return data.name == element.dataset.name; } funcs.funcModifyItem = (data, element) => { if(data.colour != element.textContent) { element.textContent = data.colour; } } SyncHTMLList(updatedArray, list, null, funcs); } , {once: true} );
 <BODY> <UL id="list"> <LI class="listItem" data-name="john">red</LI> <LI class="listItem" data-name="mark">green</LI> <LI class="listItem" data-name="jane">yellow</LI> <LI class="listItem" data-name="suzie">orange</LI> </UL> <BUTTON id="btnUpdate">Update</BUTTON </BODY>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM