Skip to content

Instantly share code, notes, and snippets.

@skysan87
Created June 29, 2025 06:04
Show Gist options
  • Select an option

  • Save skysan87/8590c603dee05870f9769cb020ba3812 to your computer and use it in GitHub Desktop.

Select an option

Save skysan87/8590c603dee05870f9769cb020ba3812 to your computer and use it in GitHub Desktop.
[Vue.js] ツリー形式のチェックボックスグループを操作するサンプル
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tree-style-checkbox-group</title>
<script type="importmap">
{
"imports": {
"Vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
</head>
<body>
<div id="app">
<div v-for="(row) in rows" :key="row.id">
<label :style="`padding-left:${row.level * 20}px;`">
<input type="checkbox" :checked="row.checked" :value="row.value" @change="handleCheck(row.id)" />
{{ row.title }}
</label>
</div>
</div>
</body>
<!-- vue.js -->
<script type="module">
import { createApp, ref, watch } from 'Vue'
/**
* @typedef {object} treeItem 一次元化したツリー要素
* @property {string} id ツリー内の識別番号
* @property {string} parentId 親要素Id
* @property {string} title 表示ラベル
* @property {string} value 値
* @property {boolean} checked チェック状態
* @property {number} level ツリーの深さ
* @property {boolean} hasChildren 子要素有無
*/
const TREE_DATA = [
{
title: 'group1',
children: [
{ title: 'title1-1', value: 'value1_1' },
{ title: 'title1-2', value: 'value1_2' }
]
},
{
title: 'group2',
children: [
{ title: 'title2-1', value: 'value2_1' },
{
title: 'sub-group-2-2', children: [
{ title: 'title2-2-1', value: 'value2_2_1' },
{ title: 'title2-2-2', value: 'value2_2_2' },
]
},
{ title: 'title2-3', value: 'value2_3' },
]
},
{
title: 'titleA', value: 'valueA'
}
]
createApp({
setup () {
/** @type {ref<treeItem[]>} */
const rows = ref([])
/**
* 再起的にツリー構造を一次元化
* @param {any[]} treeData
* @param {number} parentIds
* @returns {treeItem[]}
*/
const flattenTree = (treeData, parentIds = []) => {
if (!Array.isArray(treeData)) return []
return treeData.reduce(
/**
* @param {treeItem[]} acc
* @param {any} item
* @param {number} index
*/
(acc, item, index) => {
const hasChildren = item.children && item.children.length > 0
acc.push({
id: [...parentIds, index].join('_'),
parentId: parentIds.join('_') ?? '',
title: item.title,
value: item.value ?? '',
checked: false,
level: parentIds.length,
hasChildren: hasChildren
})
if (hasChildren) {
acc.push(...flattenTree(item.children, [...parentIds, index]))
}
return acc
}, [])
}
/**
* 再帰的に子要素を全てチェックする
* @param {treeItem} row
*/
const checkChildren = (row) => {
/** @type {treeItem[]} */
const children = rows.value.filter(r => r.parentId === row.id)
children.forEach(r => {
r.checked = true
checkChildren(r)
})
}
/**
* 再帰的に同階層がすべてチェックされていれば、親要素をチェックする
* @param {treeItem} row
*/
const checkAncestor = (row) => {
/** @type {treeItem[]} */
const samegroup = rows.value.filter(r => r.parentId === row.parentId)
if (samegroup.every(r => r.checked)) {
/** @type {treeItem} */
const parent = rows.value.find(r => r.id === row.parentId)
if (parent) {
parent.checked = true
checkAncestor(parent)
}
}
}
/**
* 再帰的に子要素を全てチェック解除する
* @param {treeItem} row
*/
const uncheckChildren = (row) => {
/** @type {treeItem[]} */
const children = rows.value.filter(r => r.parentId === row.id)
children.forEach(r => {
r.checked = false
uncheckChildren(r)
})
}
/**
* 再帰的に親要素のチェックを解除する
* @param {treeItem} row
*/
const uncheckAncestor = (row) => {
/** @type {treeItem} */
const parent = rows.value.find(r => r.id === row.parentId)
if (parent) {
parent.checked = false
uncheckAncestor(parent)
}
}
/**
* チェックイベント処理
* @param {string} id
*/
const handleCheck = (id) => {
/** @type {treeItem} */
const row = rows.value.find(row => row.id === id)
if (!row) return
row.checked = !row.checked
// チェックされた要素に合わせて、子要素と親要素も連動してチェック状態を更新する
if (row.checked) {
checkChildren(row)
checkAncestor(row)
} else {
uncheckChildren(row)
uncheckAncestor(row)
}
}
rows.value = flattenTree(TREE_DATA)
return {
rows, handleCheck
}
}
}).mount('#app')
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment