2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

日本語プログラミング言語「なでしこ」Advent Calendar 2024

Day 8

なでしこさんにグリッドが欲しい❗️ 前編 ~編集したい!~

Last updated at Posted at 2024-12-07

発端

 なでしこ1のGUI部品には、配列やCSVデータを表形式で表示して閲覧、編集のできる「グリッド」という部品があります。
 最近掲示板に、なでしこ3にグリッドはないのでしょうかという質問がありました。
 なでしこ3にはtable要素を利用した「テーブル」という部品があり、同様に配列やCSVデータを表形式で表示して閲覧できるのですが、編集は出来ません。

 ただし、元はテーブルの内容を書き換えようと思ったら、一旦部品を削除して新しいデータで再度テーブルを作るか、自前で書き換えのプログラムを実装するかしかなかったのですが、「テーブル更新」と「テーブルセル変更」の命令が追加されてとても使いやすくなりました!

 でも、ワタクシの考えではグリッドにはまだ必要な機能が4つあります。

なでしこ1のグリッド

v1グリッド.jpg

行選択

 「行選択」をオンにすると、グリッド上をクリックした時、セル毎ではなく一行が選択状態になり色が変わります。

セル幅の変更

 ヘッダ部分をドラッグすることで、ユーザーが自由にセル幅を変更できます。

自動ソート

 「自動ソート」をオンにすると、ヘッダ部分をクリックした時、その列で表ソートします。
 クリックする度に昇順と降順が入れ替わります。元には戻せません。

編集

 「編集」をオンにすると、各セルの内容を直接ユーザーが編集出来るようになります。この設定は行選択より優先します。

 ワタクシ個人的に、テーブルには無い一番重要な機能がこの編集だと思います。

外部のライブラリに頼る?

 ちょっと検索すると、グリッドっぽいものは色々見つかりました。
 その中でもちょっと良さそうかな? と思ったのがこの二つ。

 使い方が簡単そうで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子要素全取得
全セル反復:
    
対象.contentEditabletrue# 編集可能にする

 なんと、たったこれだけで全てのセルが編集出来るようになり、タブでフォーカスを移動して行くことが出来るようになりました‼️

編集内容の反映

 これで編集は出来るようになりましたが、まだ見た目上のことだけで、どこかに保存されている訳ではありません。
 編集内容が反映されたデータを取得できるようにする必要があります。
 とりあえず、編集内容を元データに反映してみることにします。
 編集されたセルの行番号と列番号が分かれば、その内容を元データの同じ場所に代入してやればいいだけ!

 先ほどは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のタグが反映されることはなく、フツーにタグごとテキストとして表示されました。

テーブルテスト.jpg

 ダメダメですね。
 innerTextの場合は、テーブル上で改行が反映されることはもちろんないのですが、改行コード自体は存在しているので表CSV変換などした時に、めんどーなことになるようです。いちおう、もう一度CSV取得すればテーブルに戻すのはだいじょぶなんですが。

CSVテーブルテスト.jpg

 とりあえず改行入力は禁止にした方が良さそうですね。
 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子要素全取得
全セル反復:
    
対象.contentEditabletrue# 編集可能にする
    対象「blur」DOMイベント発火した時には:
        
もし対象.nodeName「TD」ならば:
            
# 改行を含むコピペ対策
            テキスト対象.textContent
            対象テキストテキスト設定
            # テーブル数値右寄せ
            もしDOM部品オプション.「テーブル数値右寄せ」trueならば
                テキスト数列か判定
                もしそれtrueならば
                    対象「行揃え」「右」DOMスタイル設定
                違えば
                    対象「行揃え」「左」DOMスタイル設定
                ここまで
            ここまで
            # セルの内容をデータに反映
            対象セル行番号
            対象セル列番号
            テストデータ[][]テキスト

# セル内の改行入力を禁止する。
テストテーブルキー押した時には
    もし押されたキー「Enter」ならば
        対象イベントDOMイベント処理停止
    ここまで
ここまで

# セルの行列番号を取得
●(セルの)セル行番号
    セル.parentNode.rowIndex戻す
ここまで

●(セルの)セル列番号
    セル.cellIndex戻す
ここまで

# 編集後データの表示
「編集後のデータを取得」ボタン作成
それクリックした時には:
  
「編集後データ:」表示
    テストデータJSONエンコードして表示

 できました!
 編集出来るテーブルのサンプルです👇

 セルの内容を書き換えて編集後のデータを取得したら、ちゃんとデータの内容が編集したとおりに変わっているのが確認できます✨

つづきます

 ユキノはcontenteditableのじゅもんをおぼえた!

 予想以上にどのテキストを取得するのかの研究に、時間と行数と精神点を消費してしまったので、っていうかアドベントカレンダー書くことなくてピンチなので、他の機能は次回に持ち越します😅

2
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?