見出し画像

【チェックボックス3】スプレッドシート上で実用的な ラジオボタン 【GAS】

チェックボックスネタも最後です。小ネタのつもりが計3回になってしまいました。一応、今回が最後になります。

前回の記事



Googleスプレッドシートで ラジオボタン を作りたい?

これまでのチェックボックスシリーズで、スプレッドシート上の リージョンという考え方、そして範囲に対しての チェックボックス のチェック状態 判定方法 が理解できたかと思います。

これらの学びを応用して、今回は Googleスプレッドシートには 標準機能としては用意されていない 「実用的な」ラジオボタン(風) を チェックボックス と GAS で作ってみましょう。 



それってGoogleスプレッドシート上で必要??

ラジオボタンは Excelだと 機能として存在します。

ActiveXコントロール とか IE時代だな

開発 > 挿入 > フォームコントロール(もしくは ActiveXコントロール)から オプションボタン を選択すれば OK。

名前は オプションボタン となっていますが、チェックボックスとは 別に用意されており、ちゃんとにラジオボタン の見た目 〇 にポッチというボタンになってますね。

でも、Excelで ラジオボタンが必要だったのは、かつて

オーナー側でアンケートを Excelで作成

そのExcelファイルを ユーザーに メール等 で配布、
もしくは NASなど共有ドライブに置いて コピーしてもらう

ユーザー側で ポチポチと Excelファイル上で ラジオボタンやチェックボックスで回答選択、上書き保存

これを再度 ファイルとして回収、もしくは 印刷した紙を回収

オーナー側で集計作業 (基本目視、手作業)

こんなことをやってたからです。

いまどき、こんなアナログなことしてないですよね? 

時代は変わったのよー

いまなら チェックボックス や ラジオボタンを使うような アンケート等は、Googleフォーム や Microsoft Forms で行うべきです。

回収や集計を考えれば、こっち使った方が 数倍便利。

じゃあ、スプレッドシート上で わざわざ GASを使って、 チェックボックスをラジオボタン化 しても 意味ないんじゃね?

はい。意味はありませんw

「 スプレッドシートで作成した質問項目から Googleフォームを自動作成する【GAS】」みたいな記事の方が、よっぽど実用性があります。

これはもうね、自己満足というか 自分の世界というか、たとえるなら 空を駆けるひとすじの流れ星~ です。

GASによる スプレッドシート操作の練習にはなるかと思いますし、一部の謎の勢力は スプレッドシートでアンケート作りたがるんですよねw

ま、知ってるとなんかの役には立つかもしれません。


「実用的な」ラジオボタン(風)ってなんだ?

では、タイトルになぜ「実用的な」と付けたのか?

これは、GASによる チェックボックスの ラジオボタン化 を紹介しているサイトの内容が、どれも 実用的じゃないからです。(申し訳ないですが、ざっと調べて感じたこと)

そもそも、このネタを取り上げてるサイト自体が多くないんですが、ほぼどのサイトも

1. ラジオボタンの範囲をコード内で 直接指定している
 getRange("A5:A9") といった感じで
2. チェックのON/OFF を setValue(s) の true,falseで制御している

こんなやり方なんですよね。

これだと以下のようなケース

質問(ラジオボタン化したいグループ)が複数ある場合は、チェックが入った行、列番号を取得してと 質問のセル位置に合わせた 分岐処理が必要になっちゃいます。

しかも 上記のような場合は、チェックボックスの 間に 選択肢の文字列セルがあって そのまま 範囲に対して setValue(false) は使えません。

1つのシートに ポツンと 1箇所だけ ラジオボタンのグループがあるなんてケースはあまり無いですよね? 限界集落じゃないんだから。

シート上にアンケートみたいな形で ラジオボタンを作るとしたら、上記のような 複数質問があって 複数のラジオボタングループを作る必要が出てくると思います。これに対応できないと実用的とは言えません!

(最初に書いた通り、そもそも スプレッドシート上の ラジオボタン化が実用的じゃないんで、そこまで熱くならなくてもよいんですがw)

ちなみにラジオボタン(風) としたのは、見た目を変えることはできないからです。

四角いチェックをまーるく納められない

本来 画像のように

チェックボックスは ☐ と ☑
ラジオボタンは  〇 と ⦿
※環境依存文字なんで 化けちゃうかも

こんな感じでチェックボックス、ラジオボタンは  ビジュアル的なお約束(定義?)があります。でも、さすがにチェックボックスの形状を GASで変えることは出来ません。

