はじめに
「SvelteにハマってUIライブラリは何がいいか検討したところ、Skeletonがいい感じでした。今回はお手本を参考に作成したDataTableをいくつか改善していこうと思います。」
前提
- Node.js v22.6.0(Docker)
- SvelteKit - アプリケーション作成済み
- Skeleton - お手本を参考にDataTableを作成済み
- MySql - テストデータを登録済み
DataTableの作成は以下の記事で行いました。
※これを読んでない人でも十分理解できる内容となっております。
改善したい点
- データを渡すだけでテーブル表示するコンポーネントにしたい 今ここ
- Select可能なテーブルにできるようにしたい
- Formの要素として使いやすくしたい
1.データを渡すだけでテーブル表示するコンポーネントにしたい
現在、DataTableコンポーネントは以下のようになっています。
/lib/components/Datatable.svelte
<script lang="ts">
import { DataHandler } from "@vincjo/datatables";
import type { UserInterface } from "../../routes/UserList/userInterface";
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: UserInterface[];
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">
<Search {handler} searchFields={["username","deptC"]} />
<RowsPerPage {handler} />
</header>
<table class="table table-hover table-compact table-auto w-full">
<thead>
<tr>
<ThSort {handler} orderBy="username">名前</ThSort>
<ThSort {handler} orderBy="email">Email</ThSort>
<ThSort {handler} orderBy="deptC">部署C</ThSort>
</tr>
<tr>
<ThFilter {handler} filterBy="username" />
<ThFilter {handler} filterBy="email" />
<ThFilter {handler} filterBy="deptC" />
</tr>
</thead>
<tbody>
{#each $rows as row}
<tr>
<td>{row.username}</td>
<td>{row.email}</td>
<td>{row.deptC}</td>
</tr>
{/each}
</tbody>
</table>
<footer class="flex justify-between">
<RowCount {handler} />
<Pagination {handler} />
</footer>
</div>
「export let data:UserInterface[]
としていて、
インポート先で取得したデータを使用できるようにしています。
<ThSort {handler} orderBy="username">名前</ThSort>
のように
列名は固定となっています。」
「でも、どんな型のデータでも渡せるようにして、列名も固定じゃないほうがいいよね」
「それではまず、scriptタグ部分を以下のように修正してみました」
scriptタグ部分の修正
Datatable.svelte
+ <script context="module" lang="ts">
+ export type RowData = Record<string, any>; //行データは任意のキーとそれに対応する型(any)を持つとする
+ export type ColumnData = { //列データは列名と任意のキーを持つとする
+ key: string;
+ name: string;
+ };
+ </script>
<script lang="ts">
import { DataHandler } from "@vincjo/datatables";
- import type { UserInterface } from "../../routes/UserList/userInterface";
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: UserInterface[]; //テーブルに表示するデータはUserInterface[]型しか受け取らないのを廃止
+ export let data: RowData[];
+ export let columns: ColumnData[];
const handler = new DataHandler(data, {rowsPerPage: 5});
const rows = handler.getRows();
「Datatableコンポーネントが受け取るdataを、UserInterface型以外でも
受け取れるようにしました。また、列情報も受け取るようにしました。
これで列情報のkeyを使って行データの値を取得する予定です。」
「<script context="module" lang="ts">
って何?」
「ここに含まれるコードは、コンポーネントがインスタンス化されたときではなく、モジュールが最初に評価されたときに一度だけ実行されグローバルにアクセス可能です。」
「`script lang="ts">内で定義された型はコンポーネントのインスタンスに閉じているため、他の部分から参照することができません。」
html部分の修正
Datatable.svelte
<div class="table-container space-y-4">
<header class="flex justify-between gap-4">
- <Search {handler} searchFields={["username","deptC"]} />
+ <Search {handler} searchFields={[columns[0].key]} /> ←Searchフィールドはとりあえず、1列目のデータを検索するように指定。
<RowsPerPage {handler} />
</header>
<table class="table table-hover table-compact table-auto w-full">
<thead>
<tr>
- <ThSort {handler} orderBy="username">名前</ThSort>
- <ThSort {handler} orderBy="email">Email</ThSort>
- <ThSort {handler} orderBy="deptC">部署C</ThSort>
+ {#each columns as column}
+ <ThSort {handler} orderBy={column.key}>{column.name}</ThSort>
+ {/each}
</tr>
<tr>
- <ThFilter {handler} filterBy="username" />
- <ThFilter {handler} filterBy="email" />
- <ThFilter {handler} filterBy="deptC" />
+ {#each columns as column}
+ <ThFilter {handler} filterBy={column.key} />
+ {/each}
</tr>
</thead>
<tbody>
{#each $rows as row}
<tr>
- <td>{row.username}</td>
- <td>{row.email}</td>
- <td>{row.deptC}</td>
+ {#each columns as column}
+ <td>{row[column.key]}</td> ←与えたdataで列データと一致するデータを表示
+ {/each}
</tr>
{/each}
</tbody>
</table>
<footer class="flex justify-between">
<RowCount {handler} />
<Pagination {handler} />
</footer>
</div>
「列データを渡すようにすれば、dataの中でテーブルに表示したい値を指定できるね」
インポート先の修正
+page.svelte
<script lang="ts">
import type { UserInterface } from './userInterface.js';
- import Datatable from '$lib/components/Datatable.svelte';
+ import Datatable, { type ColumnData } from '$lib/components/Datatable.svelte';
export let data: {userList: UserInterface[]};
let userList: UserInterface[] = [];
userList = data.userList;
+ let columns:ColumnData[] = [
+ {name:"名前", key:"username"},
+ {name:"Email", key:"email"},
+ {name:"部署C", key:"deptC"}
+ ];
</script>
<h1>ユーザー一覧</h1>
<div style="width: 70%;">
<Datatable
data={userList}
+ columns={columns}
/>
</div>
「ColumnDataをインポート先で作成して、Datatableコンポーネントに渡しています。」
「Searchコンポーネントで表示している列を検索するなら、Filterコンポーネントでよくない?」
「......そうですね。気が向いたら直します。」
Searchコンポーネント修正(2024/8/20)
「Searchコンポーネントは、好きなだけ追加表示できるようにしたいと思います。」
「placeholderも指定できるようにしました。」
Search.svelte
<script lang="ts">
import type { DataHandler, Field } from '@vincjo/datatables';
export let handler: DataHandler;
export let searchFields: Field<any>[];
+ export let placeholder = "Search...";
let value: string;
</script>
<input
class="input sm:w-64 w-36"
type="search"
- placeholder="Search..."
+ placeholder={placeholder}
bind:value
on:input={() => handler.search(value, searchFields)}
/>
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;} ←keysは一つのSearchComponentが検索する対象の項目
</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">
- <Search {handler} searchFields={[columns[0].key]} />
+ {#if searchFields.length > 0} ←searchFieldsを指定しなかった場合は、Searchコンポーネントを表示しません。
+ {#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>
「これで指定したsearchFieldsの数だけ検索フィールドが表示されるね!」
「それでは、+page.svelteを修正して、実際に使ってみましょう」
+page.svelte
<script lang="ts">
import type { UserInterface } from './userInterface.js';
- import Datatable, { type ColumnData } from '$lib/components/Datatable.svelte';
+ import Datatable, { type ColumnData, type SearchField } from '$lib/components/Datatable.svelte';
export let data: {userList: UserInterface[]};
let userList: UserInterface[] = [];
userList = data.userList;
let columns:ColumnData[] = [
{name:"名前", key:"username"},
{name:"Email", key:"email"},
{name:"部署C", key:"deptC"}
];
+ let searchFields: SearchField[] = [
+ {keys:["username","email"], placeholder:"名前またはEmailで検索"},
+ {keys:["user_id"], placeholder:"user_idで検索"}
+ ]
</script>
<h1>ユーザー一覧</h1>
<div style="width: 70%;">
<Datatable
data={userList}
columns={columns}
+ searchFields={searchFields}
/>
</div>
「検索フィールドを二つ表示するように指定してみました。」
「一つ目は、"名前"or"Email"で検索し、二つ目は、"user_id"で検索します。」
「"user_id"はテーブルには表示していない列だけど、dataには存在する項目だったから、これで検索することができるようになったね!」
「以下が実行結果になります。」
終わりに
まだまだ、改善の余地はありますが、少しは使いやすくなったと思います。
- データを渡すだけでテーブル表示するコンポーネントにしたい
- Select可能なテーブルにできるようにしたい 次回はここ
- Formの要素として使いやすくしたい
参考になったり、ご指摘等あれば、いいね👍コメントよろしくお願いします。