0
0

SvelteKit Skeleton Datatable使いやすくしてみた ~Selectableにする~

Posted at

はじめに

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

前提

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

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

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

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

改善したい点

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

:baby:「今は、Datatableコンポーネントにデータを渡したら、画面でいい感じに表示できているけど、そこから先は何もできていないよね。」

:robot:「今回は、行をクリックしたら、選択状態にして、コンポーネントのインポート先で選択状態の行のデータを受け取れるようにしていきたいと思います。」

2.Select可能なテーブルにできるようにしたい

行選択可能にするやり方

:robot: 「Skeletonの公式でTailWindのデータテーブルの説明に行選択の方法がありました。」

:robot:「まずは、選択中の行データを保持する変数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>

:robot:「次に行オブジェクトのクリック時に、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>
...

:baby:「見た目だけでいうと、選択中の行には、table-row-checkedというクラスを与えればいいんだね!」

インポート先で選択された行データを受け取る

:robot:「次は、Datatable.svelteのインポート先の+page.svelteでデータを表示してみましょう」
  「まずは、先ほど作成したselectRowメソッドで'selected'イベントをdispatchします。」

:baby: 「子→親への値の受け渡しは、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
+       });
    }

:robot:「次に、+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>

実行結果

画面収録 2024-09-09 0.gif

:baby:「クリックしたら、色が変わって、選択した行データの値もインポート先で受け取れてるね!!」

終わりに

いよいよコンポーネントとして使えそうな感じになってきました。
もしわからないところや、もっとこうした方がいいよ!があれば、コメントお願いします。
※基本は公式を読むのが吉です。

次回はいよいよ「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