SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

メシウスの高速・軽量なJavaScript UIライブラリ「Wijmo」活用(AD)

Server Actionsと「Wijmo」でCRUD処理可能なデータグリッドを手軽に実装しよう

 OSSのサプライチェーンリスクが意識される昨今、出自が明確なライブラリへの関心が高まっています。今回は、国内企業が開発しているUIライブラリとして、メシウス(旧グレープシティ)のJavaScriptライブラリ「Wijmo」をNext.jsと組み合わせて解説します。

対象読者

  • Next.jsのServer ActionsとWijmoの連携方法を学びたい開発者
  • 国産の商用UIライブラリに魅力を感じている開発者

前提環境

 筆者の検証環境は以下の通りです。

  • macOS Tahoe 16.0.1
  • Node.js 25.2.0
  • npm 11.6.2
  • Next.js 15.4.10
  • React 19.2.0
  • Prisma 6.19.0
  • MySQL 8.0
  • Wijmo 5.20252.42

企業製UIライブラリという選択肢

 オープンソースライブラリには、メンテナンス停止リスクや悪意のあるコードの混入リスク、依存関係の複雑さといった課題があります。一方、商用ライブラリは継続的なサポート、安定性、長期運用の観点で安心感があります。

 
Wijmo」は、メシウス(旧グレープシティ)が開発するJavaScriptライブラリです。FlexGrid、Chart、Input系コントロールなど豊富なUIコンポーネントを提供し、React、Angular、Vue.jsなど主要フレームワークに対応しています。特にFlexGridはExcel風の操作感と大量データ対応、ソート・フィルター機能が強みです。日本語ドキュメント・サポートも充実しています。

 本記事では、Next.js App RouterのServer ActionsとWijmoの連携方法、FlexGridを使った実践的なCRUDアプリケーションの実装、Server ComponentとClient Componentの使い分けを学びます。

本記事で作成するアプリケーション

 本記事では、商品注文データを管理するCRUDアプリケーションを実装します。WijmoのInputNumberやInputDateコントロールを使った登録フォーム、FlexGridによるデータ一覧表示とインライン編集、ソート・フィルター機能、削除ボタンを備え、すべての機能がServer Actionsを通じてサーバーサイドと連携します(図1)。

図1:完成したアプリケーション
図1:完成したアプリケーション

Next.jsとServer Actions

 Server Actionsは、APIエンドポイント不要でシンプルな実装が可能なNext.jsのデータ送信機能です。TypeScriptとの相性が良く型安全性が向上し、Server ComponentsやClient Componentsとの自然な連携ができます。従来のAPI Routesでは/api/xxxエンドポイントを定義してfetchで呼び出す必要がありましたが、Server Actionsでは関数を直接呼び出すだけで済みます。

 'use server'ディレクティブをファイルの先頭に配置すると、ファイル内の全関数がServer Actionsになります(リスト1)。

[リスト1]Server Actions
"use server";

export async function createOrder(formData: FormData) {
  // サーバーサイドで実行される処理
}

 この例では、クライアントから直接createOrder関数を呼び出しつつ、実際に関数が実行されるのはサーバー側、という動きになります。

プロジェクトのセットアップ

 まず、Next.jsプロジェクトを作成します。以下のコマンドを実行し、TypeScript、ESLint、Tailwind CSS、App Routerを有効にする推奨設定を選択します(リスト2)。

[リスト2]Next.jsプロジェクト作成
npx create-next-app@latest wijmo-serveractions-app
cd wijmo-serveractions-app

 プロジェクトが作成されたら、npm run devで開発サーバーを起動し、正常に動作することを確認します(図2)。

図2:Next.jsが動いた
図2:Next.jsが動いた

 確認できたら次へ進みましょう。

MySQLとPrismaの設定

 次に、データベース環境を構築します。今回はMySQLをPrismaから操作することにします。読者各位の慣れた方法でMySQLを起動して、PrismaでORMを設定しましょう(リスト3)。

[リスト3]Prismaのインストールと初期化
# Prismaのインストール
npm install prisma --save-dev
npm install @prisma/client

# Prisma初期化(MySQL用)
npx prisma init --datasource-provider mysql

 Prismaの初期化後、.envファイルに接続設定を記載します(リスト4)。