あくまでも、ラジオボタン的な機能(選択できるのは1つの項目のみで、項目を選択し直すと他の項目は未選択となる)だけをGASで実現しているので、「風」としています。(とはいえ、何度も登場するとウザいから 以降は 「風」は省略)



実用的なラジオボタン をどう実現するか?

じゃあ、どのようにすれば 複数質問(チェックボックスグループ)があったり、チェックボックスの途中に文字列が入るケースでも対応できる「実用的な」ラジオボタンが作れるか?

前回まで学んだことを応用すればOKです。前提条件はただ一つ

「質問ごとに リージョンになっている」

これだけです。

その前提の上で、

・チェックボックスが ONとなった時に、そのチェックボックスのセルが属するリージョンを取得する

・リージョン内の 今チェックONとなったカレントセル以外を、チェックボックス専用メソッド uncheck でチェックOFFにする

こんな感じの流れで、リージョン内の対象セルに対して、チェックボックス以外に影響を与えない チェックボックス専用メソッドの uncheck を適用とすればよいわけです。



0. 最もシンプルな ラジオボタン コード

実用的なラジオボタンを実現する 一番簡単なコードはコレ ↓

function onEdit(e){
  const range = e.range;
  if(!range.isChecked()) return; //編集セルが チェックON以外なら終了
  range.getDataRegion().uncheck();
  range.check();
}

ちょーシンプルですね。

もちろん onEdit のシンプルトリガーなんで エディタで記述、保存するだけで  スクリプトの承認は必要なし。 オーナーでもユーザー(Googleログインは必要)でも 使えます。

前回までに登場したメソッドばかりなので、特に解説も不要でしょう。
一点だけ !range.isChecked()  の部分を説明すると、

range.isChecked()  range(編集されたセル)のチェックボックス判定
! (論理否定) 判定結果を 反転

