June 18th, 2021

HTML-дерево из объекта, рекурсия, innerHTML

Начало:
1. HTML-дерево из объекта, рекурсивный способ
2. HTML-дерево из объекта, итеративный способ

Продолжаю решать задачу «Создайте дерево из объекта» к подразделу 1.7 «Изменение документа» второй части учебника по JavaScript.

Авторы этой задачи изначально предлагают решить эту задачу одним из двух способов: 1) создать строку с нужным кодом на HTML, а затем вставить эту строку в заданный контейнер container через его свойство innerHTML; 2) создавать узлы через методы DOM.

В предыдущих двух постах (см. ссылки выше) я решал эту задачу вторым способом, создавая узлы через методы DOM. Авторы задачи своё собственное решение этой задачи сделали рекурсивным методом. Я же из интереса кроме рекурсивного решения еще написал и итеративное.

Теперь я решил попробовать решить эту задачу присвоением строки в container.innerHTML.

В качестве базы я решил взять моё рекурсивное решение с созданием узлов через методы DOM:
function createTree(container, data) {
    let ul = document.createElement("ul");   // создаём <ul>

    for (const [text, ref] of Object.entries(data)) {
        let li = document.createElement("li");

        li.append(text);                     // выводим текст узла (прямой обход)
        createTree(li, ref);
        ul.append(li);                       // помещаем в <ul> потомков (обратный обход)
    }

    if ( Object.entries(data).length > 0 ) {
        container.append(ul);                // помещаем сам <ul> в заданный контейнер
    }
}

Просто переписать это решение не получается, потому что при рекурсии createTree(li, ref); первым параметром передается узел, созданный через метод DOM. А это не удовлетворяет условиям нового варианта задачи.

Поэтому я решил, что создание нужной строки должно происходить в отдельной функции. То есть у нас будет две функции, первая — createTree(container, data), требуемая по общим условиям задачи, а вторая — вспомогательная, для создания нужной строки (однако, хоть она и вспомогательная, но основная работа будет выполняться в ней). Пишем код:
function createStr(data) {                 // вспомогательная функция
    let str = "";

    // ...

    return str;
}

function createTree(container, data) {     // то, что требуется по условиям задачи
    container.innerHTML = createStr(data);
}

Во вспомогательной функции, ориентируясь на рекурсивное решение с созданием узлов через методы DOM, приведенное в начале поста, я легко написал следующее:
function createStr(data) {
    let str = "";

    for (const [text, ref] of Object.entries(data)) { // создание пунктов li списка
        str += "<li>" + text + createStr(ref) + "</li>";
    }
    str = "<ul>" + str + "</ul>";                     // помещаем пункты li в список ul

    return str;
}

function createTree(container, data) {
    container.innerHTML = createStr(data);
}

В общем, этот код уже работает так, как требуется. Однако, в случае листьев дерева у пунктов списка образуются лишние пустые блоки <ul></ul>. При просмотре HTML-страницы через браузер они не видны, но в задаче сказано, что такого быть не должно. Меняем код:
function createStr(data) {
    let str = "";

    for (const [text, ref] of Object.entries(data)) {
        str += "<li>" + text + createStr(ref) + "</li>";
    }
    if ( str ) str = "<ul>" + str + "</ul>";

    return str;
}

function createTree(container, data) {
    container.innerHTML = createStr(data);
}

Это финальный код решения. Этот вариант мне даже понравился больше, чем вариант с созданием узлов через методы DOM.