発端
なでしこ1のGUI部品には、配列やCSVデータを表形式で表示して閲覧、編集のできる「グリッド」という部品があります。
最近掲示板に、なでしこ3にグリッドはないのでしょうかという質問がありました。
なでしこ3にはtable要素を利用した「テーブル」という部品があり、同様に配列やCSVデータを表形式で表示して閲覧できるのですが、編集は出来ません。
ただし、元はテーブルの内容を書き換えようと思ったら、一旦部品を削除して新しいデータで再度テーブルを作るか、自前で書き換えのプログラムを実装するかしかなかったのですが、「テーブル更新」と「テーブルセル変更」の命令が追加されてとても使いやすくなりました!
でも、ワタクシの考えではグリッドにはまだ必要な機能が4つあります。
なでしこ1のグリッド
行選択
「行選択」をオンにすると、グリッド上をクリックした時、セル毎ではなく一行が選択状態になり色が変わります。
セル幅の変更
ヘッダ部分をドラッグすることで、ユーザーが自由にセル幅を変更できます。
自動ソート
「自動ソート」をオンにすると、ヘッダ部分をクリックした時、その列で表ソートします。
クリックする度に昇順と降順が入れ替わります。元には戻せません。
編集
「編集」をオンにすると、各セルの内容を直接ユーザーが編集出来るようになります。この設定は行選択より優先します。
ワタクシ個人的に、テーブルには無い一番重要な機能がこの編集だと思います。
外部のライブラリに頼る?
ちょっと検索すると、グリッドっぽいものは色々見つかりました。
その中でもちょっと良さそうかな? と思ったのがこの二つ。
使い方が簡単そうでMITライセンス(JspreadsheetはCE版)。
gridjsは編集機能が無かった
うん、ダメじゃん😅
グリッド部品似のシンプルな見た目で自動ソートに加えてサーチとページング機能が付加できるのでスゴく良さそうだと思ったんですけどね。
サーチやソートで見た目が変化した時のデータや、サーチで引っかかったのが元データの何行目かといった情報が無いっぽいので、無理矢理系で編集できるようにするのもなかなか面倒くさそう。
⬇️自動ソートもセル幅の変更も標準装備。簡単なイベント記述で行選択も付けられました。
JspreadsheetはCSSがツライ
こちらはグリッドっていうよりエクセルっぽい見た目でかっこよく、ちゃんと編集機能もあります。
なんか色々高機能そうなのですが、まにゅあるが英語だからよくわかんにゃい。
何より自前のHTMLでなら問題ないのですが、貯蔵庫エディタで動かそうとするとCSSが敗北して見た目が崩壊するのねん😢(ちなみに崩壊する原因を作った責任の一端はワタクシにありますのです🙏💦)
⬇️雑にボーダーや背景色に関わる設定がある部分を抜き出し、IDを付与して貼っ付けて見た目を整えてます💧
貯蔵庫のCSSは絶賛改良中のようなので、いずれうまく出来るようになると良いですね✨
こういった外部ライブラリが利用しやすくなると、なでしこさんで出来ることの幅というか拡張性というかが大きく広がる気がします。
contenteditableを知る‼️
ユーザーによる要素の編集を可能にできるcontenteditable
という属性があるらしい。にゃんと!!!
なんだ、テーブルのtd要素全部にこれ付けてやれば、フツーに編集可能に出来るようになるんじゃないですか?!
やってみます。
テーブルに編集機能を追加
ここからはテーブル部品に機能を追加して、どれだけv1のグリッドに近づけるかやってみたいと思います。
contenteditable属性を付ける
一瞬、tableタグにcontenteditable属性を付ければ中身全部編集できるようになるんじゃないの? と思ったのですが、全部編集できるようになったことはなったのですが、ヘッダも編集できちゃう上フォーカスがテーブル自体に入ってしまうのでよろしくない。
td要素を地道に全て反復して、全てのセルにcontenteditableを付けていくことにします。
contenteditableは論理属性ではなく列挙型属性なのでtrueかfalseを設定しなければならず、オン(1)などではいけません。true以外では有効にならないけど、オフにする時はcontenteditableを消すか、キチンとfalseを設定せよということラシイ。
DOM属性設定では出来るのに、どうもオブジェクトプロパティ構文で直接代入しようとしたら出来ないと思っていたら、HTML上はcontenteditable
で良いのだが、正しいプロパティ名はcontentEditable
だった。むじゅい💧
テストテーブル=「名前,ふりがな,点数
太郎,たろう,55
花子,はなこ,100
一郎,いちろう,80」のテーブル作成。
変数 全セル=テストテーブルの「td」をDOM子要素全取得。
全セルを反復:
対象.contentEditable=true。# 編集可能にする
なんと、たったこれだけで全てのセルが編集出来るようになり、タブでフォーカスを移動して行くことが出来るようになりました‼️
編集内容の反映
これで編集は出来るようになりましたが、まだ見た目上のことだけで、どこかに保存されている訳ではありません。
編集内容が反映されたデータを取得できるようにする必要があります。
とりあえず、編集内容を元データに反映してみることにします。
編集されたセルの行番号と列番号が分かれば、その内容を元データの同じ場所に代入してやればいいだけ!
先ほどはcsv形式のテキストを直接指定してテーブル作成しましたが、CSV取得して二次元配列のデータを作り、これを書き換えて行くことにします。
テストデータ=「名前,ふりがな,点数
太郎,たろう,55
花子,はなこ,100
一郎,いちろう,80」をCSV取得
テストテーブル=テストデータのテーブル作成。
セルの行番号と列番号を取得する。
列番号は、セルのcellIndex
です。
行番号は、セルの親要素 tr (parentNode
)のrowIndex
になります。
テストテーブルをクリックした時には、
もし、対象.nodeName=「TD」ならば、:
「行:{対象のセル行番号},列:{対象のセル列番号}」を表示。
ここまで。
●(セルの)セル行番号
セル.parentNode.rowIndexを戻す。
ここまで。
●(セルの)セル列番号
セル.cellIndexを戻す。
ここまで。
こちらはテーブルのイベント一つで出来ました。
テーブルのイベントでも、対象にはセル(td)が入ります。
ただし、ピンポイントで外枠のボーダーをクリックしたらテーブルになってしまいindexがundefinedになってしまうので、nodeNameを確認しています。
セルの行列が分かればこっちのもの。
テストデータ[行][列]=対象のテキスト取得。
こんな感じ?
セルがフォーカスを失ったタイミングで反映させるようにすればいいかな?
問答無用で反映させるのがいいのか、フォーカスが入った時と出た時のテキストを比較して、編集されてた時だけ反映させるようにした方がいいのか・・・🤔
どのテキストを取得するのか
と、その前にぶち当たった大きな命題。
テキスト取得(innerHTML)
編集された内容を実際にテキスト取得して確認してみたところ、追記、または一部を書き換えた場合は問題ないのですが、全部消してから入力した場合、改行はしていないのに何故か<br>
がくっついちゃうことが分かりました。
# 太郎を消さずに桃を追加した場合
桃太郎
# 一旦セルの内容を全て消して、桃太郎を入力した場合。
桃太郎<br>
そもそもセルを空にするとセルの内容は空ではなく<br>
になるっぽい。
そして、その後文字入力しても<br>
はそのまま残るもよう。
また、途中に改行を入れて入力した場合には、
/* 途中で改行した場合
太
郎
*/
<div>太</div><div>郎</div>
ぎゃー、ナニコレ💦
一行ごとdivタグで括られることになっているようです。
なんにしろ、HTMLタグがくっついて来てしまう。
ワタクシ「HTML取得」はinnerHTML
で、「テキスト取得」はinnerText
なのかと思っていたけど違ったっぽい。
確認するとテキスト取得は、inputやテキストエリアの場合は入力値(Value)を返すようになっていて、その他の要素はHTML取得同様innerHTMLが返るようになっているっぽい。
なでしこ1には「タグ削除」というHTMLタグを全て消しちゃう命令があったけど、なでしこ3には・・・無さそう?
innerText
いや、最初から自分でinnerTextを取得すれば良いんですよね?
テストデータ[行][列]=対象.innerText。
・・・と思ったんですけど、空にした時<BR>
のかわりに改行コード(\n
)が入ってしまう。
# 一旦セルの内容を全て消して、桃太郎を入力した場合。
桃太郎 //改行コード(/n)がある
//空行ができる
邪魔くさい💧
textContent
っていうのもある。
innerText
が、『ノードとその子孫の「レンダリングされている」テキスト内容』であるのに対し、textContent
は『ノードおよびその子孫のテキストの内容』とのこと。
こちらは改行コードが入りません。
# 一旦セルの内容を全て消して、桃太郎を入力した場合。
桃太郎
改行しても反映されません。テキストそのものだけが取得されるということのようです。
/* 途中で改行した場合
太
郎
*/
太郎
ブラウザによっても違う⁉️
色々うだうだ書いてきましたが、これはFirefoxでのことでした。
Chromeでは、セルを空にしてから文字入力しても<br>
や改行コードがくっついてくるという現象は起こらず、どれもスッキリ同じ結果になりました。なんだって~!?!?
では、まとめてみましょう。
※セルを空にして文字入力した場合
# Chromeの場合
innerHTML:桃太郎
innerText:桃太郎
textContent:桃太郎
# Firefoxの場合
innerHTML:桃太郎<br>
innerText:桃太郎 //改行コード(\n)がある
textContent:桃太郎
Chromeは全部同じ結果となりますが、Firefoxはそれぞれ違う結果となりました。
ちなみに、空にした場合はChromeもFirefox同様<br>
や改行コードが入っているのですが、その後文字入力した時に余分なものは消えるということのようです。
※途中で改行した場合
# Chromeの場合
innerHTML:太<br>郎
innerText:太
郎
textContent:太郎
# Firefoxの場合
innerHTML:<div>太</div><div>郎</div>
innerText:太
郎
textContent:太郎
差違があるのはinnerHTMLだけですが、Chromeは普通に<br>
が入るだけなのに対し、Firefoxではそれぞれが<div>
要素に入るコトになっているようです。
Safariのことは分かりません。
もしよろしければお試しして発表して下さいね。
ところで、改行の入った編集内容を二次元配列データに反映させたとして、そのデータで再びテーブルを作ったり、テーブル更新したりしたらどうなるのかな? と思いやってみた所、innerHTMLのタグが反映されることはなく、フツーにタグごとテキストとして表示されました。
ダメダメですね。
innerTextの場合は、テーブル上で改行が反映されることはもちろんないのですが、改行コード自体は存在しているので表CSV変換などした時に、めんどーなことになるようです。いちおう、もう一度CSV取得すればテーブルに戻すのはだいじょぶなんですが。
とりあえず改行入力は禁止にした方が良さそうですね。
v1のグリッドも改行は入れられません。
そしてどっちにしろ空にしたら改行が入ってしまうので、textContentを取得するのが良さそう。という結論にとりあえず達しました。
改行入力禁止
と、いうわけで、Enterのイベントを停止して改行を入れられないようにしちゃいます。
テストテーブルのキー押した時には、
もし、押されたキー=「Enter」ならば、
対象イベントのDOMイベント処理停止。
ここまで。
ここまで。
これでOKかと思いきや、コピペではフツーに改行も入っちゃう~。
textContentだからデータには入らないんですが、見た目的に?
フォーカスが外れる時に、セルにもtextContentを設定しちゃう?
テーブル数値右寄せ
「DOM部品オプション」には「テーブル数値右寄せ」という項目があり、trueになっていると内容が数値のセルは右寄せが設定されます。デフォルトはtrue
です。
👉なでしこ3 マニュアル > plugin_browser/DOM部品オプション
編集後のテキストが数値かどうかを判定して、行揃えを変更する必要がありそうです。
「数字か判定」と「数列か判定」がありますが、すべてが数値かどうか判定出来るのは「数列か判定」です。
👉なでしこ3 マニュアル > plugin_system/数列判定
もし、DOM部品オプション.「テーブル数値右寄せ」=trueならば、
テキストを数列か判定。
もし、それがtrueならば、
対象の「行揃え」に「右」をDOMスタイル設定。
違えば、
対象の「行揃え」に「左」をDOMスタイル設定。
ここまで。
ここまで。
簡単にできる予定でしたが、DOM部品オプション.テーブル数値右寄せ
がundefined
になってしまい悩みました。
どうやらキーが「漢字+ひらがな」ひらがなで終わっているとうまくいかず、「」で括る必要があるようです。
「行揃え」もそうですね。ついでに「text-align」のような「-」が入ったヤツも括らないとエラーなるようです(?)
っていうか直接代入しようとすると「右」や「左」が使えないから、DOMスタイル設定のが早かったw
とりあえずできました
こんな感じ?
# テーブルを作成
テストデータ=「名前,ふりがな,点数
太郎,たろう,55
花子,はなこ,100
一郎,いちろう,80」をCSV取得。
テストテーブル=テストデータのテーブル作成。
# 元データを表示
「元データ:」を表示。
テストデータをJSONエンコードして表示。
# セルの設定
変数 全セル=テストテーブルの「td」をDOM子要素全取得。
全セルを反復:
対象.contentEditable=true。# 編集可能にする
対象の「blur」がDOMイベント発火した時には、:
もし、対象.nodeName=「TD」ならば、:
# 改行を含むコピペ対策
テキスト=対象.textContent。
対象にテキストをテキスト設定。
# テーブル数値右寄せ
もし、DOM部品オプション.「テーブル数値右寄せ」=trueならば、
テキストを数列か判定。
もし、それがtrueならば、
対象の「行揃え」に「右」をDOMスタイル設定。
違えば、
対象の「行揃え」に「左」をDOMスタイル設定。
ここまで。
ここまで。
# セルの内容をデータに反映
行=対象のセル行番号。
列=対象のセル列番号。
テストデータ[行][列]=テキスト。
# セル内の改行入力を禁止する。
テストテーブルのキー押した時には、
もし、押されたキー=「Enter」ならば、
対象イベントのDOMイベント処理停止。
ここまで。
ここまで。
# セルの行列番号を取得
●(セルの)セル行番号
セル.parentNode.rowIndexを戻す。
ここまで。
●(セルの)セル列番号
セル.cellIndexを戻す。
ここまで。
# 編集後データの表示
「編集後のデータを取得」のボタン作成。
それをクリックした時には、:
「編集後データ:」を表示。
テストデータをJSONエンコードして表示。
できました!
編集出来るテーブルのサンプルです👇
セルの内容を書き換えて編集後のデータを取得したら、ちゃんとデータの内容が編集したとおりに変わっているのが確認できます✨
つづきます
ユキノはcontenteditableのじゅもんをおぼえた!
予想以上にどのテキストを取得するのかの研究に、時間と行数と精神点を消費してしまったので、っていうかアドベントカレンダー書くことなくてピンチなので、他の機能は次回に持ち越します😅