はじめに
「SvelteにハマってUIライブラリは何がいいか検討したところ、Skeletonがいい感じでした。今回はお手本を参考に作成したDataTableをいくつか改善していこうと思います。」
前提
- Node.js v22.6.0(Docker)
- SvelteKit - アプリケーション作成済み
- Skeleton - お手本を参考にDataTableを作成済み
- MySql - テストデータを登録済み
DataTableの作成は以下の記事で行いました。
※これを読んでない人でも十分理解できる内容となっております。
改善したい点
- データを渡すだけでテーブル表示するコンポーネントにしたい
- Select可能なテーブルにできるようにしたい今ここ
- Formの要素として使いやすくしたい
part1「データを渡すだけでテーブル表示するコンポーネントにしたい」は下記の記事になります。
現在Datatableコンポーネントは以下のようになっています。
/lib/components/Datatable.svelte
<script context="module" lang="ts">
export type RowData = Record<string, any>;
export type ColumnData = { name: string; key: string };
export type SearchField = { keys: string[]; placeholder: string;}
</script>
<script lang="ts">
import { DataHandler } from "@vincjo/datatables";
import Search from "./Search.svelte";
import ThSort from "./ThSort.svelte";
import ThFilter from "./ThFilter.svelte";
import Pagination from "./Pagination.svelte";
import RowCount from "./RowCount.svelte";
import RowsPerPage from "./RowsPerPage.svelte";
export let data: RowData[];
export let columns: ColumnData[];
export let searchFields: SearchField[] = [];
const handler = new DataHandler(data, {rowsPerPage: 5});
const rows = handler.getRows();
</script>
<div class="table-container space-y-4">
<header class="flex justify-between gap-4">
{#if searchFields.length > 0}
{#each searchFields as data}
<Search {handler} searchFields={data.keys} placeholder={data.placeholder} />
{/each}
{/if}
<RowsPerPage {handler} />
</header>
<table class="table table-hover table-compact table-auto w-full">
<thead>
<tr>
{#each columns as column}
<ThSort {handler} orderBy={column.key}>{column.name}</ThSort>
{/each}
</tr>
<tr>
{#each columns as column}
<ThFilter {handler} filterBy={column.key} />
{/each}
</tr>
</thead>
<tbody>
{#each $rows as row}
<tr>
{#each columns as column}
<td>{row[column.key]}</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<footer class="flex justify-between">
<RowCount {handler} />
<Pagination {handler} />
</footer>
</div>
「今は、Datatableコンポーネントにデータを渡したら、画面でいい感じに表示できているけど、そこから先は何もできていないよね。」
「今回は、行をクリックしたら、選択状態にして、コンポーネントのインポート先で選択状態の行のデータを受け取れるようにしていきたいと思います。」
2.Select可能なテーブルにできるようにしたい
行選択可能にするやり方
「Skeletonの公式でTailWindのデータテーブルの説明に行選択の方法がありました。」
「まずは、選択中の行データを保持する変数selectedRow
とデータをセットするメソッドselectRow
を用意します」
Datatable.svelte
...
<script lang="ts">
import { DataHandler } from "@vincjo/datatables";
import Search from "./Search.svelte";
import ThSort from "./ThSort.svelte";
import ThFilter from "./ThFilter.svelte";
import Pagination from "./Pagination.svelte";
import RowCount from "./RowCount.svelte";
import RowsPerPage from "./RowsPerPage.svelte";
export let data: RowData[];
export let columns: ColumnData[];
export let searchFields: SearchField[] = [];
const handler = new DataHandler(data, { rowsPerPage: 5 });
const rows = handler.getRows();
+ let selectedRow: RowData | null = null;
+ function selectRow(row: RowData) {
+ selectedRow = row;
+ }
...
</script>
「次に行オブジェクトのクリック時に、selectRow
を実行し、rowオブジェクトと、selectedRowが同じな時、`table-row-checkedクラスを付与します。」
Datatable.svlete
...
<tbody>
{#each $rows as row}
<tr
+ on:click={() => selectRow(row)}
+ class="{selectedRow === row ? "table-row-checked" : ''}"
>
{#each columns as column}
{#if !column.hidden}
<td>{row[column.key]}</td>
{/if}
{/each}
</tr>
{/each}
</tbody>
</table>
...
「見た目だけでいうと、選択中の行には、table-row-checked
というクラスを与えればいいんだね!」
インポート先で選択された行データを受け取る
「次は、Datatable.svelteのインポート先の+page.svelteでデータを表示してみましょう」
「まずは、先ほど作成したselectRow
メソッドで'selected'イベントをdispatchします。」
「子→親への値の受け渡しは、dispatchすればよかったね!!」
<script lang="ts">
...
+ import { createEventDispatcher } from 'svelte';
+ const dispatch = createEventDispatcher();
export let data: RowData[];
export let columns: ColumnData[];
export let searchFields: SearchField[] = [];
const handler = new DataHandler(data, { rowsPerPage: 5 });
const rows = handler.getRows();
let selectedRow: RowData | null = null;
function selectRow(row: RowData) {
selectedRow = row;
+ dispatch('selected',{
+ value: selectedRow
+ });
}
「次に、+page.svelteでイベントを受け取り表示します。」
<script lang="ts">
import type { UserInterface } from './userInterface.js';
import Datatable, { type ColumnData, type RowData, type SearchField } from '$lib/components/Datatable.svelte';
export let data: {userList: UserInterface[]};
let userList: UserInterface[] = [];
userList = data.userList;
const columns:ColumnData[] = [
{name:"ユーザーID", key:"user_id", hidden:true},
{name:"名前", key:"username"},
{name:"Email", key:"email"},
{name:"部署C", key:"deptC"}
];
const searchFields: SearchField[] = [
{keys:["username","email"], placeholder:"名前またはEmailで検索"},
{keys:["user_id"], placeholder:"user_idで検索"}
]
+ let selectedRow:RowData | null = null;
+ function handleSelectedRow(event: { detail: { value: RowData | null; }; }){
+ selectedRow = event.detail.value;
+ }
</script>
<h1>ユーザー一覧</h1>
<div style="width: 70%;">
<Datatable
data={userList}
columns={columns}
searchFields={searchFields}
+ on:selected={handleSelectedRow}
/>
</div>
+<div class="bg-surface-400">
+ {#if selectedRow}
+ <h3>選択された行のデータ:</h3>
+ <ul>
+ {#each columns as column}
+ <li><strong>{column.key}:</strong> {selectedRow[column.key]}</li>
+ {/each}
+ </ul>
+ {:else}
+ <p>行を選択してください。</p>
+ {/if}
</div>
実行結果
「クリックしたら、色が変わって、選択した行データの値もインポート先で受け取れてるね!!」
終わりに
いよいよコンポーネントとして使えそうな感じになってきました。
もしわからないところや、もっとこうした方がいいよ!があれば、コメントお願いします。
※基本は公式を読むのが吉です。
次回はいよいよ「3.Formの要素として使いやすくしたい」です。