ゴール
使用イメージ
<y-scroll-data-table
:headers="headers"
:items="items"
:items-per-display="5"
>
<template v-slot:item.color="{ item }">
<v-icon :color="item.color" class="mr-3">mdi-circle</v-icon>
{{ item.color }}
</template>
</y-scroll-data-table>
作業の流れ
- 新しく作成したコンポーネント内で v-data-table を使用し、一部のプロパティの値を固定する
- 6行目以降をスクロール可能にする
- items-per-display プロパティを用意し、受け取った数値以降の行をスクロール可能にする
1. 新しく作成したコンポーネント内で v-data-table を使用し、一部のプロパティの値を固定する
下部のフッターを非表示にするためにhide-default-footer を true に、
アイテムを全てを表示するために items-per-page を items の数と同じにする必要があります。
v-data-table API
template タグを使用し下記のようにシンプルに記述したいところですが、slot が機能しません。
<!-- YScrollDataTable.vue -->
<template>
<v-data-table
v-bind="$attrs"
:items-per-page="$attrs.items.length"
hide-default-footer
v-on="$listeners"
/>
</template>
template タグは削除し、以下のように描画関数を使用することで解決できます。
描画関数とJSX — Vue.js
<!-- YScrollDataTable.vue -->
<script>
export default {
render(createElement) {
return createElement('v-data-table', {
props: {
...this.$attrs,
itemsPerPage: this.$attrs.items.length,
hideDefaultFooter: true
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
})
}
}
</script>
2. 6行目以降をスクロール可能にする
上記のように全てのアイテムを表示することができたため、次は5行分のみを表示し、6行目以降はスクロールする形に修正します。
デザインを調節するために、まずはクラス名を追加します。
<script>
export default {
render(createElement) {
return createElement('v-data-table', {
// ↓↓追加↓↓
class: {
'yScrollDataTable': true,
'yScrollDataTable--scrollable': this.$attrs.items.length > 5
},
// ↑↑追加↑↑
props: {
...this.$attrs,
itemsPerPage: this.$attrs.items.length,
hideDefaultFooter: true
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
})
}
}
</script>
次にデザインを調節します。
scoped 付きの style タグ内で子コンポーネントのスタイルを調節するために deep selector を使用しています。
Deep Selectors | Vue Loader
<style lang="scss" scoped>
@import '~vuetify/src/styles/styles.sass';
@import '~vuetify/src/components/VDataTable/_variables.scss';
.yScrollDataTable {
$row-height: $data-table-regular-row-height;
::v-deep {
table, thead, tbody, th, td {
display: block;
width: 100% !important;
}
tr {
display: flex;
justify-content: space-between;
align-items: center;
}
th, td {
display: flex;
align-items: center;
height: $row-height !important;
}
}
&--scrollable ::v-deep tbody {
overflow-y: scroll;
max-height: $row-height * 5;
// スクロールバー全体
&::-webkit-scrollbar {
width: 8px;
}
// スクロールバーの軌道
&::-webkit-scrollbar-track {
border-radius: 8px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
}
// スクロールバーの動く部分
&::-webkit-scrollbar-thumb {
background-color: map-get($grey, 'base');
border-radius: 8px;
}
}
}
</style>
3. items-per-display プロパティを用意し、受け取った数値以降の行をスクロール可能にする
6行目以降をスクロールで表示する形に修正できたため、次はコンポーネントを使用する側が表示する行数を指定できるようにします。
まずは itemsPerDisplay プロパティを定義します。
props: {
itemsPerDisplay: {
type: Number,
required: false,
default: 5
}
}
次に、用意した itemsPerDisplay プロパティを元に computed 内でCSS変数を作成します。
CSS カスタムプロパティ (変数) の使用 – CSS: カスケーディングスタイルシート | MDN
computed: {
cssVars() {
return {
'--itemsPerDisplay': this.itemsPerDisplay
}
}
}
作成したCSS変数を、描画関数内で style にバインドします。<v-data-table :style="cssVars">
と同じことをしています。
render(createElement) {
return createElement('v-data-table', {
class: {
'yScrollDataTable': true,
'yScrollDataTable--scrollable': this.$attrs.items.length > 5
},
// ↓↓追加↓↓
style: this.cssVars,
// ↑↑追加↑↑
props: {
...this.$attrs,
itemsPerPage: this.$attrs.items.length,
hideDefaultFooter: true
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
})
},
受け取ったCSS変数を利用して tbody に高さを与えます。
&--scrollable ::v-deep tbody {
overflow-y: scroll;
// From
max-height: $row-height * 5;
// To
max-height: calc(#{$row-height} * var(--itemsPerDisplay));
}
最後にyScrollDataTable–scrollable クラスの設定条件を修正します。
class: {
'yScrollDataTable': true,
// From
'yScrollDataTable--scrollable': this.$attrs.items.length > 5
// To
'yScrollDataTable--scrollable': this.$attrs.items.length > this.itemsPerDisplay
}
これで、以下のようにコンポーネントを使用する側が好きな行数を指定できるようになりました。
<y-scroll-data-table
:headers="..."
:items="..."
:items-per-display="5"
>
...
</y-scroll-data-table>
以上で完了です。
最後にソースコード全体を記載します。
index.vue (コンポーネントを使用する側)
<template>
<v-container>
<y-scroll-data-table
:headers="headers"
:items="items"
:items-per-display="5"
>
<template v-slot:item.color="{ item }">
<v-icon :color="item.color" class="mr-3">mdi-circle</v-icon>
{{ item.color }}
</template>
</y-scroll-data-table>
</v-container>
</template>
<script>
import YScrollDataTable from '@/components/YScrollDataTable'
export default {
components: {
YScrollDataTable
},
data: () => ({
headers: [{ text: "Colors", value: "color", align: "start" }],
items: [
{ color: "red" },
{ color: "pink" },
{ color: "purple" },
{ color: "deep-purple" },
{ color: "indigo" },
{ color: "blue" },
{ color: "light-blue" },
{ color: "cyan" },
{ color: "teal" },
{ color: "green" },
]
})
}
</script>
YScrollDataTable.vue (この記事で作成したコンポーネント)
<script>
export default {
props: {
itemsPerDisplay: {
type: Number,
required: false,
default: 5
}
},
render(createElement) {
return createElement('v-data-table', {
class: {
'yScrollDataTable': true,
'yScrollDataTable--scrollable': this.$attrs.items.length > this.itemsPerDisplay
},
style: this.cssVars,
props: {
...this.$attrs,
itemsPerPage: this.$attrs.items.length,
hideDefaultFooter: true
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
})
},
computed: {
cssVars() {
return {
'--itemsPerDisplay': this.itemsPerDisplay
}
}
}
}
</script>
<style lang="scss" scoped>
@import '~vuetify/src/styles/styles.sass';
@import '~vuetify/src/components/VDataTable/_variables.scss';
.yScrollDataTable {
$this: &;
$row-height: $data-table-regular-row-height;
::v-deep {
table, thead, tbody, th, td {
display: block;
width: 100% !important;
}
tr {
display: flex;
justify-content: space-between;
align-items: center;
}
th, td {
display: flex;
align-items: center;
height: $row-height !important;
}
}
&--scrollable ::v-deep tbody {
overflow-y: scroll;
max-height: calc(#{$row-height} * var(--itemsPerDisplay));
// スクロールバー全体
&::-webkit-scrollbar {
width: 8px;
}
// スクロールバーの軌道
&::-webkit-scrollbar-track {
border-radius: 8px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
}
// スクロールバーの動く部分
&::-webkit-scrollbar-thumb {
background-color: map-get($grey, 'base');
border-radius: 8px;
}
}
}
</style>
ありがとうございました。