この記事は、Zennに投稿した以下の記事の再投稿になります
対象者
- セレクトボックスの選択肢の状態を動的に変化させたい人
この記事でやること
選択肢1つがランダムでdisabled
になるセレクトボックスを作ります。
動作環境・前提
バージョン | |
---|---|
MacOS | 12.6.3 |
Symfony | 4.x |
本題
次の手順で実装していきます。
- Controllerを作成する
- twigファイルを編集する
disabled
にする選択肢をランダムに指定する- JavaScriptで
<option>
を動的にdisabled
にする
1. Controllerを作成する
次のコマンドでControllerを作成します。
コマンドを実行すると「Controllerの名前を決めてね」と言われるので、適当な名前を入力してください。以下の例ではIndexController
としています。
$ php bin/console make:controller
Choose a name for your controller class (e.g. AgreeableGnomeController):
> IndexController
成功したら、Controllerとtwigファイルが作成されます。
作成されたController(パスはsrc/Controller/IndexController.php
)を編集して、フォームの実装を行います。
ここでは、シンプルに3つの選択肢を持つセレクトボックスを実装します。プレースホルダーなどはお好みで追加してください。
/**
* @Route("/index", name="index")
*/
public function index(): Response
{
// フォームの実装
// 3つの選択肢を持つセレクトボックス
$form = $this->createFormBuilder()
->add('select_box', ChoiceType::class, [
'choices' => [
'select A' => 1,
'select B' => 2,
'select C' => 3,
],
])
->getForm();
return $this->render('index/index.html.twig', [
'form' => $form->createView(),
]);
}
2. twigファイルを編集する
1で作成されたtwig(パスはtemplates/index/index.html.twig
)ファイルを編集して、フォームを表示できるようにします。
body
ブロックの中にmain
ブロックを作成し、フォームを表示するコードを追加していきます。
{% block body %}
{% block main %}
{# フォームを表示するコードを追加 #}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endblock %}
{% endblock %}
これで、フォームを表示できるようになりました。
http://localhost:8001/index
などにアクセスし、画面を確認します。以下のようにセレクトボックスが表示されればOKです。
3. `disabled`にする選択肢をランダムに指定する
random_int()
を使ってdisabled
にする選択肢をランダムに指定するようにします。
Controllerでランダムな数字を生成して、twigファイルに渡します。return
で返す要素に次を加えてください。
return $this->render('index/index.html.twig', [
'form' => $form->createView(),
// 以下を追加
'disabled_select' => random_int(0, 2),
]);
今回は3つの選択肢を持つセレクトボックスを実装しているので、0~2の間でランダムな数字を生成しています。
あとは、この数字を使って選択肢をdisabled
にするコードをJavaScriptで実装するだけです。
4. JavaScriptで`
手順2で編集したtwigファイルに、JavaScriptのコードを追加していきます。
body
ブロックの中にjavascript
ブロックを作成し、以下のコードを追加してください。
{% block body %}
{% block javascript %}
<script>
window.addEventListener('load', function() {
const $disabledSelectNum = '{{disabled_select}}';
$selectBox = document.getElementById('form_select_box');
$options = $selectBox.options;
$options[$disabledSelectNum].disabled = true;
}, false);
</script>
{% endblock javascript %}
{% block main %}
{# 以下略... #}
<script>
タグの中で何をしているか説明します。
まず初めに、addEventListener()
メソッドが出てきます。これは、ターゲットに対して第一引数で指定した何らかのイベントが配信される度に呼び出され、第二引数で指定した関数を実行するメソッドです。
今回はターゲットがwindow
、第一引数が'load'
ですので、画面がロードされる度に第二引数で定義した関数が実行されることになりますね。そして、この第二引数で定義しているのが今回のテーマである「選択肢を動的に選択不可にする」部分です。
以下は、addEventListener()
メソッドの第二引数だけを取り出したものです。
function() {
const $disabledSelectNum = '{{disabled_select}}';
$selectBox = document.getElementById('form_select_box');
$options = $selectBox.options;
$options[$disabledSelectNum].disabled = true;
}
2行目では、IndexController
のindex()
メソッドから返したrandom_int(0, 2)
の値をJSの変数$disabledSelectNum
に格納しています。この$disabledSelectNum
の値を使って、選択不可にする選択肢を指定します。
3行目では、getElementById()
メソッドで取得したドキュメント要素を変数$selectBox
に格納しています。ここでは、セレクトボックス(<select>
)のid
を指定する必要がありますので、デベロッパーツールなどでid
を確認して、適切なものを指定してください。
4行目は、3行目で取得したセレクトボックスの選択肢(options)を取り出して、変数$options
に格納するコードです。$options
は配列になっており、配列の要素として<option>
のドキュメント要素が1つずつ格納されています。
最後に5行目で上記までのコードで取得した<option>
のドキュメント要素を使って、選択肢を非表示にします。$disabledSelectNum
で$options
の配列要素の1つを指定し、disabled
をtrueにすることで、<option>
の1つを非表示にすることができます。
これでコードは完成です。
http://localhost:8001/index
などにアクセスし、画面を確認してみてください。選択肢のうち1つがdisabled
になっているはずです。
また、画面をリロードするとdisabled
になる選択肢がランダムに切り替わると思います。
まとめ
以下は、ここまで説明してきたコードの全容をです。
手順通りに作ると、大体こんな感じのコードになっているはずです。
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class IndexController extends AbstractController
{
/**
* @Route("/index", name="index")
*/
public function index(): Response
{
$form = $this->createFormBuilder()
->add('select_box', ChoiceType::class, [
'choices' => [
'select A' => 1,
'select B' => 2,
'select C' => 3,
],
])
->getForm();
return $this->render('index/index.html.twig', [
'form' => $form->createView(),
'disabled_select' => random_int(0, 2),
]);
}
}
{% extends 'base.html.twig' %}
{% block title %}Hello Issue2Controller!{% endblock %}
{% block body %}
{% block javascript %}
<script>
window.addEventListener('load', function() {
const $disabledSelectNum = '{{disabled_select}}';
$selectBox = document.getElementById('form_select_box');
$options = $selectBox.options;
$options[$disabledSelectNum].disabled = true;
}, false);
</script>
{% endblock javascript %}
{% block main %}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endblock %}
{% endblock %}
実用例
たとえば、
- 予約フォームを作る際、既に予約がいっぱいになっている時間は選択できないようにする
- 画面を見ているユーザのロールに応じて選べる要素が変わる
最後に
ユーザのロールに応じて選択肢の1つを選択不可にするコードを書こうとした時に、選択肢1つだけをdisabled
にする方法が分からずとても苦戦していました。どうやってもセレクトボックス全体にdisabled
がかかるか、どこにもdisabled
がかからないかのどちらかにしかならなかったんです。
悩んでいた当時は、SymfonyのaddEventListener()
とかでできるんじゃないかと格闘していましたが、結局できずにJSでやる方法に落ち着きました。
公式ドキュメントのFormEvents::PRE_SET_DATA
の説明には
イベントはメソッドFormEvents::PRE_SET_DATAの開始時に送出されます Form::setData()。次の用途に使用できます。
・事前入力中に指定されたデータを変更します。
・事前入力されたデータに応じてフォームを変更します (フィールドを動的に追加または削除します)。
とあったので、Symfonyの機能だけでやる方法もありそうと思ったんですが。。。
いつかまた今回のような機能が必要になったら、この辺りをもう少し調べてみたいですね。
もし、Symfonyの機能だけでやる方法をご存知の方がここまで読んでくださっていたなら、是非コメントでご教示いただけると嬉しいです。