Valery Nosareu
Valery Nosareu

Reputation: 103

How to write a generic function that creates an HTMLElement using typescript

I have a problem. I need to write a common function (class) that can both accept HTMLElements and give them using typescript

An example of attempts

class Create {
  protected element: HTMLElement;

  constructor(parent: ParentNode, tag: string, classNames: string, value?: string, attr?: Record<string, unknown>) {
    this.element = document.createElement(tag);
    this.element.classList.add(...classNames.split(' '));
    if (typeof value === 'string') {
      this.element.innerHTML = value;
    }
    if ('appendChild' in parent) {
      parent.appendChild(this.element);
    }
    if (attr) {
      for (const key in attr) {
        this.element.setAttribute(key, <string>attr[key]);
      }
    }
  }

  append(element: ElementNode) {
    this.element.append(element);
  }
}

This function will be auxiliary to create elements on the page

example of available elements

const wrapper = new Create(this.container, 'div', styles.wrapper);
    const headerWrapper = document.createElement('div') as HTMLDivElement;
    const headerNav = document.createElement('nav');
    const headerList = document.createElement('ul');
    const headerLogo = document.createElement('div');
    const headerLogoLink = document.createElement('a');
    const headerLogoTitle = document.createElement('h2');
    headerLogo.classList.add(styles.headerWrapperLogo);
    headerLogoLink.classList.add(styles.headerWrapperLogoLink);
    headerLogoLink.href = '/';
    headerLogoTitle.classList.add(styles.headerWrapperLogoTitle);
    headerLogoTitle.innerHTML = 'Manga Store';
    headerLogo.append(headerLogoLink);
    headerLogoLink.append(headerLogoTitle);
    headerWrapper.append(headerLogo);
    headerWrapper.classList.add(styles.headerWrapper);
    headerNav.classList.add(styles.headerWrapperNav);
    headerList.classList.add(styles.headerWrapperNavList);
    btnHeader.forEach((btns) => {
      const headerListItem = document.createElement('li');
      const headerListItemLink = document.createElement('a') as HTMLAnchorElement;
      headerListItem.classList.add(styles.headerWrapperNavListItem);
      headerListItemLink.classList.add(styles.headerWrapperNavListItemLink);
      headerListItemLink.href = `#${btns.id}`;
      headerListItemLink.innerText = btns.content;
      wrapper.append(headerWrapper);
      headerWrapper.append(headerNav);
      headerNav.append(headerList);
      headerList.append(headerListItem);
      headerListItem.append(headerListItemLink);

Or tell me if it is possible to rewrite the "Abstract" class (see example) to get the desired result

abstract class Template {
  protected container: HTMLElement;
  static content = {};

  protected constructor(id: string, classNames?: string) {
    this.container = document.createElement('main');
    if (typeof classNames === 'string') {
      this.container.classList.add(classNames);
    }
    this.container.id = id;
  }

  protected header(content: string) {
    const title = document.createElement('h1');
    title.innerText = content;
    return title;
  }

  render() {
    return this.container;
  }
}

When I tried this code:

const wrapper = new Create(document.body, 'div', '1')
const subWrap = new Create(wrapper, 'div', '2') // ???

I got this error:

enter image description here

Upvotes: 0

Views: 741

Answers (2)

Valery Nosareu
Valery Nosareu

Reputation: 103

export type Values = string | null | unknown;
export type ParentElem = ParentNode | Create | null;

class Create {
  element: Element;

  constructor(tag: string, classNames: string, parent?: ParentElem, value?: Values, attr?: Record<string, unknown>) {
    this.element = document.createElement(tag);
    this.element.classList.add(...classNames.split(' '));
    if (typeof value === 'string') {
      this.element.innerHTML = value;
    }
    parent ? (!(parent instanceof Create) ? parent.appendChild(this.element) : null) : undefined;
    if (attr) {
      for (const key in attr) {
        this.element.setAttribute(key, <string>attr[key]);
      }
    }
  }

  append(element: Create | Node | string) {
    return element ? (!(element instanceof Create) ? this.element.append(element) : null) : undefined;
  }

  remove(element = this.element) {
    return element ? (!(element instanceof Create) ? this.element.remove() : null) : undefined;
  }
}

ternary operators helped me, and a few checks, to protect against surprises in the form of undefind and null, I also created two additional methods for adding and removing elements, maybe it will come in handy for someone)

Upvotes: 0

Alex Wayne
Alex Wayne

Reputation: 187034

If you want Create's append method to support being passed a ParentNode or an instance of Create, then you just need to type that argument with a union.

For example:

constructor(
  parent: ParentNode | Create, // changed this type
  tag: string,
  classNames: string,
  value?: string,
  attr?: Record<string, unknown>
) {
  //...
}

See playground

Upvotes: 1

Related Questions