skip to Main Content

I’ve created a custom element that mimics c#’s datagridview. I was able to make the <thead> stick at the top like dot net’s listview or datagridview header until just recently I noticed it doesn’t work anymore.

Everything in this custom element are programmatically generated including the CSS rules. Here’s the code to generate the CSS rules:

class ListView extends HTMLElement {
    static #lvwId = null;
    static #lvwCss;
    static #initId() {
        if (ListView.#lvwId == null) {
            ListView.#lvwId = '-lvw-' + Array.from(Date.now().toString()).map(b => ('00' + b.toString(16)).slice(-2)).join('');
            ListView.#lvwCss = ListView.#lvwId + '-css';
        }
    }
    connectedCallback() {
        ListView.#initId();
        let style = document.getElementById(ListView.#lvwId);
        if (style == null) {
            ListView.#style = style = document.createElement('style');
            style.type = 'text/css';
            style.id = ListView.#lvwId;
            const fn = (...b) => {
                if (b.length == 1) {
                    let c = b[0];
                    let d = '.' + ListView.#lvwCss;
                    if (c[0] == '-')
                        c = c.substr(1);
                    else
                        d += ' ';
                    d += c;
                    ListView.#style.appendChild(document.createTextNode(d));
                }
                else {
                    let c = '';
                    for (let d of b[0]) {
                        if (c != '') c += ',';
                        c += '.' + ListView.#lvwCss + ' ' + d;
                    }
                    ListView.#style.appendChild(document.createTextNode(c + ' ' + b[1]));
                }
            };
            fn('{position:relative;background-color:white;outline:none;display:block;overflow:auto;}');
            fn('*{user-select:none;box-sizing:border-box;position:relative;padding:0px;margin:0px;background-color:transparent;color:inherit}');
            fn('{--gridcolor:lightgray;--xmargins:5px;--itembackcolor:white;--itemforecolor:black;--headerbackcolor:gray;--headerforecolor:black;--headerhoverback:forestgreen;--headerhoverfore:black;--headerpadding:4px;--itempadding:4px;}');
            fn('table{border-collapse:collapse;z-index:0;left:0px;top:0px;background:white;table-layout:fixed;display:block}');
            fn('thead{position:sticky;top:0px;z-index:3}');
            fn('thead>tr{position:sticky}');
            fn(['th', 'td'], '{text-align:left;vertical-align:top;}');
            fn('th{position:sticky;top:-1px;background-color:var(--headerbackcolor);color:var(--headerforecolor);border-right:1px solid var(--gridcolor);transition:background-color .5s}');
            fn('-:not([headeronly]) th{cursor:pointer;}');
            fn('-:not([headeronly]) th:hover{background-color:var(--headerhoverback);color:var(--headerhoverfore)}');
            fn(['th>div', 'td>div'], '{height:100%;display:flex;flex-direction:row;justify-content:flex-start;align-content:flex-start;align-items:flex-start}');
            fn('th>div{margin-left:var(--xmargins);padding: var(--headerpadding) var(--xmargins) var(--headerpadding) 0px;}');
            fn('th>div>p{width:100%}');
            fn('td{background:var(--itembackcolor);color:var(--itemforecolor);}')
            fn('th::after{content:"";width:var(--xmargins);position:absolute;right:0px;top:0px;height:100%;cursor:ew-resize}')
            fn('th:not(:first-child)::before{content:"";width:var(--xmargins);position:absolute;left:0px;top:0px;height:100%;cursor:ew-resize}')
            fn('td>div{padding:var(--xmargins) var(--itempadding);width:100%;}');
            fn('td>div>p>img{float:left;margin-right:5px}');
            fn('td>input{height:100%;width:100%;outline:0px solid transparent;border:none;padding:1px;background:white;color:black;}');
            fn('td{color:black}');
            fn('tr[selected]{background:#018;color:white}');
            fn('tr[selected]>td:not([selected]){background:transparent;color:white}');
            fn('tr[selected]>td[selected]{background:rgba(255,255,255,.2);}');
            fn('tbody>tr:nth-child(even){--bg-color:rgba(0,0,0,.05)}');
            fn('tbody>tr:nth-child(even) td{background-color:var(--bg-color);}');
            fn('.sticky-hdr{position:sticky;overflow:hidden;z-index:1;top:0px;width:100%}');
            fn('tbody>tr{border-bottom:1px solid var(--gridcolor);color:black}');
            fn('.splits{border-top:1px solid var(--gridcolor)}');
            fn('tbody>tr>td p{outline:none;width:100%;}');
            fn('tbody td{border-right:1px solid var(--gridcolor)}');
            fn('-[nogrid] tbody * {border-color:transparent}');
            fn('-[nogrid] tbody>tr:nth-child(even)>td{border-color:var(--bg-color)}');
            fn('-[nowrap] p{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}');
            fn('-[checkboxes] td>div>div[checkbox]{float:left;border:1px solid gray;margin-right:5px;width:16px;height:16px;background:white}')
            fn("-[checkboxes] td>div>div[checkbox="checked"]::after{content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0' y='0' viewbow='0 0 16 16'%3E%3Cpath fill='black' d='M4,9l0,-3l3,3l5,-5,l0,3l-5,5Z' /%3E%3C/svg%3E");width:16px;height:16px;position:absolute;left:-1.5px;top:-1px}");
            document.body.insertAdjacentElement('beforebegin', style);
        }
        this.tabIndex = $(this).index();
        $(this).addClass(ListView.#lvwCss);
        this.#headerPlaceholder = document.createElement('div');
        this.#headerPlaceholder.setAttribute('style', 'width:100%;height:32px;overflow:none;position:sticky;left:0px;top:0px;background:gray');
        this.#headerPlaceholder.innerHTML = '&nbsp;';
        this.append(this.#headerPlaceholder);
        this.#table = document.createElement('table');
        this.#table.style.top = '0px';
        this.#thead = document.createElement('thead');
        this.#table.appendChild(this.#thead);
        this.#tbody = document.createElement('tbody');
        this.#table.appendChild(this.#tbody);
        this.#header = document.createElement('tr');
        this.#table.firstChild.insertAdjacentElement('afterbegin', this.#header);
        $(this).append(this.#table);

        this.#handleEvents();
        this.#handleHeaderEvents();

        const observer = new ResizeObserver(entries => {
            const ofh = this.#thead.offsetHeight - 1;
            this.#headerPlaceholder.style.height = ofh + 'px';
            this.#table.style.top = (-ofh) + 'px';
        });
        observer.observe(this.#thead);
    }
}

Inside the connectedCallback function is the creation of this.#headerPlaceholder:

        this.#headerPlaceholder = document.createElement('div');
        this.#headerPlaceholder.setAttribute('style', 'width:100%;height:32px;overflow:none;position:sticky;left:0px;top:0px;background:gray');
        this.#headerPlaceholder.innerHTML = '&nbsp;';

And as you can see, this element is sticky and it do sticks. But it’s the <thead> that sticky positioning don’t work. Again, it was working and I don’t know why it just suddenly stopped working.

I have searched a lot to find the cause of this but I’ve got no clue. I followed the suggested solutions and accepted solutions I’ve found in stackoverflow like combining sticky position with css top rule but still it doesn’t work. I’ve tried using position fixed but it created another problem of column sizes e.g. <th> element not following the width value and header column widths and td widths not the same. I’ve checked if outside rules are affecting but nothing I’ve found so far. I have also extended the sticky position to all <tr>s and <th>s but to no avail.

A non-jquery solution is preferrable although you might observe that there are some jquery calls in the snippet I have posted. I am trying my custom element to have less to zero dependencies but as of now it uses jquery in some parts.

Update

I can’t directly paste the generated CSS and mark-up here but I think links will do:

Generated CSS: https://pastebin.com/embed_js/9tVx409n

Generated mark-up: https://pastebin.com/embed_js/6EmpvY6Q

Edit: adding an SO snippet:

<div class="flex fdcol jcsb cx100 cy100">
  <div class="cx100 cy100">
    <ek-listview class="cx100 -lvw-01070108000005050008030200-css" id="lvw" nowrap="" style="border: 1px solid rgb(170, 170, 170); width: 1094px; height: 265.672px;" tabindex="0">
      <table style="top: 0px; width: 890px;">
        <thead>
          <tr>
            <th style="width: 250px;">
              <div>
                <p style="text-align: left;">Name</p>
              </div>
            </th>
            <th style="width: 100px;">
              <div>
                <p style="text-align: left;">Bio Name</p>
              </div>
            </th>
            <th style="width: 100px;">
              <div>
                <p style="text-align: left;">Date Hired</p>
              </div>
            </th>
            <th style="width: 120px;">
              <div>
                <p style="text-align: left;">Service Length</p>
              </div>
            </th>
            <th style="width: 120px;">
              <div>
                <p style="text-align: left;">Position</p>
              </div>
            </th>
            <th style="width: 90px;">
              <div>
                <p style="text-align: right;">Salary</p>
              </div>
            </th>
            <th style="width: 110px;">
              <div>
                <p style="text-align: right;">Payments</p>
              </div>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>
              <div>
                <p><span>Employee 1</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Aug 1, 2019</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>4 years, 10 months &amp; 9 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Sales Executive</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,000.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 2</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>May 3, 2023</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>1 year, 1 month &amp; 7 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Installer</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,100.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 4</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Dec 7, 2021</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>2 years, 6 months &amp; 3 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Installer</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>10,400.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 5</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>May 1, 2023</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>1 year, 1 month &amp; 8 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Installer</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,999.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 6</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Apr 4, 2024</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>0 year, 2 months &amp; 6 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Office Admin</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,750.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 7</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Mar 1, 2024</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>0 year, 3 months &amp; 9 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Installer</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>10,444.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 8</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Apr 3, 2024</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>0 year, 2 months &amp; 6 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Installer</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,750.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 9</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Sep 11, 2023</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>0 year, 8 months &amp; 29 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Area Supervisor</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,750.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 10</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Feb 1, 2024</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>0 year, 4 months &amp; 9 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>IT Programmer</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>12,000.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <div>
                <p><span>Employee 11</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span></span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Feb 26, 2024</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>0 year, 3 months &amp; 13 days</span></p>
              </div>
            </td>
            <td>
              <div>
                <p><span>Office Admin</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span>9,750.00</span></p>
              </div>
            </td>
            <td>
              <div>
                <p style="text-align: right;"><span></span></p>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </ek-listview>
  </div>
</div>
<style type="text/css" id="-lvw-01070107090900090401010003">
  .-lvw-01070107090900090401010003-css {
    position: relative;
    background-color: white;
    outline: none;
    display: block;
    overflow: auto;
  }
  
  .-lvw-01070107090900090401010003-css * {
    user-select: none;
    box-sizing: border-box;
    position: relative;
    padding: 0px;
    margin: 0px;
    background-color: transparent;
    color: inherit
  }
  
  .-lvw-01070107090900090401010003-css {
    --gridcolor: lightgray;
    --xmargins: 5px;
    --itembackcolor: white;
    --itemforecolor: black;
    --headerbackcolor: gray;
    --headerforecolor: black;
    --headerhoverback: forestgreen;
    --headerhoverfore: black;
    --headerpadding: 4px;
    --itempadding: 4px;
  }
  
  .-lvw-01070107090900090401010003-css table {
    border-collapse: collapse;
    z-index: 0;
    left: 0px;
    top: 0px;
    background: white;
    table-layout: fixed;
    display: block
  }
  
  .-lvw-01070107090900090401010003-css thead {
    position: sticky;
    z-index: 11;
    top: 0px
  }
  
  .-lvw-01070107090900090401010003-css th,
  .-lvw-01070107090900090401010003-css td {
    text-align: left;
    vertical-align: top;
  }
  
  .-lvw-01070107090900090401010003-css th {
    position: sticky;
    top: 0px;
    background-color: var(--headerbackcolor);
    color: var(--headerforecolor);
    border-right: 1px solid var(--gridcolor);
    transition: background-color .5s
  }
  
  .-lvw-01070107090900090401010003-css:not([headeronly]) th {
    cursor: pointer;
  }
  
  .-lvw-01070107090900090401010003-css:not([headeronly]) th:hover {
    background-color: var(--headerhoverback);
    color: var(--headerhoverfore)
  }
  
  .-lvw-01070107090900090401010003-css th>div,
  .-lvw-01070107090900090401010003-css td>div {
    height: 100%;
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-content: flex-start;
    align-items: flex-start
  }
  
  .-lvw-01070107090900090401010003-css th>div {
    margin-left: var(--xmargins);
    padding: var(--headerpadding) var(--xmargins) var(--headerpadding) 0px;
  }
  
  .-lvw-01070107090900090401010003-css th>div>p {
    width: 100%
  }
  
  .-lvw-01070107090900090401010003-css td {
    background: var(--itembackcolor);
    color: var(--itemforecolor);
  }
  
  .-lvw-01070107090900090401010003-css th::after {
    content: "";
    width: var(--xmargins);
    position: absolute;
    right: 0px;
    top: 0px;
    height: 100%;
    cursor: ew-resize
  }
  
  .-lvw-01070107090900090401010003-css th:not(:first-child)::before {
    content: "";
    width: var(--xmargins);
    position: absolute;
    left: 0px;
    top: 0px;
    height: 100%;
    cursor: ew-resize
  }
  
  .-lvw-01070107090900090401010003-css td>div {
    padding: var(--xmargins) var(--itempadding);
    width: 100%;
  }
  
  .-lvw-01070107090900090401010003-css td>div>p>img {
    float: left;
    margin-right: 5px
  }
  
  .-lvw-01070107090900090401010003-css td>input {
    height: 100%;
    width: 100%;
    outline: 0px solid transparent;
    border: none;
    padding: 1px;
    background: white;
    color: black;
  }
  
  .-lvw-01070107090900090401010003-css td {
    color: black
  }
  
  .-lvw-01070107090900090401010003-css tr[selected] {
    background: #018;
    color: white
  }
  
  .-lvw-01070107090900090401010003-css tr[selected]>td:not([selected]) {
    background: transparent;
    color: white
  }
  
  .-lvw-01070107090900090401010003-css tr[selected]>td[selected] {
    background: rgba(255, 255, 255, .2);
  }
  
  .-lvw-01070107090900090401010003-css tbody>tr:nth-child(even) {
    --bg-color: rgba(0, 0, 0, .05)
  }
  
  .-lvw-01070107090900090401010003-css tbody>tr:nth-child(even) td {
    background-color: var(--bg-color);
  }
  
  .-lvw-01070107090900090401010003-css tbody>tr {
    border-bottom: 1px solid var(--gridcolor);
    color: black
  }
  
  .-lvw-01070107090900090401010003-css .splits {
    border-top: 1px solid var(--gridcolor)
  }
  
  .-lvw-01070107090900090401010003-css tbody>tr>td p {
    outline: none;
    width: 100%;
  }
  
  .-lvw-01070107090900090401010003-css tbody td {
    border-right: 1px solid var(--gridcolor)
  }
  
  .-lvw-01070107090900090401010003-css[nogrid] tbody * {
    border-color: transparent
  }
  
  .-lvw-01070107090900090401010003-css[nogrid] tbody>tr:nth-child(even)>td {
    border-color: var(--bg-color)
  }
  
  .-lvw-01070107090900090401010003-css[nowrap] p {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden
  }
  
  .-lvw-01070107090900090401010003-css[checkboxes] td>div>div[checkbox] {
    float: left;
    border: 1px solid gray;
    margin-right: 5px;
    width: 16px;
    height: 16px;
    background: white
  }
  
  .-lvw-01070107090900090401010003-css[checkboxes] td>div>div[checkbox="checked"]::after {
    content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0' y='0' viewbow='0 0 16 16'%3E%3Cpath fill='black' d='M4,9l0,-3l3,3l5,-5,l0,3l-5,5Z' /%3E%3C/svg%3E");
    width: 16px;
    height: 16px;
    position: absolute;
    left: -1.5px;
    top: -1px
  }
</style>

2

Answers


  1. Chosen as BEST ANSWER

    First of all, guys, thank you for your time. In the widget or component or whatever it may be appropriately called, my main objective is to have a resizable header that sticks at the top. Whatever I did I just can’t make it happen. I tried to hardcode the enclosing div’s width and height but it still didn’t solve the problem.

    I want this component to be as reusable as possible (at least, for me) without having to do other stuff aside from adding it to a web page. I mean, no more linking to external css and js files.

    So, my solution, although I think not a permanent one but at least it serves the purpose, is to have two tables. The first will serve as the header and the other is the body.

    Both tables are inside a div for the scrollbars.

    The basic css goes like this:

    table{border-collapse:collapse;z-index:0;left:0px;top:0px;background:white;table-layout:fixed}
    table:first-of-type{position:sticky;top:0px;z-index:2}
    table:last-child{position:absolute}
    

    The dynamic creation of css is necessary to create an ID as unique as possible for the style element. The purpose is to avoid collition with other elements’ IDs. The generated ID concatenated with the string '-css' will serve as the root class of the component to make all css rules scoped (I am not sure if it is the right term). This ID also ensures that only style is created for all instances of the component.

    To ascertain the creation of the unique ID, this method is called

        static #initId() {
            if (ListView.#lvwId == null) {
                ListView.#lvwId = '-lvw-' + Array.from(Date.now().toString()).map(b => ('00' + b.toString(16)).slice(-2)).join('');
                ListView.#lvwCss = ListView.#lvwId + '-css';
            }
        }
    

    And in the connectedCallback() function...

        connectedCallback() {
            ListView.#initId();
            let style = document.getElementById(ListView.#lvwId);
            if (style == null) {
                ListView.#style = style = document.createElement('style');
                style.type = 'text/css';
                style.id = ListView.#lvwId;
                const fn = (...b) => {
                    if (b.length == 1) {
                        let c = b[0];
                        let d = '.' + ListView.#lvwCss;
                        if (c[0] == '-')
                            c = c.substr(1);
                        else
                            d += ' ';
                        d += c;
                        ListView.#style.appendChild(document.createTextNode(d));
                    }
                    else {
                        let c = '';
                        for (let d of b[0]) {
                            if (c != '') c += ',';
                            c += '.' + ListView.#lvwCss + ' ' + d;
                        }
                        ListView.#style.appendChild(document.createTextNode(c + ' ' + b[1]));
                    }
                };
                fn('*{user-select:none;box-sizing:border-box;position:relative;padding:0px;margin:0px;background-color:transparent;color:inherit}');
                fn('{--gridcolor:lightgray;--xmargins:5px;--itembackcolor:white;--itemforecolor:black;--headerbackcolor:gray;--headerforecolor:black;--headerhoverback:forestgreen;--headerhoverfore:black;--headerpadding:4px;--itempadding:4px;}');
                fn('table{border-collapse:collapse;z-index:0;left:0px;top:0px;background:white;table-layout:fixed}');
                fn('table:first-of-type{position:sticky;top:0px;z-index:2}');
                fn('table:last-child{position:absolute}');
                fn('>div{width:100%;height:32px;overflow:hidden;position:sticky;left:0px;top:0px;background:gray}');
                fn(['th', 'td'], '{text-align:left;vertical-align:top;}');
                fn('th{background-color:var(--headerbackcolor);color:var(--headerforecolor);border-right:1px solid var(--gridcolor);transition:background-color .5s}');
                fn('-:not([headeronly]) th{cursor:pointer;}');
                fn('-:not([headeronly]) th:hover{background-color:var(--headerhoverback);color:var(--headerhoverfore)}');
                fn(['th>div', 'td>div'], '{height:100%;display:flex;flex-direction:row;justify-content:flex-start;align-content:flex-start;align-items:flex-start}');
                fn('th>div{margin-left:var(--xmargins);padding: var(--headerpadding) var(--xmargins) var(--headerpadding) 0px;}');
                fn('th>div>p{width:100%}');
                fn('thead>tr:not(:first-child){border-top:1px solid var(--gridcolor)}');
                fn('td{background:var(--itembackcolor);color:var(--itemforecolor);}');
                fn('th::after{content:"";width:var(--xmargins);position:absolute;right:0px;top:0px;height:100%;cursor:ew-resize}');
                fn('th:not(:first-child)::before{content:"";width:var(--xmargins);position:absolute;left:0px;top:0px;height:100%;cursor:ew-resize}');
                fn('td>div{padding:var(--xmargins) var(--itempadding);width:100%;}');
                fn('td>div>p>img{float:left;margin-right:5px}');
                fn('td>input{height:100%;width:100%;outline:0px solid transparent;border:none;padding:1px;background:white;color:black;}');
                fn('td{color:black}');
                fn('tr[selected]{background:#018;color:white}');
                fn('tr[selected]>td:not([selected]){background:transparent;color:white}');
                fn('tr[selected]>td[selected]{background:rgba(255,255,255,.2);}');
                fn('tbody>tr:nth-child(even){--bg-color:rgba(0,0,0,.05)}');
                fn('tbody>tr:nth-child(even) td{background-color:var(--bg-color);}');
                fn('tbody>tr{border-bottom:1px solid var(--gridcolor);color:black}');
                fn('tbody>tr>td p{outline:none;width:100%;}');
                fn('tbody td{border-right:1px solid var(--gridcolor)}');
                fn('-[nogrid] tbody * {border-color:transparent}');
                fn('-[nogrid] tbody>tr:nth-child(even)>td{border-color:var(--bg-color)}');
                fn('-[nowrap] p{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}');
                fn('-[checkboxes] td>div>div[checkbox]{float:left;border:1px solid gray;margin-right:5px;width:16px;height:16px;background:white}')
                fn("-[checkboxes] td>div>div[checkbox="checked"]::after{content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0' y='0' viewbow='0 0 16 16'%3E%3Cpath fill='black' d='M4,9l0,-3l3,3l5,-5,l0,3l-5,5Z' /%3E%3C/svg%3E");width:16px;height:16px;position:absolute;left:-1.5px;top:-1px}");
                fn('-[rowheader] th:first-child{position:sticky;left:0px}');
                fn('-[rowheader] td:first-child{position:sticky;left:0px}');
                document.body.insertAdjacentElement('beforebegin', style);
            }
            this.tabIndex = $(this).index();
            this.classList.add(ListView.#lvwCss);
            this.setAttribute('style', 'background-color:white;outline:none;display:block;overflow:auto;width:100%;height:100%');
    
            this.#headTable = document.createElement('table');
            this.#headColGroup = document.createElement('colgroup');
            this.#headTable.appendChild(this.#headColGroup);
            this.#thead = document.createElement('thead');
            this.#header = document.createElement('tr');
            this.#thead.appendChild(this.#header);
            this.#headTable.appendChild(this.#thead);
            this.appendChild(this.#headTable);
    
            this.#bodyTable = document.createElement('table');
            this.#bodyColGroup = document.createElement('colgroup');
            this.#bodyTable.appendChild(this.#bodyColGroup);
            this.#tbody = document.createElement('tbody');
            this.#bodyTable.appendChild(this.#tbody);
            this.appendChild(this.#bodyTable);
    
            this.#handleEvents();
            this.#handleHeaderEvents();
    
            const observer = new ResizeObserver(() => {
                const r = this.#headTable.getBoundingClientRect();
                this.#bodyTable.style.top = `${r.height}px`;
                this.#bodyTable.style.width = `${r.width}px`;
            });
            observer.observe(this.#headTable);
        }
    

    Note that there is a fn() method inside the connectedCallback() method which obvious is responsible in placing the css rules. It also ensures that all rules are prepended with the ListView.#lvwCss value.

    Regarding the ResizeObserver inside the connectedCallback, it is necessary to make sure that the position of the second table is right at the bottom of the first table. The header table height could change, first: when nowrap attribute is not specified when adding the component in the web page and, second: where there are split columns.

    The basic mark-up could look like this:

    <div class="the_generated_class_name" style="outline:none;display:block;overflow:auto;width:100%;height:100%">
        <table>
            <colgroup>
                ...cols added dynamically
            </colgroup>
            <thead>
                ...trs and ths are added dynamically
            </thead>
        </table>
        <table>
            <colgroup>
                ...cols added dynamically
            </colgroup>
            <tbody>
                ...trs and tds are added dynamically
            </tbody>
        </table>
    </div>
    

    The <colgroup>’s main purpose is the synchronized resizing of columns of both tables. The component is fully customizable through the css variables provided and of course its design can still be manipulated through the last css rules or inline styles.

    Since adding headers and rows are done via script, here is a sample script.

    const lvw = document.getElementById('lvw');
    lvw.headers.add(
        {
            name: 'name',
            text: 'Name',
            width: 250,
            valign: 'middle'
        },
        {
            name: 'datehired',
            text: 'Date Hired',
            width: 100,
            editable: true,
            deletable: false,
            type: 'date',
            valign: 'middle'
        },
        {
            name: 'service',
            text: 'Service Length',
            width: 120,
            valign: 'middle'
        },
        {
            name: 'position',
            text: 'Position',
            width: 120,
            valign: 'middle'
        },
        {
            name: 'salary',
            text: 'Salary',
            width: 90,
            alignment: 'right',
            editable: true,
            pseudotype: 'ufloat',
            valign: 'middle'
        },
        {
            name: 'payments',
            text: 'Payments',
            width: 110,
            alignment: 'right',
            valign: 'middle'
        },
        {
            name: 'sss',
            text: 'SSS',
            width: 200,
            alignment: 'center',
            splits: [
                {
                    name: 'sssnum',
                    text: 'Number',
                    width: 120,
                    editable: true
                },
                {
                    name: 'sssshare',
                    text: 'Share',
                    width: 80,
                    alignment: 'right',
                    pseudotype: 'ufloat',
                    editable: true
                }
            ]
        },
        {
            name: 'philhealth',
            text: 'PhilHealth',
            width: 200,
            alignment: 'center',
            splits: [
                {
                    name: 'philhealthnum',
                    text: 'Number',
                    width: 120,
                    editable: true
                },
                {
                    name: 'philhealthshare',
                    text: 'Share',
                    width: 80,
                    alignment: 'right',
                    pseudotype: 'ufloat',
                    editable: true
                }
            ]
        },
        {
            name: 'pagibig',
            text: 'Pag-Ibig',
            width: 200,
            alignment: 'center',
            splits: [
                {
                    name: 'pagibignum',
                    text: 'Number',
                    width: 120,
                    editable: true
                },
                {
                    name: 'pagibigshare',
                    text: 'Share',
                    width: 80,
                    alignment: 'right',
                    pseudotype: 'ufloat',
                    editable: true
                }
            ]
        }
    );
    

    And adding items goes like this:

        $.get('/datasource/', { data: cookies.sessionid,q:'e' }, data =>
        {
            for (let a of data.data) {
                const row = lvw.rows.add(a);
                row.salary = parseFloat(a.salary.replace(/,/g, ''));
                row.datehired = new Date(row.datehired);
            }
        });
    

    Again, thank you.


  2. The class of the ek-listview ancestor does not match all the classes of the ancestors in the CSS.

    The CSS has .-lvw-01070107090900090401010003-css

    The ek-listview has class -lvw-01070108000005050008030200-css

    Changing the class (by hand) in the snippet gives formatting of the table, with the thead having position sticky.

    <div class="flex fdcol jcsb cx100 cy100">
      <div class="cx100 cy100">
        <ek-listview class="cx100 -lvw-01070107090900090401010003-css" id="lvw" nowrap="" style="border: 1px solid rgb(170, 170, 170); width: 1094px; height: 265.672px;" tabindex="0">
          <table style="top: 0px; width: 890px;">
            <thead>
              <tr>
                <th style="width: 250px;">
                  <div>
                    <p style="text-align: left;">Name</p>
                  </div>
                </th>
                <th style="width: 100px;">
                  <div>
                    <p style="text-align: left;">Bio Name</p>
                  </div>
                </th>
                <th style="width: 100px;">
                  <div>
                    <p style="text-align: left;">Date Hired</p>
                  </div>
                </th>
                <th style="width: 120px;">
                  <div>
                    <p style="text-align: left;">Service Length</p>
                  </div>
                </th>
                <th style="width: 120px;">
                  <div>
                    <p style="text-align: left;">Position</p>
                  </div>
                </th>
                <th style="width: 90px;">
                  <div>
                    <p style="text-align: right;">Salary</p>
                  </div>
                </th>
                <th style="width: 110px;">
                  <div>
                    <p style="text-align: right;">Payments</p>
                  </div>
                </th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 1</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Aug 1, 2019</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>4 years, 10 months &amp; 9 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Sales Executive</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,000.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 2</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>May 3, 2023</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>1 year, 1 month &amp; 7 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Installer</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,100.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 4</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Dec 7, 2021</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>2 years, 6 months &amp; 3 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Installer</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>10,400.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 5</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>May 1, 2023</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>1 year, 1 month &amp; 8 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Installer</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,999.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 6</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Apr 4, 2024</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>0 year, 2 months &amp; 6 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Office Admin</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,750.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 7</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Mar 1, 2024</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>0 year, 3 months &amp; 9 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Installer</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>10,444.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 8</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Apr 3, 2024</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>0 year, 2 months &amp; 6 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Installer</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,750.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 9</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Sep 11, 2023</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>0 year, 8 months &amp; 29 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Area Supervisor</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,750.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 10</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Feb 1, 2024</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>0 year, 4 months &amp; 9 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>IT Programmer</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>12,000.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
              <tr>
                <td>
                  <div>
                    <p><span>Employee 11</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span></span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Feb 26, 2024</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>0 year, 3 months &amp; 13 days</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p><span>Office Admin</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span>9,750.00</span></p>
                  </div>
                </td>
                <td>
                  <div>
                    <p style="text-align: right;"><span></span></p>
                  </div>
                </td>
              </tr>
            </tbody>
          </table>
        </ek-listview>
      </div>
    </div>
    <style type="text/css" id="-lvw-01070107090900090401010003">
      .-lvw-01070107090900090401010003-css {
        position: relative;
        background-color: pink;
        outline: none;
        display: block;
        overflow: auto;
      }
      
      .-lvw-01070107090900090401010003-css * {
        user-select: none;
        box-sizing: border-box;
        position: relative;
        padding: 0px;
        margin: 0px;
        background-color: transparent;
        color: inherit
      }
      
      .-lvw-01070107090900090401010003-css {
        --gridcolor: lightgray;
        --xmargins: 5px;
        --itembackcolor: white;
        --itemforecolor: black;
        --headerbackcolor: gray;
        --headerforecolor: black;
        --headerhoverback: forestgreen;
        --headerhoverfore: black;
        --headerpadding: 4px;
        --itempadding: 4px;
      }
      
      .-lvw-01070107090900090401010003-css table {
        border-collapse: collapse;
        z-index: 0;
        left: 0px;
        top: 0px;
        background: white;
        table-layout: fixed;
        display: block
      }
      
      .-lvw-01070107090900090401010003-css thead {
        position: sticky;
        z-index: 11;
        top: 0px
      }
      
      .-lvw-01070107090900090401010003-css th,
      .-lvw-01070107090900090401010003-css td {
        text-align: left;
        vertical-align: top;
      }
      
      .-lvw-01070107090900090401010003-css th {
        position: sticky;
        top: 0px;
        background-color: var(--headerbackcolor);
        color: var(--headerforecolor);
        border-right: 1px solid var(--gridcolor);
        transition: background-color .5s
      }
      
      .-lvw-01070107090900090401010003-css:not([headeronly]) th {
        cursor: pointer;
      }
      
      .-lvw-01070107090900090401010003-css:not([headeronly]) th:hover {
        background-color: var(--headerhoverback);
        color: var(--headerhoverfore)
      }
      
      .-lvw-01070107090900090401010003-css th>div,
      .-lvw-01070107090900090401010003-css td>div {
        height: 100%;
        display: flex;
        flex-direction: row;
        justify-content: flex-start;
        align-content: flex-start;
        align-items: flex-start
      }
      
      .-lvw-01070107090900090401010003-css th>div {
        margin-left: var(--xmargins);
        padding: var(--headerpadding) var(--xmargins) var(--headerpadding) 0px;
      }
      
      .-lvw-01070107090900090401010003-css th>div>p {
        width: 100%
      }
      
      .-lvw-01070107090900090401010003-css td {
        background: var(--itembackcolor);
        color: var(--itemforecolor);
      }
      
      .-lvw-01070107090900090401010003-css th::after {
        content: "";
        width: var(--xmargins);
        position: absolute;
        right: 0px;
        top: 0px;
        height: 100%;
        cursor: ew-resize
      }
      
      .-lvw-01070107090900090401010003-css th:not(:first-child)::before {
        content: "";
        width: var(--xmargins);
        position: absolute;
        left: 0px;
        top: 0px;
        height: 100%;
        cursor: ew-resize
      }
      
      .-lvw-01070107090900090401010003-css td>div {
        padding: var(--xmargins) var(--itempadding);
        width: 100%;
      }
      
      .-lvw-01070107090900090401010003-css td>div>p>img {
        float: left;
        margin-right: 5px
      }
      
      .-lvw-01070107090900090401010003-css td>input {
        height: 100%;
        width: 100%;
        outline: 0px solid transparent;
        border: none;
        padding: 1px;
        background: white;
        color: black;
      }
      
      .-lvw-01070107090900090401010003-css td {
        color: black
      }
      
      .-lvw-01070107090900090401010003-css tr[selected] {
        background: #018;
        color: white
      }
      
      .-lvw-01070107090900090401010003-css tr[selected]>td:not([selected]) {
        background: transparent;
        color: white
      }
      
      .-lvw-01070107090900090401010003-css tr[selected]>td[selected] {
        background: rgba(255, 255, 255, .2);
      }
      
      .-lvw-01070107090900090401010003-css tbody>tr:nth-child(even) {
        --bg-color: rgba(0, 0, 0, .05)
      }
      
      .-lvw-01070107090900090401010003-css tbody>tr:nth-child(even) td {
        background-color: var(--bg-color);
      }
      
      .-lvw-01070107090900090401010003-css tbody>tr {
        border-bottom: 1px solid var(--gridcolor);
        color: black
      }
      
      .-lvw-01070107090900090401010003-css .splits {
        border-top: 1px solid var(--gridcolor)
      }
      
      .-lvw-01070107090900090401010003-css tbody>tr>td p {
        outline: none;
        width: 100%;
      }
      
      .-lvw-01070107090900090401010003-css tbody td {
        border-right: 1px solid var(--gridcolor)
      }
      
      .-lvw-01070107090900090401010003-css[nogrid] tbody * {
        border-color: transparent
      }
      
      .-lvw-01070107090900090401010003-css[nogrid] tbody>tr:nth-child(even)>td {
        border-color: var(--bg-color)
      }
      
      .-lvw-01070107090900090401010003-css[nowrap] p {
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden
      }
      
      .-lvw-01070107090900090401010003-css[checkboxes] td>div>div[checkbox] {
        float: left;
        border: 1px solid gray;
        margin-right: 5px;
        width: 16px;
        height: 16px;
        background: white
      }
      
      .-lvw-01070107090900090401010003-css[checkboxes] td>div>div[checkbox="checked"]::after {
        content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0' y='0' viewbow='0 0 16 16'%3E%3Cpath fill='black' d='M4,9l0,-3l3,3l5,-5,l0,3l-5,5Z' /%3E%3C/svg%3E");
        width: 16px;
        height: 16px;
        position: absolute;
        left: -1.5px;
        top: -1px
      }
    </style>

    We can see the class being created dynamically here:

    ListView.#lvwId = '-lvw-' + Array.from(Date.now().toString()).map(b => ('00' + b.toString(16)).slice(-2)).join('');
                ListView.#lvwCss = ListView.#lvwId + '-css';
    

    It is dependent on date so I think what has happened is that the CSS being invoked is not in step with the dynamic code. They have to be created at the same time.

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