編集されたセルが チェックボックス ON → true (反転で false
編集されたセルがチェックボックス OFF → false(反転で true
編集されたセルがチェックボックス以外 → null (反転で true

!(論理否定)は、ちょうど 土日更新のシリーズ 自作関数でも登場しましたね。  null も falseと見なされるので 反転すると trueになります。

true なら return として、その時点で終了となります。 反転後が false の時(つまり セルがチェックONとなった時)のみ、その後のリージョンに対する check,uncehck の処理が動くわけです。

うごかしてみよう
こっちの配置でも

実際の動作です。

うん、ちゃんとに質問(リージョン)単位で ラジオボタンとして機能はしてるけど・・・。

なんか動きがカッコ悪いw

一度 オフになってからチェックが戻るってのが、なんか微妙ー。

実は 処理の中で「リージョン内の 今チェックONとなったカレントセル以外」、この判別・取得がちょっと面倒なんですよね。

ここを リージョン全部を一度チェックOFFにして、今チェックを入れたカレントセル のみ チェックONに戻す。

この流れにすると非常に簡単なんで、まずは簡単な方のコードでやってみました。

VBAなら Application.ScreenUpdating = False 入れて、時を止めたいところですが、残念ながら GASには画面更新を止めるメソッドがありません。(逆に強制的に更新させる方法はある)

このコードで十分って人もいるでしょうが、もうちょい思考して カレントセル以外を適用範囲とするコードにしてみましょう。


チェックボックスを 実用的なラジオボタン にする 2つの GAS

チェックが一度外れて再度つくという微妙な挙動を回避する為に、今チェックを入れた カレントセルを除いて uncheckを実行する  2つのアプローチを考えました。

  1. リージョン内のセルを1つずつ 厳密なチェックボックス判定

  2. リージョンを カレントセル以外の最大4つの 範囲に分割

まずは 1からやってみましょう。



1. リージョンのセルを1つずつ 厳密なチェックボックス判定

これは前回の GASによる 厳密なチェックボックス ON/OFF判定の応用です。

■判定から 処理までの流れ
1. 範囲(リージョン)を取得
2. getDataValidations で データ入力規制を 二次元配列で取得
3. 二次元配列から 一つずつ dataValidation を取り出すループ処理
4. 入力規制が設定されていない場合 dataValidationは  null となる。null 以外が対象
5. getCriteriaType で CHECKBOX 判定。チェックボックスのみが対象
6. チェックボックスである 単体セルを リージョン内から取得
7. 単体セルに対しての isCheckedが true かつ カレントセル以外か?
8. そのセルを uncheck して終了

※ ラジオボタンなので、既にチェックされているセルはあっても  1つという想定

コードにするとこんな感じ

ラジオボタン コード その1 (判定方式)

//汎用性のある ラジオボタンコード3
function onEdit(e){
  const range = e.range;
  if(!range.isChecked()) return;

  //編集されたセルの A1表記のアドレスを取得
  const add = range.getA1Notation();

  //1.範囲(リージョン)を取得
  const region = range.getDataRegion();
  //2.getDataValidations で データ入力規制を 二次元配列で取得
  let dataValidations = region.getDataValidations();


  //3.二次元配列から 一つずつ dataValidation を取り出すループ処理
  //縦方向ループ
  for(i=0; i<dataValidations.length; i++){
    //横方向ループ
    for(j=0; j<dataValidations[0].length; j++){
      const dataValidation = dataValidations[i][j];

      //4.入力規制が設定されていない場合 dataValidationは  null となる。null 以外が対象
      if(dataValidation != null){
        //5.getCriteriaType で CHECKBOX 判定。チェックボックスのみが対象
        if(dataValidation.getCriteriaType() == SpreadsheetApp.DataValidationCriteria.CHECKBOX){
          //6.チェックボックスである 単体セルを リージョン内から取得
          const cell = region.getCell(i+1,j+1);
          //7.単体セルに対しての isCheckedが true かつ カレントセル以外か?
          if(cell.isChecked() === true && cell.getA1Notation() != add){
            //8.そのセルを uncheck して終了
            cell.uncheck();
            return;
          }
        }
      }
    }
  }
}

長めですけど、前回のコードがそのまま使えますね。
これも 前回の記事を読んでいただければ解説は十分かと思います。

処理速度を上げる為に、カレントセル以外の チェックONが 1つ見つかった時点で、そのセルに対して uncheckして終了としています。

※ラジオボタンとして使っているので、チェックを付ける段階で 事前に範囲内にチェックONのセルが2つ以上あることは無いという想定です。

ややモッタリした動きではありますが、ちゃんとに 今チェックを付けたセル以外に uncheckが適用されてますね。 さすがに大量データのラジオボタンってケースはないので この程度の処理速度なら許容範囲かと思います。

セルを1つずつ判定する方法でのラジオボタン化は実現できました。



2. リージョンを カレントセル以外の最大4つの 範囲に分割

2つ目は 1つずつセルのチェックボックス判定をするのではなく、最初から リージョンをカレントセル以外の区画に分割して、その区画に対して uncheck をいてしまう方法です。


リージョンを分割ってどうやるの?

リージョンを分割というのは、こんな感じです。範囲は四角である必要があるので 1セルを除いた残りは 最大4つに分割されます。

この1つの塊ではない 複数の範囲(Windowsだと Ctrlキー押しながら選択できる)は GAS上では RangeList という扱いになります。

前回も少し触れましたが、リファレンスに記載がある通り RangeListに対しても check, uncheck メソッドは使えます。これでいけそうですね!

RangeListは sheetクラスのメソッドで 通常は A1表記の配列で指定しますが、今回のようなケースは行、列の番号がそのまま使える R1C1表記 で指定した方が簡単でしょう。

R1CでもOK

必要となる 行、列の番号は 以下のように e.range  および region からget系メソッドで 取得できます。

つまり このような4つに分割した範囲 の位置情報(行・列)は、

↓ こんな感じに取得できるわけです。

たとえば 上の画像だと リージョン内で チェックされた カレントセルを除いた 「① 範囲 上」は、

r3c2:r5c8
つまり `r${startRow}c${startCol}:r${row-1}c${lastCol}`

このように表せます。

最大4つの範囲の r1c1表記を生成して、配列にプッシュしていけば、ラジオボタンとして チェックを外す (uncheckの)対象となる RangeList が生成できそうですね!

ただし、カレントセルのリージョン内の 位置 によっては 上部が無いとか左部がないといったケースがあるので、そこを ifで 捌く必要があります。



余談:テンプレートリテラルを使ってみよう

ちなみに 文字列内に変数を入れ込むのに 使っているのが、バッククォートを使った テンプレートリテラル という記述法です。

 `r${startRow}c${startCol}:r${row-1}c${lastCol}`
` ← これがバッククォート

バッククォートは  Shift + @マークのあるキー で打てます。

バッククォート内は、普通の改行や シングルクォート、ダブルクォートもそのまま 使えて、さらに  ${変数や式} といった形で 変数 や 関数、数式なんかも + で連結せず 文字列内に 入れ込めちゃいます。 

トラファルガー・ロー の「ルーム」内で「シャンブルズ」使えるみたいな、まさに チート領域ですね。

もちろん

"r" + startRow + "c" + startCol + ":r" + (row-1) + "c" + lastCol 

でも同じ結果が得られますが、Googleスプレッドシート でGASを使っていると、このバッククォートを使った テンプレートリテラルでの記述が必要になる時があります。

例えば Query関数を GASで 生成して setValue(または setFormula)しようとすると、式内に ダブルクォートもシングルクォートもあったりして、じゃあ全体をどう括るんじゃぃ!ってなります。 こんな時は バッククォートの出番ですね。

あとは、なんとなく + で繋げるより 玄人っぽい(かっこいい)ってのもあるかなとw  覚えておいて損はないです!


ラジオボタン コード その2 (分割方式)

分割処理をしたうえで uncheckする流れをコードにするとこんな感じ。

function onEdit(e){
  const range = e.range;
  if(range.isChecked() != true ) return;

  const sheet = range.getSheet();

  //チェックを入れたセルの行、列
  const row = range.rowStart;
  const col = range.columnStart;

  //チェックを入れたセルの リージョン の開始セルの行、列、終了セルの行、列を取得
  const region =range.getDataRegion(); 
  const startRow = region.getRow();
  const startCol = region.getColumn();
  const lastRow = region.getLastRow();
  const lastCol = region.getLastColumn();

  //対象を格納する ターゲット配列
  const target =[]; 

  //範囲1 上
  if(row > startRow){
    target.push(`r${startRow}c${startCol}:r${row-1}c${lastCol}`);
  }

  //範囲2 下
  if(row < lastRow){
    target.push(`r${row+1}c${startCol}:r${lastRow}c${lastCol}`);
  }

  //範囲3 左
  if(col > startCol){
    target.push(`r${row}c${startCol}:r${row}c${col-1}`);
  }
  
  //範囲4 右
  if(col < lastCol){
    target.push(`r${row}c${col+1}:r${row}c${lastCol}`);
  }

  //レンジリストのチェックを外す
  sheet.getRangeList(target).uncheck();
}

当然ですが、動きというかシート上の挙動は その1の判定型方式と変わらないです。モッタリ感も同じくらいですねw

これは RangeList に対する uncheck の動きが、結局は 範囲に対して順番に実行されているからです。並列で一気に処理されてるわけではないんですね。

ラジオボタンではないですが、以下のようなケースで試してみると動きがわかります。



おまけ:2つ方式 それぞれの動きを比較してみよう

せっかくなんで、2つの方式の処理を比べてみましょう。

判定方式の方のコードは、return の記述を削除して 1つ uncheckしても終わらず処理が継続されるように変更しています。

その1 判定方式
その2 分割方式

判定方式のほうは、一つ一つ処理してるのがわかりますね。目視できるレベルの動きは、

「ピ、ピ、ピ、ピピピピ 、テテーン、テテーン」

って感じで、欽ちゃんの仮装大賞 っぽいかもw

下の分割方式も、上、下、左、右の 順でブロックごとに処理されているのが、なんとなくわかります。

チェックボックスが多い場合は、分割方式のほうがマシってくらいでしょうか、もうちょい サクサクな動きだとよいんですが・・・。

せっかくなんで 最初のシンプルな コードも試してみましょう。

0. シンプル式

うーん、この 全部外してカレントセルだけチェック入れなおす、最初のシンプルなコードが 結局一番早いかもw

もちろんラジオボタンとして使うなら、こんなに複数のチェックボックスが既にチェックされた状態ってことは無いでしょうが。

とくにチェックした箇所が一旦消えて復活してもきにならない
→ 0.シンプル方式で

質問ごとの チェックボックスの数は多くない
→ 1.判定方式で

一つの質問の中にチェックボックスが多数ある
→ 2.分割方式で

こんな棲み分けでしょうか。あとは 使う人の お好みで使える場面があったら 活用ください!

ってまとめたけど、「実用的」と謳ったわりに、あんま実用的じゃなかったんじゃね?って言われそう・・・。



チェックボックス の一括 ON/OFF、今回の ラジオボタン化を通じて、 GASの理解が深まれば幸いです。

とりあえずは、 一番最初に紹介した GASを使わない

  • Ctrl + A の リージョン選択

  • スペースキーでの チェックボックス 一括ON/OFF

この2つはガチ便利なんで、これだけ覚えて活用するでもいいかも。

次回、番外編ってことで チェックボックスの小ネタをもう少しだけ。GASが続いたんで、GASを使わないネタ を幾つか紹介したいと思います。

意外とGASなしでも色々出来るという驚きと発見があるかも!?



■このシリーズの次の記事


この記事が気に入ったらサポートをしてみませんか?