Last active
December 13, 2024 14:03
-
-
Save fsubal/1caff830c0628a3ad359cbf712c38421 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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