json-to-html-table-with-lit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

JSON to HTML Table with Lit

使用Lit实现JSON转HTML表格

In this article I will go over how to set up a Lit web component and use it to create a HTML Table from json url or inline json.
TLDR The final source here and an online demo.
本文将介绍如何搭建一个Lit Web组件,并使用它从JSON接口或内联JSON数据生成HTML表格
快速概览 最终源码可查看此处,在线演示可查看此处

Prerequisites 

前提条件

  • Vscode
  • Node >= 16
  • Typescript
  • Vscode
  • Node >= 16
  • Typescript

Getting Started 

开始上手

We can start off by navigating in terminal to the location of the project and run the following:
npm init @vitejs/app --template lit-ts
Then enter a project name 
lit-html-table
and now open the project in vscode and install the dependencies:
cd lit-html-table
npm i lit
npm i -D @types/node
code .
Update the 
vite.config.ts
with the following:
import { defineConfig } from "vite";
import { resolve } from "path";

export default defineConfig({
  base: "/lit-html-table/",
  build: {
    lib: {
      entry: "src/lit-html-table.ts",
      formats: ["es"],
    },
    rollupOptions: {
      input: {
        main: resolve(__dirname, "index.html"),
      },
    },
  },
});
我们可以先在终端中导航到项目所在位置,然后运行以下命令:
npm init @vitejs/app --template lit-ts
然后输入项目名称
lit-html-table
,接着在Vscode中打开项目并安装依赖:
cd lit-html-table
npm i lit
npm i -D @types/node
code .
更新
vite.config.ts
文件内容如下:
import { defineConfig } from "vite";
import { resolve } from "path";

export default defineConfig({
  base: "/lit-html-table/",
  build: {
    lib: {
      entry: "src/lit-html-table.ts",
      formats: ["es"],
    },
    rollupOptions: {
      input: {
        main: resolve(__dirname, "index.html"),
      },
    },
  },
});

Template 

模板文件

Open up the 
index.html
and update it with the following:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSON to Lit HTML Table</title>
    <script type="module" src="/src/lit-html-table.ts"></script>
  </head>

  <body>
    <lit-html-table src="https://jsonplaceholder.typicode.com/posts">
      <!-- <span slot="title" style="color: red;">Title</span> -->
      <!-- <script type="application/json">
      [
        {
          "id": "0",
          "name": "First Item"
        }
      ]
    </script> -->
    </lit-html-table>
  </body>
</html>
We are passing a src attribute to the web component for this example but we can also add a script tag with the type attribute set to 
application/json
 with the contents containing the json.
If any table header cell needed to be replaced an element can be provided with the slot name set to the key in the json object.
打开
index.html
并更新内容如下:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSON to Lit HTML Table</title>
    <script type="module" src="/src/lit-html-table.ts"></script>
  </head>

  <body>
    <lit-html-table src="https://jsonplaceholder.typicode.com/posts">
      <!-- <span slot="title" style="color: red;">Title</span> -->
      <!-- <script type="application/json">
      [
        {
          "id": "0",
          "name": "First Item"
        }
      ]
    </script> -->
    </lit-html-table>
  </body>
</html>
在这个示例中,我们给Web组件传递了一个src属性,但也可以添加一个type为
application/json
的script标签,并在其中写入JSON内容。
如果需要替换某个表头单元格,可以提供一个元素,并将其slot属性设置为JSON对象中的对应键名。

Web Component 

Web组件

Before we update our component we need to rename 
my-element.ts
 to 
lit-html-table.ts
Open up 
lit-html-table.ts
and update it with the following:
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

type ObjectData = { [key: string]: any };

@customElement("lit-html-table")
export class LitHtmlTable extends LitElement {
  @property() src = "";

  data?: ObjectData[];

  static styles = css`
    tr {
      text-align: var(--table-tr-text-align, left);
      vertical-align: var(--table-tr-vertical-align, top);
      padding: var(--table-tr-padding, 10px);
    }
  `;

  render() {
    // Check if data is loaded
    if (!this.values) {
      return html`<slot name="loading">Loading...</slot>`;
    }
    // Check if items are not empty
    if (this.values.length === 0) {
      return html`<slot name="empty">No Items Found!</slot>`;
    }
    // Convert JSON to HTML Table
    return html`
      <table>
        <thead>
          <tr>
            ${Object.keys(this.values[0]).map((key) => {
              const name = key.replace(/\b([a-z])/g, (_, val) =>
                val.toUpperCase()
              );
              return html`<th>
                <slot name="${key}">${name}</slot>
              </th>`;
            })}
          </tr>
        </thead>
        <tbody>
          ${this.values.map((item) => {
            return html`
              <tr>
                ${Object.values(item).map((row) => {
                  return html`<td>${row}</td>`;
                })}
              </tr>
            `;
          })}
        </tbody>
      </table>
    `;
  }

  async firstUpdated() {
    await this.fetchData();
  }

  // Download the latest json and update it locally
  async fetchData() {
    let _data: any;
    if (this.src.length > 0) {
      // If a src attribute is set prefer it over any slots
      _data = await fetch(this.src).then((res) => res.json());
    } else {
      // If no src attribute is set then grab the inline json in the slot
      const elem = this.parentElement?.querySelector(
        'script[type="application/json"]'
      ) as HTMLScriptElement;
      if (elem) _data = JSON.parse(elem.innerHTML);
    }
    this.values = this.transform(_data ?? []);
    this.requestUpdate();
  }

  transform(data: any) {
    return data;
  }
}
We have defined a few CSS Custom Properties to style the table cell but many more can be added here.
If everything goes well run the command 
npm run dev
and the follow should appear:
在更新组件之前,我们需要将
my-element.ts
重命名为
lit-html-table.ts
打开
lit-html-table.ts
并更新内容如下:
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

type ObjectData = { [key: string]: any };

@customElement("lit-html-table")
export class LitHtmlTable extends LitElement {
  @property() src = "";

  data?: ObjectData[];

  static styles = css`
    tr {
      text-align: var(--table-tr-text-align, left);
      vertical-align: var(--table-tr-vertical-align, top);
      padding: var(--table-tr-padding, 10px);
    }
  `;

  render() {
    // Check if data is loaded
    if (!this.values) {
      return html`<slot name="loading">Loading...</slot>`;
    }
    // Check if items are not empty
    if (this.values.length === 0) {
      return html`<slot name="empty">No Items Found!</slot>`;
    }
    // Convert JSON to HTML Table
    return html`
      <table>
        <thead>
          <tr>
            ${Object.keys(this.values[0]).map((key) => {
              const name = key.replace(/\b([a-z])/g, (_, val) =>
                val.toUpperCase()
              );
              return html`<th>
                <slot name="${key}">${name}</slot>
              </th>`;
            })}
          </tr>
        </thead>
        <tbody>
          ${this.values.map((item) => {
            return html`
              <tr>
                ${Object.values(item).map((row) => {
                  return html`<td>${row}</td>`;
                })}
              </tr>
            `;
          })}
        </tbody>
      </table>
    `;
  }

  async firstUpdated() {
    await this.fetchData();
  }

  // Download the latest json and update it locally
  async fetchData() {
    let _data: any;
    if (this.src.length > 0) {
      // If a src attribute is set prefer it over any slots
      _data = await fetch(this.src).then((res) => res.json());
    } else {
      // If no src attribute is set then grab the inline json in the slot
      const elem = this.parentElement?.querySelector(
        'script[type="application/json"]'
      ) as HTMLScriptElement;
      if (elem) _data = JSON.parse(elem.innerHTML);
    }
    this.values = this.transform(_data ?? []);
    this.requestUpdate();
  }

