javascriptcssweb-componentnative-web-component

Align separate native HTML web component into a table or grid


I have a like a spreadsheet of data (rows and columns), and I am converting to web components. It makes sense to make each column a web component. However some of the fields may require more vertical space than others. I could set a max height and scroll within the box, but I would prefer for the "row", i.e. the nth cell of each column, to be the same height. How would you make it such that it always appears as a grid?

In the example below "cell" A2 and B2 do not align vertically because they are independant. How does one insert webcomponents into a <table> or a display: grid element when the web component needs span multiple rows, but keep the rows aligned?

class Component extends HTMLElement {
    constructor() {
        super().attachShadow({mode:'open'});
        const template = document.getElementById("TEMPLATE");
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    connectedCallback() {
        if (this.initialised) return; // Prevent initialising twice is item is moved
        setTimeout(() => {
            this.initialised = true;
            this.init();
        });
    }
    get(id) {
        return this.shadowRoot.getElementById(id);
    }
    init(required = true) {
        const prev = this.previousElementSibling;
        let props = JSON.parse(prev.textContent);
        this.props = props;
    }
    set props(value) {
        this.get('TEXTAREA1').textContent = value.textarea1;
        this.get('TEXTAREA2').textContent = value.textarea2;
    }
}
window.customElements.define('wc-column', Component);
#CONTAINER {
   display: flex;
   flex-direction: row;
}
<template id="TEMPLATE">
<style>
    :host {
        display: block;
        border: 1px solid lime;
    }
    #TEXTAREA1, #TEXTAREA2 {
        border: 1px solid red;
        display: block;
    }
</style>
<div id="TEXTAREA1"></div>
<div id="TEXTAREA2"></div>
</template>

<div id="CONTAINER">
    <script type="application/json">
        {
            "textarea1": "Cell A1", 
            "textarea2": "Cell A2"
        }
    </script>
    <wc-column></wc-column>
    <script type="application/json">
        {
            "textarea1": "Cell B1 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum",
            "textarea2": "Cell B2 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum"
        }
    </script>
    <wc-column></wc-column>
</div>


Solution

  • If i'm understanding the question correctly, what you want is so that all the rows have equal height right? if so, you just need to add display: grid to both #CONTAINER and :host like this:

    class Component extends HTMLElement {
      constructor() {
        super().attachShadow({
          mode: 'open'
        });
        const template = document.getElementById("TEMPLATE");
        this.shadowRoot.appendChild(template.content.cloneNode(true));
      }
      connectedCallback() {
        if (this.initialised) return; // Prevent initialising twice is item is moved
        setTimeout(() => {
          this.initialised = true;
          this.init();
        });
      }
      get(id) {
        return this.shadowRoot.getElementById(id);
      }
      init(required = true) {
        const prev = this.previousElementSibling;
        let props = JSON.parse(prev.textContent);
        this.props = props;
      }
      set props(value) {
        this.get('TEXTAREA1').textContent = value.textarea1;
        this.get('TEXTAREA2').textContent = value.textarea2;
      }
    }
    window.customElements.define('wc-column', Component);
    #CONTAINER {
      display: grid;
      grid-auto-flow: column;
    }
    <template id="TEMPLATE">
    <style>
        :host {
            border: 1px solid lime;
            display: grid;
            grid-auto-rows: 1fr;
        }
        #TEXTAREA1, #TEXTAREA2 {
            border: 1px solid red;
            display: block;
        }
    </style>
    <div id="TEXTAREA1"></div>
    <div id="TEXTAREA2"></div>
    </template>
    
    <div id="CONTAINER">
      <script type="application/json">
        {
          "textarea1": "Cell A1",
          "textarea2": "Cell A2"
        }
      </script>
      <wc-column></wc-column>
      <script type="application/json">
        {
          "textarea1": "Cell B1 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum",
          "textarea2": "Cell B2 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum"
        }
      </script>
      <wc-column></wc-column>
    </div>

    EDIT

    I just realized that above solution will create equal heights for all of the rows (not just equal per row, but for all rows). There is another way (which is more hacky) by using display: contents. We can add the display: contents to the wc-column so that the children of that element is treated as if they're direct children of the container, thus applying the grid layout:

    class Component extends HTMLElement {
      constructor() {
        super().attachShadow({
          mode: 'open'
        });
        const template = document.getElementById("TEMPLATE");
        this.shadowRoot.appendChild(template.content.cloneNode(true));
      }
      connectedCallback() {
        if (this.initialised) return; // Prevent initialising twice is item is moved
        setTimeout(() => {
          this.initialised = true;
          this.init();
        });
      }
      get(id) {
        return this.shadowRoot.getElementById(id);
      }
      init(required = true) {
        const prev = this.previousElementSibling;
        let props = JSON.parse(prev.textContent);
        this.props = props;
      }
      set props(value) {
        this.get('TEXTAREA1').textContent = value.textarea1;
        this.get('TEXTAREA2').textContent = value.textarea2;
      }
    }
    window.customElements.define('wc-column', Component);
    #CONTAINER {
      display: grid;
      grid-template-rows: auto auto;
      grid-auto-flow: column;
    }
    <template id="TEMPLATE">
    <style>
        :host {
            border: 1px solid lime; 
            display:contents
        }
        #TEXTAREA1, #TEXTAREA2 {
            border: 1px solid red;
            display: block;
        }
    </style>
    <div id="TEXTAREA1"></div>
    <div id="TEXTAREA2"></div>
    </template>
    
    <div id="CONTAINER">
      <script type="application/json">
        {
          "textarea1": "Cell A1",
          "textarea2": "Cell A2"
        }
      </script>
      <wc-column></wc-column>
      <script type="application/json">
        {
          "textarea1": "Cell B1 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum",
          "textarea2": "Cell B2 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum"
        }
      </script>
      <wc-column></wc-column>
      <script type="application/json">
        {
          "textarea1": "Cell C1 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum",
          "textarea2": "Cell C2 - Long text that overflow and requires extra vertical height because it is so long, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsu, lorem ipsummmmmmmmmmmmmmmmmmmm, lorem ipsum"
        }
      </script>
      <wc-column></wc-column>
    </div>

    But with this method, please note that if you want to add another TEXTAREA to the template, you also need to add more auto to the grid-template-rows: auto auto (e.g. 3 rows/3 TEXTAREA, then grid-template-rows: auto auto auto