作りたいものがありすぎる

40歳を過ぎてプログラミングを始めた人の顛末とこれからなど

Laravel フォームで配列を扱う ヘルパ関数old() でチェックボックスを扱う方法

最初の記事がとてもバズり、おかげ様でこの週のはてなブログのランキングに乗ることができました。ありがとうございます。
今回は具体的な技術のTIPSエントリーとなります。こんな感じの記事の時もありますし、おっさんらしく蘊蓄をたれたり心構え的なものや日記みたいな事も記事にしていこうと思ってますので、よかったら今後もお付き合いいただければ幸いです。





フォームのチェックボックスで、DBの boolean 値 を変更させるユーザーフォームを作っていた際、ヘルパ関数の old() やPOSTすべき値で散々悩んだので、うまくった例を記述します。

実装したフォームの参考画像
実装例、『非表示』とあるチェックボックスのバリデート時にリダイレクトされた際に、ユーザーのチェックした内容をどう保持するか、という問題です。

対象はユーザーフォームの 『端末を非表示にする』 というチェックボックスカラム名は hide で 1なら非表示 0なら表示としてます。 しかもこのフォームでは複数端末が表示されるので、配列で扱うというめんどくさい状態。さらに言うなら、DBの値を引っ張りだしてきて、 true なら checkd="checked" を表示させる。 さらに バリデートエラーの際はヘルパ関数 old() でチェックの状態を保持する、という  boolean が何重にも絡んでくるややこしい状態で問題の整理にかなりの時間を要しました。
前提条件としてhtmlのformで配列を扱う際には foreachなどで連続して同じ様なformを出力する必要がある場合があります。
その際に name等は以下の様に配列として扱います。
name="hide[]"

また、連想配列で扱いたい際は
name="hide[key]"
と、書いて扱います。

参考サイト
FormのPOST送信で配列を、できれば連想配列を送信したい。

以上を踏まえた上で先に結論です。

@php
if(
    ($mac_add->hide == 1 && old('mac_address.'.$mac_add->id.'.hide') == null) ||
    old('mac_address.'.$mac_add->id.'.hide') == 1
) {
    $check_hide[$mac_add->id] = "checked='checked'";
} else {
    $check_hide[$mac_add->id] = "";
}
@endphp
<!-- チェックされていない場合は0を送信 -->
<input type="hidden" name="mac_address[{{$mac_add->id}}][hide]" value="0">
<input type="checkbox" name="mac_address[{{$mac_add->id}}][hide]" value="1" id="devise-check-{{$mac_add->id}}" {{$check_hide[$mac_add->id]}}>

viewに @php で直接設定書いてますが、さんざん悩んだので、これ以上いじりたくないという状態です。 あと、三項演算子が苦手で、htmlの中にIF文書きたくないのです。

このphpコードは要は最初のifで チェックボックスに印をつけるか否かの判定をしているのみです。
$check_hide[] という配列に html の"checked='checked'" を書くべきか否かを処理してます。

以下はヘルパ関数 old() で取れる hide の配列内の値です。
'mac_address.'.$mac_add->id.'.hide'

これは、以下の様に解釈します。
array[id][hide]

端末情報 mac_address の配列情報の中に端末の固有IDをキーとして配列を保持させ、さらにその中の hide キーの中身を呼び出してます。
じつは、array[][hide] という風に配列を空にして、キーを自動で入る通し番号にしても良い気がしますが、controller側に渡して処理する際に 配列キーがDBのプライマリキーのIDだと、何かと都合が良いのでこうしてます。

この値はバリデートエラーなどでセッションに初めて保持される値で、普段は null です。
従って以下は フォームの値が 1 の状態か、 old() の値が無いなら、 true となります。
($mac_add->hide == 1 && old('mac_address.'.$mac_add->id.'.hide') == null)

さらにif関数の論理式でor条件を立ててます。
old('mac_address.'.$mac_add->id.'.hide') == 1

単純に、 old の値で 1、つまり true が入っているかを判定。 このいずれかに該当すれば、非表示状態の設定とみなし "checked='checked'" がフォーム内に描画され、チェックが入ります。

さらに下のhtmlフォームです。

<!-- チェックされていない場合は0を送信 -->
<input type="hidden" name="mac_address[{{$mac_add->id}}][hide]" value="0">
<input type="checkbox" name="mac_address[{{$mac_add->id}}][hide]" value="1" {{$check_hide[$mac_add->id]}}>

チェックボックスの前の行にhiddenでcheckboxに同じ name="" を付けてますが、これがちょっとややこしい事になります。 知っている人には当たり前かもしれませんが、実はチェックボックスのフォームって、チェックを入れないと、POSTされないそうです。 つまり、このままですと、ユーザーが空のフォームにチェックした情報は取れますが、ユーザーが意図的にチェックを外した。という情報が取れない訳です。 そこで、 hiddenを chekboxの前に書いて、もしユーザーがチェックを入れてない場合は、 name="hide" に 0 の値をPOSTしてやる、という事をしています。 htmlフォームで name が同じものが2つ以上あった場合は、後に書いた方の値が飛ぶ、という仕様をハックして、普段は下のフォームの値が飛び、いざチェックが外された際はhidden の値が飛ぶ、という訳です。

詳しくは以下を参照
[HTML]formでcheckboxにチェックしていない時にもパラメータを送る方法 isket.jp

これで各フォームの name="hide" に 0と1の状態がPOSTされる状態が表現できました。あとはバリデートエラー等で、再度フォームを描画する際に old 関数の値を引っ張り出すことで、フォーム直前の状態が 0か1がわかり、それを元に "checked='checked'" を入れるか入れないかを判定すれば良い。ということですね。

参考サイト

Laravelで多次元配列のバリデーション
qiita.com

checked="checked"はどうやって表示させるの? qiita.com

Laravelで、チェックボックスをPOSTした後の old() 問題 qiita.com

次回は軽めの記事にしようと思います。