はじめに
ちょっとお仕事で必要が生じて、HTMLのテーブルのカラム幅をドラッグ&ドロップで変更して、その状態を保存&復元する機能を実装することになったので、その検証メモ。
今回は、Laravelを使って組んでいく。
なお、実行環境は以下だが、構築の詳細は割愛。
- VirtualBox 7.1.0
- AlmaLinux 9.4
- PHP 8.3.22
下準備
まずはLaravelのプロジェクトを作る。
cd /var/www/html composer create-project laravel/laravel:11.* sample03 chmod -R a+w sample03/storage/ chmod a+w sample03/bootstrap/cache
お仕事がLaravel 11なので、バージョンとしてLaravel 11系を指定しているが、最新のLaravel 12系でも問題なく動くと思う。
ファイルの作成・編集
sample03/resources/views/sample/index.blade.php
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <link rel="stylesheet" type="text/css" href="sample.css" /> <script type="text/javascript" src="sample.js"></script> </head> <body> <table> <thead> <tr> <th> <div>姓</div> </th> <th> <div>名</div> </th> <th> <div>メールアドレス</div> </th> </tr> </thead> <tbody> <tr> <td>姓1</td> <td>名1</td> <td>メールアドレス1</td> </tr> <tr> <td>姓2</td> <td>名2</td> <td>メールアドレス2</td> </tr> <tr> <td>姓3</td> <td>名3</td> <td>メールアドレス3</td> </tr> </tbody> </table> </body> </html>
コツとしては、thタグに直接resizeプロパティを設定しちゃうと、あとで幅を縮小できないという悩みにぶつかるので、thタグの子としてdivタグなどを挟んであげる必要がある。
sample03/app/Http/Controllers/SampleController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; class SampleController extends Controller { public function index() { return view('sample.index'); } public function save(Request $request) { $columnName = $request->get('column_name'); $width = $request->get('width'); if ($width > 0) { // TODO 本当はDBから読み書きする if (Session::has('ShowWidth')) { $widths = Session::get('ShowWidth'); } else { $widths = []; } $widths[$columnName] = $width; Session::put('ShowWidth', $widths); } echo json_encode(['status' => true]); } public function load() { // TODO 本当はDBから読み込む echo json_encode(['status' => true, 'widths' => Session::get('ShowWidth')]); } }
sample03/public/sample.css
table { border-collapse: collapse; table-layout: fixed; } th, td { border: 1px solid #ccc; /* セル内のテキストが改行されないようにする */ white-space: nowrap; /* セルからはみ出た内容を非表示にする */ overflow: hidden; /* 非表示になったテキストの末尾を「...」で表示する */ text-overflow: ellipsis; } th div { resize: horizontal; overflow: hidden; }
sample03/public/sample.js
$(function () { // テーブルヘッダのリサイズを検知して変更後の幅を保存するためのResizeObserver const resizeObserver = new ResizeObserver((entries) => { // DOMツリー生成時のイベントを無視する(やっつけ) if (entries.length === 1) { entries.forEach((entry) => { console.log( entry.target.innerText + " is resized: " + entry.target.style.width + ", " + entry.target.offsetWidth ); $.ajax({ type: "get", url: "sample/save", dataType: "json", data: { column_name: entry.target.innerText, width: entry.target.offsetWidth, }, }) .done((result) => { const allRows = document.querySelectorAll("table tbody tr"); const header = entry.target; allRows.forEach((row) => { const columnIndex = header.parentNode.cellIndex; const cells = row.children; if (cells.length > columnIndex) { cells[columnIndex].style.width = header.style.width; cells[columnIndex].style.maxWidth = header.style.width; } }); }) .fail((jqXHR, textStatus, errorThrown) => { alert("Ajax通信に失敗しました。"); console.log("jqXHR : " + jqXHR.status); // HTTPステータスを表示 console.log("textStatus : " + textStatus); // タイムアウト、パースエラーなどのエラー情報を表示 console.log("errorThrown : " + errorThrown.message); // 例外情報を表示 }); }); } }); // テーブルのヘッダにResizeObserverを紐づけ const headers = document.querySelectorAll("table thead th div"); headers.forEach((header) => { resizeObserver.observe(header); }); // 初期サイズをロード $.ajax({ type: "get", url: "sample/load", dataType: "json", }) .done((result) => { if (result.status && result.widths) { const headers = document.querySelectorAll("table thead th div"); const allRows = document.querySelectorAll("table tbody tr"); headers.forEach((header) => { if (result.widths[header.innerText]) { header.style.width = result.widths[header.innerText] + "px"; allRows.forEach((row) => { const columnIndex = header.parentNode.cellIndex; const cells = row.children; if (cells.length > columnIndex) { cells[columnIndex].style.maxWidth = result.widths[header.innerText] + "px"; cells[columnIndex].style.width = result.widths[header.innerText] + "px"; } }); } }); } }) .fail((jqXHR, textStatus, errorThrown) => { alert("Ajax通信に失敗しました。"); console.log("jqXHR : " + jqXHR.status); // HTTPステータスを表示 console.log("textStatus : " + textStatus); // タイムアウト、パースエラーなどのエラー情報を表示 console.log("errorThrown : " + errorThrown.message); // 例外情報を表示 }); });
sample03/routes/web.php
<?php use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); Route::get('/sample', [\App\Http\Controllers\SampleController::class, 'index']); Route::get('/sample/save', [\App\Http\Controllers\SampleController::class, 'save']); Route::get('/sample/load', [\App\Http\Controllers\SampleController::class, 'load']);
動作確認
以下のURLにアクセスして動作確認。
http://<your-ip-address>/sample03/public/sample
- ドラッグ&ドロップでカラム幅が拡大、縮小できること
- リロードすると、状態が復元されること
が確認できれば完成。