0

I'm wondering if there is an "easy" way to create a big DOM object by specifying the attributes in a json?

I know it's possible to do this with appendChild and/or innerHTML, but for this object in question, it looks quite messy.

The goal is to create this HTML object from the bottom up in javascript:

<div class="p-2">
    <div class="d-flex p-2 bg-dark2-35">
        <div class="Popover h-3">
            <div class="pfp" style="background-image: url('/data/images/PP/1.png')"></div>
        </div>
        <div class="d-grid ms-2 w-100">
            <p><b class="lead text-blue">Username</b> — <i>2020-01-16 19:29:34</i></p>
            <p class="text-white">some comment text</p>
        </div>
    </div>
</div>

I was wondering if it's possible to do something like this (I know it doesn't work):

let comment = document.getElementById("comment-section");
let elm = {
    className: "p-2",
    appendChild(node) {
        classList.add("d-flex", "p-2", "bg-dark2-35"),
        appendChild(node2){
            classList.add("Popover", "h-3"),
            // ... and so on
        }
    }
}
comment.appendChild(elm);

Is there an easy way to do this in pure JS? Would I have to make a function to achieve this? or maybe go as far as to import a library?

The question is a bit similar to Create DOM element from Object in javascript, but I'm completely lost when it comes to this many childrens, with childrens and so on

I'm a bit of a newbie when it comes to JavaScript. The question might come off as strange

3
  • comment.insertAdjacentHTML('beforeEnd', templateLiteralOfTheEntireHTML) Commented Feb 26, 2022 at 2:42
  • @zer00ne Thank you for the hint! Can you elaborate how you set up templateLiteralOfTheEntireHTML ? Is there a known way to set it up properly? Commented Feb 26, 2022 at 2:52
  • That entire HTML wrapped in `. Commented Feb 26, 2022 at 3:04

2 Answers 2

1

You can use template literals Mdn docs - Template Literals

const selector = document.getElementById('comment-section');

// By using createElement you can deal with loops, event listener, etc..

const comment = document.createElement('div');
comment.classList.add("p-2");

const username = "TypeWar";

// Template literals

comment.innerHTML = `
  <div class="d-flex p-2 bg-dark2-35">
      <div class="Popover h-3">
          <div class="pfp" style="background-image: url('/data/images/PP/1.png')"></div>
      </div>
      <div class="d-grid ms-2 w-100">
          <p><b class="lead text-blue">${username}</b> — <i>2020-01-16 19:29:34</i></p>
          <p class="text-white">some comment text</p>
      </div>
  </div>
`;

selector.appendChild(comment);
<div id="comment-section">
</div>

Sign up to request clarification or add additional context in comments.

4 Comments

Alright I see. So the way to go is to write the whole HTML code as a string, there might not be a "good" way to use an object to do this job ?
You can wrap the code in a function with params, then pass them in the template.
Doing this with just ${username} creates XSS vulnerabilities. You should properly encode username for HTML entities. One easy way would be a function like this: function encode (s) { const el = document.createElement('i'); el.innerText = s; return el.innerHTML; } and then use ${encode(username)}
Agreed for XSS vulnerabilities, it is important to mention it. This is why never trust user data input and sanitization is done before storing in database. In this exemple i think it is safe because the data is already defined as a string and is not provided by user input.
0

Coming back to this question, check out this function:

/**
 * A "fancier" function to make a HTML element
 * @param nodeName
 * @param elementOptions
 */
function createElement(nodeName, elementOptions) {
    if (!nodeName && !elementOptions) return;
    if (!nodeName) nodeName = elementOptions.nodeName;

    // Defining the new element
    let elm = document.createElement(nodeName);

    if(elementOptions?.href){
        elm.setAttribute("href", elementOptions.href); // Use setAttribute to set the href attribute
    }

    // Assign classes if defined
    if (elementOptions?.classList)
        for (let i = 0; i < elementOptions.classList.length; i++)
            if (elementOptions.classList[i] && elementOptions.classList[i]?.trim() !== "")
                elm.classList.add(elementOptions.classList[i]);

    // Assign id if defined
    if (elementOptions?.id) elm.id = elementOptions["id"];

    // Assign title if defined
    if (elementOptions?.title) elm.title = elementOptions["title"];

    // Assign tabIndex if defined
    if (elementOptions?.tabIndex) elm.tabIndex = elementOptions["tabIndex"];

    // Assign innerHTML if defined
    if (elementOptions?.innerHTML) elm.innerHTML = elementOptions["innerHTML"];

    // Assign style if defined
    if (elementOptions?.style) {
        Object.entries(elementOptions.style).forEach(([property, value]) => {
            elm.style[property] = value;
        });
    }

    if (elementOptions?.events) {
        for (const [eventName, eventListener] of Object.entries(elementOptions.events)) {
            if (eventName in elm) {
                elm.addEventListener(eventName, eventListener);
            }else{
                console.warn(`Invalid event name: ${eventName}`);
            }
        }
    }

    // Assign attributes if defined
    if (elementOptions?.attributes) {
        Object.entries(elementOptions.attributes).forEach(([property, value]) => {
            elm.setAttribute(property, value);
        });
    }

    // Lastly do recursion to create children
    if (elementOptions?.children){
        for (let i = 0; i < elementOptions.children.length; i++) {
            let child = createElement(null, elementOptions.children[i])
            if (child) elm.appendChild(child)
        }
    }

    return elm;
}

And it can be created like so:

const myElm = createElement("DIV", {
    classList: ["p-2"],
    children: [{
        nodeName: "DIV",
        classList: ["d-flex", "p-2", "bg-dark2-35"],
        children: [
            {
                nodeName: "DIV",
                classList: ["Popover", "h-3"],
                children: [{
                    nodeName: "DIV",
                    classList: ["pfp"],
                    style: {
                        backgroundImage: "url('/data/images/PP/1.png')"
                    }
                }]
            },
            {
                nodeName: "DIV",
                classList: ["d-grid", "ms-2", "w-100"],
                children: [
                    {
                        nodeName: "P",
                        innerHTML: `<b class="lead text-blue">${username}</b> — <i>${timestamp}</i>`
                    },
                    {
                        nodeName: "P",
                        classList: ["text-white"],
                        innerHTML: "some comment text"
                    }
                ]
            }
        ]
    }]
})

Overall this is a bit messy compared to something that React can achieve or just using template literals.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.