  transform(data: any) {
    return data;
  }
}
我们定义了一些CSS自定义属性来设置表格单元格样式,还可以在此基础上添加更多样式。
如果一切顺利,运行命令
npm run dev
后,页面将显示如下内容:

Editing 

编辑功能

What if we wanted to support editing of any cell? With Lit and Web Components we can progressively enhance the experience without changing the html.
At the top of the class add the following boolean property:
@property({ type: Boolean }) editable = false;
Now update the 
tbody
tag in the render method:
<tbody>
  ${this.values.map((item, index) => {
    return html`
      <tr>
        ${Object.entries(item).map((row) => {
          return html`<td>
            ${this.editable
              ? html`<input
                  value="${row[1]}"
                  type="text"
                  @input=${(e: any) => {
                    const value = e.target.value;
                    const key = row[0];
                    const current = this.values![index];
                    current[key] = value;
                    this.values![index] = current;
                    this.requestUpdate();
                    this.dispatchEvent(
                      new CustomEvent("input-cell", {
                        detail: {
                          index: index,
                          data: current,
                        },
                      })
                    );
                  }}
                />`
              : html`${row[1]}`}
          </td>`;
        })}
      </tr>
    `;
  })}
</tbody>
By checking to see if the 
editable
 and if 
true
 return an input with an event listener to update the data and dispatch an 
input
 event.
Add the 
editable
 attribute to the 
index.html
:
<lit-html-table editable> ... </lit-html-table>
After a reload the table should look like this and any cell can be edited.
An event listener can be added just before the closing 
body
 tag in 
index.html
to grab the latest values or cell information:
<script>
  const elem = document.querySelector("lit-html-table");
  elem.addEventListener(
    "input-cell",
    (e) => {
      // Index and data for the individual cell
      const { index, data } = e.detail;
      // New array of json items
      const values = elem.values;
    },
    false
  );
</script>
This can be taken farther by checking for the type of the value and returning a color, number or checkbox input.
如果我们想支持单元格编辑怎么办?借助Lit和Web组件,我们可以在不修改HTML结构的前提下逐步增强交互体验。
在类的顶部添加以下布尔类型属性:
@property({ type: Boolean }) editable = false;
然后更新render方法中的
tbody
标签:
<tbody>
  ${this.values.map((item, index) => {
    return html`
      <tr>
        ${Object.entries(item).map((row) => {
          return html`<td>
            ${this.editable
              ? html`<input
                  value="${row[1]}"
                  type="text"
                  @input=${(e: any) => {
                    const value = e.target.value;
                    const key = row[0];
                    const current = this.values![index];
                    current[key] = value;
                    this.values![index] = current;
                    this.requestUpdate();
                    this.dispatchEvent(
                      new CustomEvent("input-cell", {
                        detail: {
                          index: index,
                          data: current,
                        },
                      })
                    );
                  }}
                />`
              : html`${row[1]}`}
          </td>`;
        })}
      </tr>
    `;
  })}
</tbody>
通过检查
editable
属性,如果为
true
则返回一个输入框,并添加事件监听器来更新数据并派发
input
事件。
index.html
中给组件添加
editable
属性:
<lit-html-table editable> ... </lit-html-table>
重新加载页面后,表格将显示为可编辑状态,任意单元格都能修改。
可以在
index.html
body
闭合标签前添加事件监听器,以获取最新的值或单元格信息:
<script>
  const elem = document.querySelector("lit-html-table");
  elem.addEventListener(
    "input-cell",
    (e) => {
      // Index and data for the individual cell
      const { index, data } = e.detail;
      // New array of json items
      const values = elem.values;
    },
    false
  );
</script>
还可以进一步优化,根据值的类型返回颜色选择器、数字输入框或复选框。

Conclusion 

总结

If you want to learn more about building with Lit you can read the docs here. There is also an example on the Lit playground here.
The source for this example can be found here.
如果想了解更多关于Lit的开发内容,可以查看官方文档此处。Lit在线游乐场中也有相关示例此处.
本示例的源码可查看此处