[リスト4].env
# 各位の環境に合わせたMySQLの接続先情報を書いてください
DATABASE_URL="mysql://root:rootpassword@localhost:3306/wijmo_db"

 次は、prisma/schema.prismaでサンプルデータ格納用のOrderDataモデルを定義します(リスト5)。

[リスト5]prisma/schema.prisma
# 略
model OrderData {
  id        Int      @id @default(autoincrement())
  product   String
  price     Int
  quantity  Int
  orderdate DateTime
}

 定義できたら、マイグレーションとシードを実行します。シード(seed)処理とは、データベースに初期データを投入する処理のことで、開発やテストに必要なサンプルデータを自動的に準備できます。今回のシード処理はprisma/seed.tsに定義済みです。詳細はダウンロードサンプルのprisma/seed.tsを参照してください(リスト6)。

[リスト6]マイグレーションとシード実行
npx prisma migrate dev --name init
npx prisma db seed

 これでデータベースにテーブルが作成され、サンプルデータが投入されます。npx prisma studioで投入されたデータを確認できます(図3)。

図3:データがあることを確認
図3:データがあることを確認

 シード処理で入れたデータが入っていますね。

Wijmoのインストールとライセンス設定

 最後に、Wijmoパッケージをインストールします。@mescius/wijmo.react.allで必要なコンポーネントが一括インストールされます(リスト7)。

[リスト7]Wijmoインストール
npm install @mescius/wijmo.react.all

 ライセンスキーは.envファイルにNEXT_PUBLIC_プレフィックスを付けて設定します。このプレフィックスにより、Client Componentからアクセス可能になります(リスト8)。

[リスト8].env
DATABASE_URL=...
NEXT_PUBLIC_WIJMO_LICENSE_KEY="your-license-key"

 ライセンスキーを設定したら、これを読み込んで適用するコンポーネントを作成します。見た目を持たないClient Componentとして定義し、setLicenseKey()でライセンスを有効化します(リスト9)。

[リスト9]components/WijmoLicense.tsx
"use client";
import { setLicenseKey } from "@mescius/wijmo";

const licenseKey = process.env.NEXT_PUBLIC_WIJMO_LICENSE_KEY;
if (licenseKey) {
  setLicenseKey(licenseKey);
}

export default function WijmoLicense() {
  return null;
}

 このコンポーネントをレイアウトファイルからインポートし、全ページで読み込まれるようにします(リスト10)。

[リスト10]app/layout.tsx
import WijmoLicense from "@/components/WijmoLicense";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <WijmoLicense />
        {children}
      </body>
    </html>
  );
}

 これでセットアップは完了です。

【基本編】Input系コントロールでデータ登録

 このセクションでは、WijmoのInputNumberやInputDateコントロールを使ってフォームを作成し、Server Actionsでデータを登録する方法を解説します。

Server Actionsの作成

 Server Actionsは'use server'ディレクティブを付けたファイルに定義します。FormDataから値を取得し、バリデーション後にPrismaでデータを作成します。処理完了後はrevalidatePath('/')でキャッシュを更新し、ページを再描画します(リスト11)。

[リスト11]actions/orderActions.ts(createOrder)
"use server";
import { revalidatePath } from "next/cache";
import prisma from "@/lib/prisma";

export async function createOrder(prevState: ActionState, formData: FormData) {
  const product = formData.get("product") as string;
  const price = parseInt(formData.get("price") as string, 10);
  // ...バリデーションとDB処理
  await prisma.orderData.create({ data: { product, price, quantity, orderdate } });
  revalidatePath("/");
  return { success: true, message: "データを登録しました" };
}

 このServer Actionsは、フォームから送信されたデータを受け取り、Prismaを使ってデータベースに保存します。処理完了後はrevalidatePathでページのキャッシュを無効化し、最新データを表示します。

フォームコンポーネント(Wijmo Input系コントロール)

 Wijmoのコントロールは標準のHTML inputではないため、useStateで値を保持し、submit時にFormDataに追加する必要があります。日本語カルチャをインポートすることで、数値や日付の表示が日本語対応になります(リスト12)。

[リスト12]components/OrderForm.tsx
"use client";
import { useActionState, useState } from "react";
import { createOrder, type ActionState } from "@/actions/orderActions";
import "@mescius/wijmo.cultures/wijmo.culture.ja";
import { InputNumber, InputDate } from "@mescius/wijmo.react.input";

// initialStateは省略

export default function OrderForm() {
  // (1) useActionState: Server Actionsの状態管理(React 19)
  const [state, formAction, isPending] = useActionState(createOrder, initialState);
  // Wijmoコントロールの値をReactで管理
  const [price, setPrice] = useState<number>(0);

  const handleSubmit = (formData: FormData) => {
    // Wijmoの値をFormDataに追加
    formData.set("price", price.toString());
    formAction(formData);
  };

  return (
    <form action={handleSubmit}>
      {/* Wijmo InputNumberコントロール */}
      <InputNumber value={price} valueChanged={(s) => setPrice(s.value ?? 0)} />
      {/* 送信中はボタンを無効化 */}
      <button disabled={isPending}>{isPending ? "登録中..." : "登録"}</button>
    </form>
  );
}

 (1)のuseActionStateはReact 19で導入された新しいフックで、Server Actionsの実行状態と結果を管理します。第1引数にServer Actions関数、第2引数に初期状態を渡すと、現在の状態(state)、アクション実行関数(formAction)、実行中フラグ(isPending)の3つを返します。これにより、フォーム送信中のローディング表示やエラーハンドリングが簡潔に実装できます。

 上記のコードを実装すると、WijmoのInputNumberコントロールを使った入力フォームが表示されます。数値入力時には自動的に3桁区切りのフォーマットが適用され、ユーザーは直感的にデータを入力できます(図4)。

図4:フォーム登録画面
図4:フォーム登録画面

 フォームに値を入力して登録ボタンをクリックすると、Server Actionsが実行され、データベースに保存されます。

【応用編】FlexGridでCRUD処理

 このセクションでは、FlexGridを使ってデータの表示、ソート・フィルター、インライン編集、削除機能を実装します。

データの表示(READ)とソート・フィルター

 Server Componentで直接Prismaを呼び出してデータを取得し、Client ComponentのFlexGridにpropsで渡します。FlexGridはデフォルトでカラムヘッダークリックによるソートが可能です。FlexGridFilterを子コンポーネントとして配置するだけでフィルター機能が有効化されます。formatプロパティで数値(n0)や日付(yyyy/MM/dd)のフォーマットを指定できます(リスト13)。

[リスト13]components/OrderGrid.tsx(FlexGrid基本構造)
"use client";
import { FlexGrid, FlexGridColumn } from "@mescius/wijmo.react.grid";
import { FlexGridFilter } from "@mescius/wijmo.react.grid.filter";

export default function OrderGrid({ orders }: Props) {
  return (
    <FlexGrid itemsSource={orders}>
      <FlexGridFilter />
      <FlexGridColumn binding="id" header="ID" isReadOnly={true} />
      <FlexGridColumn binding="product" header="商品名" />
      <FlexGridColumn binding="price" header="価格" format="n0" />
      {/* ...その他のカラム */}
    </FlexGrid>
  );
}

 このコードにより、データがFlexGridに表示されます。カラムヘッダーをクリックするとソートが実行され、フィルターアイコンから条件を指定してデータを絞り込むことができます(図5)。

図5:FlexGridのソート・フィルター機能
図5:FlexGridのソート・フィルター機能

 図のように、価格列のフィルターパネルを開くと、値による絞り込みや条件指定が可能です。日本語カルチャが適用されているため、UIも日本語で表示されます。

データの更新(UPDATE)

 FlexGridのcellEditEndedイベントで編集完了を検知し、Server Actionsを呼び出します。useTransitionはReact 19の機能で、非同期処理中の状態管理を行います。編集されたセルの情報(行、列、新しい値)を取得してServer Actionsに渡し、revalidatePath('/')でキャッシュを更新して最新データを再取得します(リスト14)。

[リスト14]components/OrderGrid.tsx(cellEditEndedハンドラ)
const handleCellEditEnded = (grid: wjGrid.FlexGrid, e: wjGrid.CellRangeEventArgs) => {
  const item = grid.rows[e.row].dataItem;
  const binding = grid.columns[e.col].binding;
  const newValue = grid.getCellData(e.row, e.col, false);

  startTransition(async () => {
    await updateOrder(item.id, { [binding]: newValue });
  });
};

 handleCellEditEndedは、セル編集が終了した時に呼び出されるイベントハンドラです。引数としてグリッドインスタンス(grid)とイベント引数(e)を受け取るので、これらの値を元に編集したセル、入力値を取り出します。それぞれの行の意味は、以下の通りです。

  + grid.rows[e.row].dataItem:編集された行のデータオブジェクト

  + grid.columns[e.col].binding:編集されたカラムのプロパティ名("product"、"price"など)

  + grid.getCellData(e.row, e.col, false):編集後の新しい値を取得

 getCellDataメソッドの第3引数をfalseにすることで表示用フォーマットではなく生の値を取得できます。あとは、これらの情報をもとにServerAction(updateOrder関数)を呼び出し、データベースを更新するだけです。

 これでユーザーはセルを編集したところで、データが即座に反映される——Excelのような操作感を体感できます(図6)。

図6:インライン編集中の画面
図6:インライン編集中の画面

データの削除(DELETE)

 FlexGridでデータを削除するための手順として、まずカスタムHTML(削除ボタン)をグリッド内に挿入します。React環境において、WjGridのcellTemplateプロパティはJSXを直接受け取れないため、formatItemイベントを利用してHTMLを直接挿入し、イベントデリゲーションを用いてボタンのクリックイベントを処理します(リスト15)。

[リスト15]components/OrderGrid.tsx(formatItemハンドラ)
const handleFormatItem = (grid: wjGrid.FlexGrid, e: wjGrid.FormatItemEventArgs) => {
  if (e.panel === grid.cells && grid.columns[e.col].header === "操作") {
    const item = grid.rows[e.row].dataItem;
    e.cell.innerHTML = `<button class="delete-btn" data-id="${item.id}">削除</button>`;
  }
};

 handleFormatItem関数は、FlexGridが各データセルを描画する直前に発生するformatItemイベントを処理するためのハンドラです。このイベントを利用することで、セルの内容をカスタマイズできます。

 まず、このハンドラは処理対象のセルを絞り込んでいます。具体的には「e.panel === grid.cells」という条件で通常のデータ本体のセルであることを確認し、さらに「grid.columns[e.col].header === "操作"」という条件で、列ヘッダーが「操作」となっている特定の列のみを対象としています。これにより、削除ボタンはデータ行の「操作」列に限定して表示されます。

 対象のセルを特定したら、「const item = grid.rows[e.row].dataItem」で、描画中の行に対応するデータオブジェクト(レコード)を取得します。このデータオブジェクトから、レコードを一意に識別するためのID(item.id)を取り出します。

 最後に、e.cell.innerHTMLに対して、削除ボタンのHTMLコードを直接挿入することでセルの内容を書き換えます。挿入されるHTMLには「<button class="delete-btn" data-id="${item.id}">削除</button>」という形で、ボタンのカスタムデータ属性であるdata-idに、先ほど取得したレコードの一意なIDが埋め込まれています。これで、後でクリック時にどの行を削除するか判別できます。

図7:削除ボタンの表示
図7:削除ボタンの表示

 サンプルを実行してみましょう。上の図のように、操作列に削除ボタンが配置され、ボタンクリックで確認ダイアログが表示されること、さらに[はい]ボタンをクリックすることでデータが削除されることが確認できます。

動作確認

 完成したアプリケーションでは、ページ読み込み時にデータ一覧を表示(READ)、フォームからデータ登録(CREATE)、FlexGridセルをダブルクリックして編集(UPDATE)、削除ボタンでデータ削除(DELETE)が可能です。すべての操作がServer Actionsを通じてサーバーサイドと連携し、データベースと同期されます。

まとめ

 Wijmoは、最新パラダイムのServer Actionsと組み合わせることで、より直感的なWebアプリケーション実装を行うことができます。費用をかけてでも一定の安心を手に入れるための選択肢として、商用ライブラリの導入を検討してみるのはいかがでしょうか。

この記事は参考になりましたか?

提供:メシウス株式会社

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/22788 2026/01/19 12:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング