skip to Main Content

Could you please help me to build a html document by nested classes?

My main aim is to build a string with html markup like this

<html>
  <div>
    <p>123</p>
    <div>
      <div>
        123
        <a>123</a>
      </div>
    </div>
  </div>
</html>

I have a class DOMElement with the following fileds:

class DOMElement {
    TagName :string;
    TextContent: string | null;
    Attributes: string[];
    AttributesValue: string[];
    Children: DOMElement[]; // stored nested tags;
}

So I already have a DOM structure of my document with this class, and I want to return this into markup.

For this I used an incorrect method:

public convertToHtml (domTree: DOMElement, str: string = ''): string {
    if (domTree.Children.length != 0) {
        domTree.Children.forEach((x) => {
            let attributes = '';
            x.Attributes.forEach((attr, index) => {
                attributes += attr + '=' + `${x.AttributesValue[index]}"`;
            });
            if (x.Children.length != 0) {
                x.TextContent = '';
            }

            str += `<${x.TagName.toLowerCase()} ${attributes}>${this.convertToHtml(
                x,
                str,
            )}${x.TextContent}</${x.TagName.toLowerCase()}>`;
        });
    }

    return str;
};

How to fix this recursion to make it return a markup structure? Or are there other options to create markup with a nested object?

2

Answers


  1. I think there were several typos/small mistakes in the code that added up. Here is a working example with no real changes other than typo fixes:

    interface DOMElement {
        TagName: string;
        TextContent: string | null;
        Attributes: string[];
        AttributesValue: string[];
        Children: DOMElement[]; // stored nested tags;
    }
    
    const convertToHtml = (domTree: DOMElement, str = ''): string => {
        let result = str;
        if (domTree.Children.length !== 0) {
            domTree.Children.forEach((child) => {
                let attributes = child.Attributes.length > 0 ? '"' : '';
                child.Attributes.forEach((attr, index) => {
                    attributes += `${attr}=${child.AttributesValue[index]}"`;
                });
                if (child.Children.length !== 0) {
                    child.TextContent = '';
                }
    
                result += `<${child.TagName.toLowerCase()} ${attributes}>${convertToHtml(
                    child,
                    str,
                )}${child.TextContent}</${child.TagName.toLowerCase()}>`;
            });
        }
    
        return result;
    };
    
    const sampleInput = {
        TagName: '',
        TextContent: '',
        Attributes: [],
        AttributesValue: [],
        Children: [
            {
                TagName: 'html',
                TextContent: '',
                Attributes: [],
                AttributesValue: [],
                Children: [
                    {
                        TagName: 'div',
                        TextContent: '',
                        Attributes: ['ok'],
                        AttributesValue: ['test'],
                        Children: [
                            {
                                TagName: 'p',
                                TextContent: '123',
                                Attributes: [],
                                AttributesValue: [],
                                Children: [],
                            },
                        ],
                    },
                ],
            },
        ],
    };
    
    console.log(convertToHtml(sampleInput));
    // <html ><div "ok=test"><p >123</p></div></html>
    

    Playground link

    Extra Notes/Suggestions/Tips

    class vs. interface

    DOMElement was a class, but it could also have been an interface or a type. In this case, it looks like you’re not using it as a class really, so a TS type is probably better.

    Default argument value type inference

    You used str: string = '' as an argument, but TS can infer that it’s a string already when you specify '' as the default.

    Login or Signup to reply.
  2. This is how I handle this. I have a function I call ‘createHTMLElement’.

    const createHTMLElement = (data) => {
    
        let element = document.createElement(data.type)
    
        if(data.UUID) element.setAttribute('data-UUID', data.UUID)
        if(data.id) element.id = data.id
        if(data.class) element.className = data.class
        if(data.click) element.addEventListener('click', data.click)
        if(data.blur) element.addEventListener('blur', data.blur)
        if(data.focus) element.addEventListener('focus', data.focus)
        if(data.innerHTML) element.innerHTML = data.innerHTML
        if(data.innerText) element.innerText = data.innerText
        if(data.attribute) element.setAttribute(data.attribute.key, data.attribute.value)
        if(data.attributes){
            for(attribute of data.attributes){
                element.setAttribute(attribute.key, attribute.value)
            }
        }
        if(data.child) element.appendChild(data.child)
        if(data.children){
            for(child of data.children){
                element.appendChild(child)
            }
        }
        if(data.title) element.title = data.title
        if(data.value) element.value = data.value
        if(data.placeholder) element.placeholder = data.placeholder
    
        return element
    }
    

    I create JSON objects that look like this:

    let obj = {
        "UUID": this._UUID, 
        "type": "div",
        "class": "divClass",
        "id": "divID",
        "click": functionYouWantToRun,  
        "child": childHTMLElement,
        "attribute":{
        "key": attributeName,
        "value": attributeValue
        }
    }
    

    And I call it like this:

    let ele = createHTMLElement(obj)
    

    The ele that is returned will be a dom object. Instead of passing a single child, it can take an array (passed as ‘children’) to add multiple at one time. Maybe this, or some tweaked version, can help you get what you’re looking for.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search