0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SvelteKit SkeletonでDataTableを使いやすくしてみた ~データを渡すだけでテーブル表示~

Last updated at Posted at 2024-08-18

はじめに

:baby: 「SvelteにハマってUIライブラリは何がいいか検討したところ、Skeletonがいい感じでした。今回はお手本を参考に作成したDataTableをいくつか改善していこうと思います。」

前提

  • Node.js v22.6.0(Docker)
  • SvelteKit - アプリケーション作成済み
  • Skeleton - お手本を参考にDataTableを作成済み
  • MySql - テストデータを登録済み

DataTableの作成は以下の記事で行いました。

※これを読んでない人でも十分理解できる内容となっております。

画面収録 2024-08-14 23.gif

改善したい点

  1. データを渡すだけでテーブル表示するコンポーネントにしたい :point_left_tone3:今ここ
  2. Select可能なテーブルにできるようにしたい
  3. 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>

:robot:export let data:UserInterface[]としていて、
  インポート先で取得したデータを使用できるようにしています。
  <ThSort {handler} orderBy="username">名前</ThSort>のように
  列名は固定となっています。」

:baby:「でも、どんな型のデータでも渡せるようにして、列名も固定じゃないほうがいいよね」

:robot:「それではまず、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();

:robot:「Datatableコンポーネントが受け取るdataを、UserInterface型以外でも
受け取れるようにしました。また、列情報も受け取るようにしました。
これで列情報のkeyを使って行データの値を取得する予定です。」

:baby:<script context="module" lang="ts">って何?」
:robot: 「ここに含まれるコードは、コンポーネントがインスタンス化されたときではなく、モジュールが最初に評価されたときに一度だけ実行されグローバルにアクセス可能です。」
「`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>

:baby:「列データを渡すようにすれば、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>

:robot:「ColumnDataをインポート先で作成して、Datatableコンポーネントに渡しています。」

:baby:「Searchコンポーネントで表示している列を検索するなら、Filterコンポーネントでよくない?」

:robot:「......そうですね。気が向いたら直します。」

Searchコンポーネント修正(2024/8/20)

:robot:「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>

:baby:「これで指定したsearchFieldsの数だけ検索フィールドが表示されるね!」

:robot:「それでは、+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>

:robot:「検索フィールドを二つ表示するように指定してみました。」
  「一つ目は、"名前"or"Email"で検索し、二つ目は、"user_id"で検索します。」

:baby:「"user_id"はテーブルには表示していない列だけど、dataには存在する項目だったから、これで検索することができるようになったね!」

:robot: 「以下が実行結果になります。」

画面収録 2024-08-20 23.gif

終わりに

まだまだ、改善の余地はありますが、少しは使いやすくなったと思います。

  1. データを渡すだけでテーブル表示するコンポーネントにしたい
  2. Select可能なテーブルにできるようにしたい :point_left_tone3:次回はここ
  3. Formの要素として使いやすくしたい

参考になったり、ご指摘等あれば、いいね👍コメントよろしくお願いします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?