Excelみたいな表入力はcontentEditable属性で簡単に実装できる
今から14~5年前のこと、業務用のWebシステムで、ユーザーが Microsoft Access※ のように自由に入力フォームをデザインできるようにしてほしい、という無茶ぶりをされたことがあります。
※あるいはLotus Notesのように、と言ったほうが通じるでしょうか
当時は起業した直後で、大事な取引先からの要望、断るわけにもいきません。そこでゴリゴリとJavaScriptを使い、当時はまだベータ版で使いものにならなかったGoogleスプレッドシートのようなモノを自作したことがあります。
ざっくり言うと、TABLEタグで表を描画して、カーソルキーの入力を受け付け、F2キーやENTERキーを押されたら、現在位置にテキストボックスを移動する、という仕組みです。もちろん罫線の描画やセルの結合にも対応しました。
ついでに色や太字等、様々な装飾も可能にすることで、Web画面上でまるでExcelを操作しているかのように、自由な入力フォームの編集ができるというわけですね。これを納品したとき、ブラウザ上でこんなことができるのかとお客様には大変好評でした。
…今考えるとExcelで画面作ってもらってアップロードさせるほうが簡単だったのでは…と思わなくもありませんが。
さて、それで今回は上述のようなプログラムを紹介しようかな、と思い、HTML5に合わせて書き直して解説しよう……………………と思ったのですが、HTML5について調べていて驚愕の事実を知ってしまいました。
contenteditable属性を付けるだけであらゆるタグが編集可能になる
例えば、こんな感じ。
実行例
↑の文章をクリックするとテキストボックスに変わるのが確認できます
HTMLソース
<div contenteditable="true">ここをクリックすると編集できます</div>
これ、DIVタグにcontenteditable="true"を付けただけですよ。もちろんSPANタグやPタグでも構いませんし、TABLEのTDタグにも使えます。
TABLEを使った例
001 | 山田 一郎 | 03-1234-5678 |
002 | 山田 二郎 | 03-2234-5678 |
003 | 山田 三郎 | 03-3234-5678 |
004 | 山田 四郎 | 03-4234-5678 |
005 | 山田 五郎 | 03-5234-5678 |
各セルをクリックすると文章が編集可能になっていることが確認できます。
HTMLソース
<table>
<tr><td contenteditable="true">001</td><td contenteditable="true">山田 一郎</td><td contenteditable="true">03-1234-5678</td></tr>
<tr><td contenteditable="true">002</td><td contenteditable="true">山田 二郎</td><td contenteditable="true">03-2234-5678</td></tr>
<tr><td contenteditable="true">003</td><td contenteditable="true">山田 三郎</td><td contenteditable="true">03-3234-5678</td></tr>
<tr><td contenteditable="true">004</td><td contenteditable="true">山田 四郎</td><td contenteditable="true">03-4234-5678</td></tr>
<tr><td contenteditable="true">005</td><td contenteditable="true">山田 五郎</td><td contenteditable="true">03-5234-5678</td></tr>
</table>
これだけでTABLEの編集が可能って、ちょっと便利すぎィ! 昔JavaScriptでゴリゴリ書いていたのは何だったんだという気すらします。
contenteditable属性を一括設定する例
とはいえ、contenteditableを全ての行に書くのは面倒くさい。一括設定する方法はないものでしょうか?
CSSを使ったcontenteditableの一括設定(非推奨)
<style>
table td {
-webkit-user-modify: read-write;
-moz-user-modify: read-write;
user-modify: read-write;
}
</style>
一応このCSSはChromeでもFirefoxでも、(テストしていませんが)Safariでも動くはずです。でも、webkitなのであくまでブラウザ側の先行対応であり、Web標準的には非推奨。将来のアップデートで使えなくなる恐れがあります。
…………………まぁ、これに限らずアップデートで表示崩れが起きる問題なんか山ほどあるから今更じゃねーか、という話は置いておいて。
JavaScriptを使ったcontenteditableの一括設定
<script>
window.addEventListener('load', function () {
document.querySelectorAll('table td').forEach(elm => {
elm.contentEditable = true;
});
});
</script>
JavaScriptアレルギーでもなければこちらのほうが安全でしょう。ちなみに、querySelectorAll('table td')だとそのページ内の全てのTABLE TDが対象になってしまうので適宜書き換えてください。対象TABLEのIDに"table_edit"と付けて querySelectorAll('#table_edit td') にするなど。
TABLE全体をcontenteditableにすれば良いのでは?
お行儀よく、全てのTDタグをcontenteditableにするのではなく、いっそTABLE全体を編集可能にする例。
表示例
001 | 山田 一郎 | 03-1234-5678 |
002 | 山田 二郎 | 03-2234-5678 |
003 | 山田 三郎 | 03-3234-5678 |
004 | 山田 四郎 | 03-4234-5678 |
005 | 山田 五郎 | 03-5234-5678 |
クリックするとセル単位ではなく、テーブル全体がフォーカスされるので、見た目はちょっとアレですが、テーブル内のセルを編集したい、という要望は叶えられるかと。
HTMLソース例
<table contenteditable="true">
<tr><td>001</td><td>山田 一郎</td><td>03-1234-5678</td></tr>
<tr><td>002</td><td>山田 二郎</td><td>03-2234-5678</td></tr>
<tr><td>003</td><td>山田 三郎</td><td>03-3234-5678</td></tr>
<tr><td>004</td><td>山田 四郎</td><td>03-4234-5678</td></tr>
<tr><td>005</td><td>山田 五郎</td><td>03-5234-5678</td></tr>
</table>
かなり強引ですが、管理者しか使わないページなど、ある程度お行儀の良い人が使うページなら、アリ………かなぁ?
編集したセルをCSVやJSONへ変換する
Web上で表入力をしたい、ということはつまり、たくさんの項目を一括で編集し、まとめて更新したい、ということだと思います。
ならば、その表データをCSVなり、JSONなりに変更し、PHP等のサーバーサイドスクリプトへ受け渡す必要があるでしょう。
表示例
001 | 山田 一郎 | 03-1234-5678 |
002 | 山田 二郎 | 03-2234-5678 |
003 | 山田 三郎 | 03-3234-5678 |
004 | 山田 四郎 | 03-4234-5678 |
005 | 山田 五郎 | 03-5234-5678 |
CSVまたはJSONボタンをクリックすると、その下のテキストエリアに変換結果が表示されるはずです。
パッと見はCSVのほうが見やすく感じますが、データ中にカンマや改行が含まれている場合はダブルクォートで囲まないといけないことを考えると、JSON形式で渡したほうが何かと便利なんじゃないかなーと思います。PHPならjson_decode関数一発で配列に戻せますしね。
尚、contentEditableを使った場合、テキストを選択してCTRL+Bで太字、CTRL+Uで下線などの装飾が出来ますが、CSV/JSON変換においてはその装飾は無視しています。文字装飾までデータ出力に反映させたいとなると、textContentプロパティではなく、innerHTMLプロパティから入力データを取る必要があります。
ちなみに文字装飾とは別の話ですが、CTRL+Zでのアンドゥ(やり直し)機能もあるので地味に便利。
HTMLとJavaScript
<table id="table_edit">
<tr><td>001</td><td>山田 一郎</td><td>03-1234-5678</td></tr>
<tr><td>002</td><td>山田 二郎</td><td>03-2234-5678</td></tr>
<tr><td>003</td><td>山田 三郎</td><td>03-3234-5678</td></tr>
<tr><td>004</td><td>山田 四郎</td><td>03-4234-5678</td></tr>
<tr><td>005</td><td>山田 五郎</td><td>03-5234-5678</td></tr>
</table>
<p>
<button id="csv_button">CSV</button>
<button id="json_button">JSON</button>
</p>
<textarea id="data"></textarea>
<style>
#table_edit {
border-collapse:collapse;
}
#table_edit td {
border:1px solid lightgray;
}
#data {
height:8em;
width:100%;
}
</style>
<script>
window.addEventListener('load', function () {
document.getElementById('csv_button').onclick = function () {
let table = document.getElementById('table_edit');
let csv = '';
for (r = 0; r < table.rows.length; r++) {
for (c = 0; c < table.rows[r].cells.length; c++) {
if (c > 0) {
csv += ',';
}
csv += table.rows[r].cells[c].textContent;
}
csv += "\n";
}
document.getElementById('data').textContent = csv;
};
document.getElementById('json_button').onclick = function () {
let table = document.getElementById('table_edit');
let rows = [];
for (r = 0; r < table.rows.length; r++) {
let row = [];
for (c = 0; c < table.rows[r].cells.length; c++) {
row[c] = table.rows[r].cells[c].textContent;
}
rows[r] = row;
}
document.getElementById('data').textContent = JSON.stringify(rows);
};
document.querySelectorAll('#table_edit td').forEach(elm => {
elm.contentEditable = true;
});
});
</script>
やっていることは csv_button も json_button もほとんど一緒で、for (r = 0; r < table.rows.length; r++) { ほにゃらら } でTABLEタグ内のデータを読み取り、CSVでは文字列に、JSONではいったん配列に格納してから JSON.stringify でJSON文字列に変換しているだけです。
こうしてJavaScript側でデータ変換(シリアライズと言う)しておけば、textareaの値をPHP等のサーバーサイドスクリプトへPOSTするだけで一括更新処理ができるわけですね。
まとめ
- 昔、JavaScriptで作った表入力処理を紹介しようと思ったら、contenteditableなる属性が追加されていた
- contenteditable属性はTABLEタグどころか、DIV/SPAN/P等あらゆるタグで使える便利な属性だった
- せっかくなのでcontenteditable属性を使った表入力と、そのCSV/JSON変換の例を掲載してみた
といったところでしょうか。
Excel入力のような高機能、とはいきませんが、たったこれだけのコードでWeb上での表入力や、その入力データの一括送信ができるってホント便利ですよねー。
wikiが流行る前から、ユーザー投稿型のゲーム攻略サイトなどを運営しているのですが、そういったユーザーがデータ編集するタイプのWebサイトで使ってみたいと思いました。