Skip to content

Instantly share code, notes, and snippets.

@skysan87
Created November 28, 2024 15:56
Show Gist options
  • Save skysan87/2c4dcb513031f8c43a5b62332e9a87be to your computer and use it in GitHub Desktop.
Save skysan87/2c4dcb513031f8c43a5b62332e9a87be to your computer and use it in GitHub Desktop.
動的にテーブルヘッダーを作成
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>動的にテーブルヘッダーを作成</title>
<script type="importmap">
{
"imports": {
"Vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
</head>
<!--
なお、rowspanと行の組み合わせ次第では、Excelの結合を表現できない。
例:1-2行目の結合と2-3行目の結合が被るとき
-->
<body>
<div id="app">
<table>
<caption>見本</caption>
<thead>
<tr>
<th rowspan="4" style="background-color:aqua;">1</th>
<th colspan="3" rowspan="2" style="background-color: green;">2</th>
<th rowspan="4" style="background-color:orange;">3</th>
<th colspan="5" style="background-color:red;">4</th>
</tr>
<tr>
<th colspan="4" style="background-color: violet;">4-1</th>
<th rowspan="3" style="background-color: pink;">4-2</th>
</tr>
<tr>
<th rowspan="2" style="background-color: greenyellow;">2-1</th>
<th colspan="2">2-2</th>
<th rowspan="2">4-1-1</th>
<th rowspan="2">4-1-2</th>
<th colspan="2">4-1-3</th>
</tr>
<tr>
<th>2-2-1</th>
<th>2-2-2</th>
<th>4-1-3-1</th>
<th>4-1-3-2</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div style="padding: 10px;"></div>
<table>
<caption>headerを動的に生成 見本</caption>
<thead>
<tr v-for="(row,index) in converted" :key="index">
<th v-for="(col, i) in row" :key="i" :rowspan="col.rowspan" :colspan="col.colspan">
{{ col.title }}
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<table>
<caption>headerを動的に生成 動作確認</caption>
<thead>
<tr v-for="(row,index) in map" :key="index">
<th v-for="(col, i) in row" :key="i" :rowspan="col.rowspan" :colspan="col.colspan">
{{ col.title }}
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</body>
<!-- vue.js -->
<script type="module">
import { createApp, ref, watch } from 'Vue'
createApp({
setup () {
// 変換前の定義
const data = [
{
title: '1'
},
{
title: '2',
rowspan: 2,
child: [
{
title: '2-1'
},
{
title: '2-2',
child: [
{ title: '2-2-1' },
{ title: '2-2-2' },
]
}
]
},
{
title: '3',
},
{
title: '4',
child: [
{
title: '4-1',
child: [
{ title: '4-1-1' },
{ title: '4-1-2' },
{
title: '4-1-3',
child: [
{ title: '4-1-3-1' },
{ title: '4-1-3-2' }
]
},
]
},
{
title: '4-2'
},
]
}
]
// 変換後の期待値
const converted = [
[
{ title: '1', rowspan: 4, colspan: 1 },
{ title: '2', rowspan: 2, colspan: 3 },
{ title: '3', rowspan: 4, colspan: 1 },
{ title: '4', rowspan: 1, colspan: 5 }
],
[
{ title: '4-1', rowspan: 1, colspan: 4 },
{ title: '4-2', rowspan: 3, colspan: 1 }
],
[
{ title: '2-1', rowspan: 2, colspan: 1 },
{ title: '2-2', rowspan: 1, colspan: 2 },
{ title: '4-1-1', rowspan: 2, colspan: 1 },
{ title: '4-1-2', rowspan: 2, colspan: 1 },
{ title: '4-1-3', rowspan: 1, colspan: 2 },
],
[
{ title: '2-2-1', rowspan: 1, colspan: 1 },
{ title: '2-2-2', rowspan: 1, colspan: 1 },
{ title: '4-1-3-1', rowspan: 1, colspan: 1 },
{ title: '4-1-3-2', rowspan: 1, colspan: 1 },
]
]
// dataのネストの深さを調べる(最深部->rowspan)
const getNestLevel = (obj) => {
const getMaxDepth = (d) => {
let maxDepth = 1
// TODO: check 最深部のカウント
if (d.rowspan) {
maxDepth = d.rowspan
}
if (d.child) {
d.child.forEach(child => {
const depth = 1 + getMaxDepth(child)
maxDepth = Math.max(maxDepth, depth)
})
}
return maxDepth
}
const results = obj.map(d => getMaxDepth(d))
return Math.max(...results)
}
// 全子要素(childプロパティ)の数をカウント
const countChildren = (obj) => {
let count = 0
obj.child.forEach(child => {
if (child.child) {
count += countChildren(child)
} else {
count++
}
})
return count
}
// rowspanとcolspanの計算
const calcRowspanAndColspan = (_data) => {
// jsonのネストの最深部のレベル
const deepestLevel = getNestLevel(_data)
const rows = [[]]
const calc = (d, level) => {
if (!rows[level]) {
rows[level] = []
}
const hasChild = d.child
let offset = 0
// rowspan: 縦方向のセル結合(子要素あり:1,子要素なし:ネストの最深-ネストの現在の深さ)
let rowspan
if (d.rowspan) {
rowspan = d.rowspan
offset = d.rowspan - 1
} else if (hasChild) {
rowspan = 1
} else {
rowspan = deepestLevel - level
}
// colspan: 横方向のセル結合(子要素あり:全個要素の数,子要素なし:1)
const colspan = hasChild ? countChildren(d) : 1
// jsonのネストの深さ=TR要素のindexに変換
rows[level].push({ title: d.title, rowspan, colspan })
if (hasChild) {
d.child.forEach((child, i) => calc(child, level + offset + 1))
}
}
_data.forEach((d, i) => calc(d, 0))
return rows
}
const map = calcRowspanAndColspan(data)
// 実際に列となる項目を取得する
const getColumns = (_data) => {
const columns = []
const loop = (d) => {
if (d.child) {
d.child.forEach(c => loop(c))
} else {
columns.push(d.title)
}
}
_data.forEach(d => loop(d))
return columns
}
return {
converted,
map
}
}
}).mount('#app')
</script>
<style>
table {
border-collapse: collapse;
}
th,
td {
border: 1px solid black;
padding: 10px;
text-align: center;
}
</style>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment