アプリケーションサービス部の鎌田(義)です。
今回は、
ドラッグ/ドロップやリサイズ、追加/削除が可能な自由度の高いダッシュボードが実装できる
gridstack.jsというライブラリを紹介したいと思います。
本記事ではVue3を使用し、基本的な使用方法について記載します。
gridstackとは
gridstackの特徴
公式ページにも記載されていますが、
純粋なTypeScriptであり、フレームワークを選ばない点が大きな特徴かと思います。
今回は、OSSかつVue3でも利用できるという点でgridstackを選択しました。
その他の類似サービス
類似したサービスとしては以下のようなものがあります。
-
- Vue専用。現在(2023/7/11)時点では、Vue3はbeta版(3.0.0-beta1)であり、正式に対応していない模様
-
- jQueryプラグイン
-
- React専用
環境構築
Vue3環境作成
$ mkdir demo_gridstack $ cd demo_gridstack/ $ npm create vite@latest . ✔ Select a framework: › Vue ✔ Select a variant: › TypeScript $ npm install $ npm run dev
http://localhost:5173/ にアクセスして 画面が表示できることを確認します。
gridstackインストール
$ npm install --save gridstack@8.3.0
コードの実装
スタイル修正
Bootstrapを使用する為、最低限の修正を加えます。
index.html
linkを2行追記してタイトルもついでに修正しています。
src/main.ts
スタイルのインポートをコメントアウトしています。
デモ(移動/リサイズ)
src/App.vue
デモ用のコンポーネントを読み込むよう修正しています。
※適宜DemoGridStack*.vueを置き換えます。
src/components/DemoGridStackBasic.vue
今回は一部のみパラメータを指定していますが、 詳しくはドキュメントをご確認下さい。
gridOptionsでは今回、以下を指定しました。
const gridOptions: GridStackOptions = { float: true, // 縦方向にウィジェットを自動的に詰めないようtrueを指定。デフォルトはfalse cellHeight: 100, // ウィジェットの高さを100pxに指定。デフォルトは自動 disableResize: false, // ウィジェットのリサイズを許可する為falseを指定。デフォルトはfalse maxRow: 20, // 最大行数を20に指定。ウィジェットの高さ(h)が2の場合、10個まで縦に並べることができる。デフォルトは最大値なし };
initWidgetsでは、初期表示するウィジェットを4つ用意しています。
const initWidgets: GridStackWidget[] = [ { id: "initWidget-1", w: 2, h: 2 }, { id: "initWidget-2", w: 2, h: 2 }, { id: "initWidget-3", w: 2, h: 2 }, { id: "initWidget-4", w: 2, h: 2 }, ];
サイズは2×2とし、
x軸やy軸は今回は使用せず、配列の順番通りに並ぶようになっています。
マウント時にgridstackインスタンスを生成しています。
nextTick()でDOM更新が完了した後にmakeWidgets()を呼び出し、
初期ウィジェットをgridstackインスタンスに追加しています。
onMounted(() => { grid = GridStack.init(gridOptions); nextTick(() => { makeWidgets(); }); }); const makeWidget = (widget: GridStackWidget): void => { const elSelector = `#${widget.id}`; grid.makeWidget(elSelector); }; const makeWidgets = (): void => { widgets.value.forEach((widget) => { makeWidget(widget); }); };
template部分では、
gridstackが管理する要素を特定する為、指定のclass名を使用します。
<template> <div class="grid-stack"> <!--gridstackの親要素--> <div <!--gridstackの子要素--> v-for="widget in widgets" :id="widget.id" :key="widget.id" :gs-id="widget.id" :gs-x="widget.x" :gs-y="widget.y" :gs-h="widget.h" :gs-w="widget.w" > <div class="grid-stack-item-content card shadow"> <!--gridstackの子要素のコンテンツ--> <div class="col d-flex align-items-center justify-content-center"> <span class="text">{{ widget.id }}</span> </div> </div> </div> </div> </template>
デモ(追加/削除/レイアウト保存)
src/components/DemoGridStack.vue
gridOptionsでは、handleClassを追加、disableResizeをtrueに変更しました。
const gridOptions: GridStackOptions = { float: true, cellHeight: 100, handleClass: "draggable", // ドラッグ可能な要素のクラス名を指定。デフォルトはnull disableResize: true, maxRow: 20, };
addNewWidget関数では、
ランダムなサイズのウィジェットを追加し、gridstackインスタンスへの追加を行っています。
簡易的にユニークなIDを生成しています。
また、こちらもDOM更新後にgridstackインスタンスへ追加するようにしています。
const addNewWidget = (): void => { const uniqueId = Date.now().toString(16); const widget = { id: `widget-${uniqueId}`, w: Math.floor(Math.random() * 2) ? 2 : 4, h: Math.floor(Math.random() * 2) ? 2 : 4, }; widgets.value.push(widget); nextTick(() => { makeWidget(widget); }); };
gridstackの子要素にヘッダを用意し、gridOptionsで指定した「draggable」をクラス名に加えました。
ドラッグ可能な要素を分かりやすくする為、カーソルを変更しています。
<!--省略--> <div class="grid-stack-item-content card shadow"> <div class="bg-light d-flex"> <div class="flex-grow-1 draggable"></div> <div> <button class="btn" @click="deleteWidget(widget.id)"> <i class="bi bi-x-lg"></i> </button> </div> </div> <div class="col d-flex align-items-center justify-content-center"> <span class="text">{{ widget.id }}</span> </div> </div> </div> </div> </template> <style scoped> .draggable { cursor: move; } </style>
整列ボタンでは、compactメソッドを使い空白を埋めて詰めるようにしています。
<button class="m-1 btn btn-secondary" @click="grid.compact()">整列</button>
レイアウトの保存では、saveメソッドで出力されるウィジェットの配列を使用して
ローカルストレージに保存しています。
const saveLayout = (): void => { const layouts = grid.save(); localStorage.setItem("gridstack-layout", JSON.stringify(layouts)); };
マウント時に、ローカルストレージにレイアウトが存在する場合はロードしています。
onメソッドでは第一引数にdragstopイベントを指定し、infoメッセージにドラッグ後の座標を入れています。
onMounted(() => { grid = GridStack.init(gridOptions); loadLayout(); nextTick(() => { makeWidgets(); }); grid.on("dragstop", (_: DragStopParams[0], element: DragStopParams[1]) => { const node = element.gridstackNode; info.value = `you just dragged node #${node?.id} to {x: ${node?.x}, y: ${node?.y}}`; }); });
まとめ
いかがでしたでしょうか。
比較的少ないコードで自由度の高いダッシュボードが実装できました。
今回は、味気ないウィジェットのみでしたがChart.jsなどのチャートライブラリを使用して
グラフウィジェットを組み込むことも可能です。
ぜひ試してみてください。
鎌田 義章 (執筆記事一覧)
2023年4月入社 AS部DS3課