Skip to content

Instantly share code, notes, and snippets.

@fsubal
Last active December 13, 2024 14:03
Show Gist options
  • Save fsubal/1caff830c0628a3ad359cbf712c38421 to your computer and use it in GitHub Desktop.
Save fsubal/1caff830c0628a3ad359cbf712c38421 to your computer and use it in GitHub Desktop.
import Papa from 'papaparse'
class Csv {
constructor(readonly header: string[], readonly rows: string[][]) {}
static parse(csv: string) {
const [header, ...rows] = Papa.parse(csv.trim(), { header: false })
return new this(header, rows)
}
get columnsCount() {
return this.header.length
}
write(x: number, y: number, value: string) {
this.rows[y][x] = value
}
addColumn(name: string) {
this.header.push(name)
for (const row of this.rows) {
row.push('')
}
}
addRow() {
this.rows.push(
Array.from({ length: this.columnsCount }, () => '')
)
}
removeColumn(x: number) {
this.header.splice(x, 1)
for (const row of this.rows) {
row.splice(x, 1)
}
}
removeRow(y: number) {
this.rows.splice(y, 1)
}
toFormData() {
const formData = new FormData()
for (let y = 0; y < this.rows.length; y++) {
for (let x = 0; x < this.header.length; x++) {
formData.append(this.header[x], this.rows[y][x])
}
}
return formData
}
toTable() {
return new CsvTable(this)
}
}
class CsvTable {
constructor(readonly csv: Csv) {}
get headers() {
const thead = document.createElement('thead')
const headRow = document.createElement('tr')
for (const header of this.csv.header) {
const th = document.createElement('th')
th.innerText = header
headRow.appendChild(th)
}
thead.appendChild(headRow)
return thead
}
get body() {
const tbody = document.createElement('tbody')
for (let y = 0; y < this.csv.rows.length; y++) {
const row = this.csv.rows[y]
const bodyRow = document.createElement('tr')
for (let x = 0; x < row.length; x++) {
const cell = row[x]
const td = document.createElement('td')
td.innerText = cell
td.dataset.x = x.toString()
td.dataset.y = y.toString()
bodyRow.appendChild(td)
}
tbody.appendChild(bodyRow)
}
return tbody
}
render() {
const table = document.createElement('table')
table.appendChild(this.headers)
table.appendChild(this.body)
return table
}
get innerHTML() {
return this.render().innerHTML
}
}
/**
* @example
* ```html
* <csv-editor method="post" action="/items">
* id,name,price
* 1,りんご,200
* 2,バナナ,150
* </csv-editor>
* ```
*/
export class CsvEditor extends HTMLElement {
static {
if (typeof customElements !== 'undefined') {
customElements.define('csv-editor', this)
}
}
shadowRoot!: ShadowRoot
value: Csv
constructor() {
super()
this.attachShadow({ mode: 'closed' })
}
get method(): HTMLFormElement['method'] | null {
return this.getAttribute('method')
}
set method(value: HTMLFormElement['method']) {
this.setAttribute('method', value)
}
get action(): HTMLFormElement['action'] | null {
return this.getAttribute('action')
}
set action(value: HTMLFormElement['action']) {
this.setAttribute('action', value)
}
connectedCallback() {
this.value = Csv.parse(this.getAttribute('value') ?? '')
this.shadowRoot.innerHTML = this.value.toTable().innerHTML
this.shadowRoot.addEventListener('change', this.#handleChange)
}
disconnectedCallback() {
this.shadowRoot.removeEventListener('change', this.#handleChange)
}
#handleChange = (e: Event) => {
if (e.target instanceof HTMLInputElement) {
const { x, y } = e.target.dataset
this.value.write(parseInt(x!), parseInt(y!), e.target.value)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment