はじめに
「登録する」ボタンを持つ登録フォームに「プレビュー」機能を追加するときのお話。
プレビュー機能はPOST送信したいので、1form内で2つのsubmitボタンを置きたい、という形になる。
<form method="POST" action="./register">
<input type="submit" value="登録する">
// <input type="submit" value="プレビュー"> // 追加したい
</form>
少し前ではJSで遷移先を変更したり、サーバーにリクエスト送ってからsubmitされたinputのvalueの値で処理内容変えたりとかやりたいことのわりに結構面倒なことをしなくてはいけなかったらしい。
※参考:JSでの制御
単一フォーム に 複数サブミット を配置して 遷移先 を切り替える 方法
※参考:サーバーでの制御(PHP)
ただ月日は流れ、HTML5で導入されたformactionというHTMLタグを使えばものすごく簡単に遷移先を変更できるとのことでウッキウキで実装してみた。
: 入力欄 (フォーム入力) 要素 - HTML: HyperText Markup Language | MDN
↓実際の使い方
html5 で導入された button タグに付けれる formaction と formtarget 属性 を用いてプレビューを実現する - Qiita
環境
- PHP 8.0.17
- Laravel 8.75
formactionうごかなくね?
準備
実際にbladeファイルを以下のようにした
尚、Laravelを使っているので、いろんな補完機能を持つLaravelCollective
による実装になっています。
register.blade.php
@extends('layouts.app')
@section('content')
{{ Form::open(['method' => 'POST', 'route' => 'register']) }}
...
...
{{ Form::submit('登録する') }}
{{ Form::submit('プレビュー', ['formaction' => '/preview']) }} // 追加
{{ Form::close() }}
@endsection
ここでは「登録する」と「プレビュー」の2つのボタンが表示されるだけ。
そして、プレビューボタンを押して./preview
にアクセスすることを確認すると、なんだか思った通りに動かない…
検証ツールでリクエスト先を見てみると
リクエスト URL: http://.../register
リクエスト メソッド: POST
と、Form::open
で指定しているmethodとactionが反映されていた。
なのでformaction
が機能していないことになる。
原因
結論だけ言うとformactionがプロジェクト内のJavaScriptと競合して、formactionの機能がJavaScriptに打ち消されてしまったのが原因でした。
どういうことかというと、プロジェクト内でsubmit押した際のダブルクリック対応として、以下を導入していました。
Laravel 5で確認画面付き問い合わせフォームを作る - Qiita
$('form :submit').click(function (event) {
var TIMEOUT = 10000;
var target = event.target;
var $form = $(target).closest('form');
var $submit = $form.find(':submit');
...
...
...
$form.submit();
});
これによりsubmit押した際にsubmitボタンにdisabled
属性が付与されダブルクリックできない、という機能。
ここでformの操作をしていく中でformactionで指定した遷移先がformのactionで指定した遷移先に切り替わり、 http://.../register
へアクセスしてしまった、という流れでした。
対策
JSで打ち消してもらっては困るので、先ほどのダブルクリック対応内で遷移先を切り替える処理を追記
$('form :submit').click(function (event) {
var TIMEOUT = 10000;
var target = event.target;
var $form = $(target).closest('form');
var $submit = $form.find(':submit');
...
// 追加: ダブルクリック防止対応により本来のformactionの機能を打ち消してしまうのでここで代入する
if (target.name === 'preview') {
$form.attr('action', target.formAction)
}
...
$form.submit();
});
<input type="submit">
のボタンがname=”preview”
であればformactionで指定した遷移先でsubmitするように変更
register.blade.php
@extends('layouts.app')
@section('content')
<form method="POST" action="./register">
<input type="submit" value="登録する">
<input type="submit" formaction="./preview" name="preview" value="プレビュー"> // 修正
</form>
@endsection
プレビューボタンにname=”preview”
を追加
これで無事プレビューボタンを押すと、./previrew
へ遷移するようになりました!
ちなみに、LaravelCollectiveの書き方でも問題なく動作します。
register.blade.php
@extends('layouts.app')
@section('content')
{{ Form::open(['method' => 'POST', 'route' => 'register']) }}
...
...
{{ Form::submit('登録する') }}
{{ Form::submit('プレビュー', ['formaction' => '/preview', 'name' => 'preview']) }} // 修正
{{ Form::close() }}
@endsection
備考:他の試したこと
formactionが動かなかった時にいくつか試してみたこと
LaravelCollective不使用
生の
タグを用いて実装→挙動変わらず。
register.blade.php
@extends('layouts.app')
@section('content')
<form method="POST" action="./register">
<input type="submit" value="登録する">
<input type="submit" formaction="./preview" value="プレビュー"> // 追加
</form>
@endsection
submit毎にformactionを指定
→挙動変わらず
register.blade.php
@extends('layouts.app')
@section('content')
<form method="POST" action="">
<input type="submit" formaction="./register" value="登録する">
<input type="submit" formaction="./preview" value="プレビュー"> // 追加
</form>
@endsection
HTMLディレクティブを削除
→これでやっとformactionが動くことを確認
ただ、Laravelでは必須のHTMLディレクティブとの併用がいけないのか確認したかった
使えないとなるとformactionが実用的じゃなくなる。。。
<form method="POST" action="">
<input type="submit" formaction="./register" value="登録する">
<input type="submit" formaction="./preview" value="プレビュー"> // 追加
</form>
プロジェクト以外のBlade内で実装
自分の学習用のLaravelで確認(バージョンは9.系)
→こちらは問題なく動くことを確認。HTMLディレクティブの使用がformactionが機能しない原因じゃなくてよかった、、、
@extends('layouts.app')
@section('content')
<form method="POST" action="">
<input type="submit" formaction="./register" value="登録する">
<input type="submit" formaction="./preview" value="プレビュー"> // 追加
</form>
@endsection
ココまでくるとプロジェクト内特有のソースコードが原因で機能していない、というところまでたどり着き今回の原因特定に至ったわけです。
終わりに
- formaction以外にも、送信方式を変更する
formmethod
、target属性を指定できるformtarget
がある
: 入力欄 (フォーム入力) 要素 - HTML: HyperText Markup Language | MDN
- 上記は以下でのみ使える
-
<input type="submit">
-
<input type="image">
- 3.
<button>
-
- formに
target
付与できるの初めて知った。target=”_blank”
が持つ脆弱性対策のrel
属性も付与できる
最後まで読んでいただきありがとうございました。