[HTML, JavaScript]formaction動かない…

# はじめに

「登録する」ボタンを持つ登録フォームに「プレビュー」機能を追加するときのお話。

プレビュー機能はPOST送信したいので、1form内で2つのsubmitボタンを置きたい、という形になる。

```php
<form method="POST" action="./register">
    <input type="submit" value="登録する">
    // <input type="submit" value="プレビュー">  // 追加したい
</form>
```

少し前ではJSで遷移先を変更したり、サーバーにリクエスト送ってからsubmitされたinputのvalueの値で処理内容変えたりとかやりたいことのわりに結構面倒なことをしなくてはいけなかったらしい。

※参考：JSでの制御

[単一フォーム に 複数サブミット を配置して 遷移先 を切り替える 方法](https://garafu.blogspot.com/2017/04/one-form-multiple-submit.html)

※参考：サーバーでの制御（PHP）

[PHP - 複数のsubmitで処理を分ける](http://taustation.com/php-multiple-submits/)

ただ月日は流れ、HTML5で導入されたformactionというHTMLタグを使えばものすごく簡単に遷移先を変更できるとのことでウッキウキで実装してみた。

[: 入力欄 (フォーム入力) 要素 - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input#attr-formaction)

↓実際の使い方

[html5 で導入された button タグに付けれる formaction と formtarget 属性 を用いてプレビューを実現する - Qiita](https://qiita.com/yamotuki/items/e394bcc38614c091c7fd)

## 環境

- PHP 8.0.17
- Laravel 8.75

# formactionうごかなくね？

## 準備

実際にbladeファイルを以下のようにした

尚、Laravelを使っているので、いろんな補完機能を持つ`LaravelCollective`による実装になっています。

register.blade.php
```php
@extends('layouts.app')

@section('content')
		{{ Form::open(['method' => 'POST', 'route' => 'register']) }}
		...
		...
		{{ Form::submit('登録する') }}
		{{ Form::submit('プレビュー', ['formaction' => '/preview']) }}  // 追加
		{{ Form::close() }}
@endsection
```

ここでは「登録する」と「プレビュー」の２つのボタンが表示されるだけ。

そして、プレビューボタンを押して`./preview`にアクセスすることを確認すると、なんだか思った通りに動かない…

検証ツールでリクエスト先を見てみると

```
リクエスト URL: http://.../register
リクエスト メソッド: POST
```

と、`Form::open`で指定しているmethodとactionが反映されていた。

なので`formaction`が機能していないことになる。

## 原因

結論だけ言うと**formactionがプロジェクト内のJavaScriptと競合して、formactionの機能がJavaScriptに打ち消されてしまった**のが原因でした。

どういうことかというと、プロジェクト内で**submit押した際のダブルクリック対応**として、以下を導入していました。

[Laravel 5で確認画面付き問い合わせフォームを作る - Qiita](https://qiita.com/ponko2/items/fd7ac38b964e10f16f52#%E9%80%81%E4%BF%A1%E3%83%9C%E3%82%BF%E3%83%B3%E3%83%80%E3%83%96%E3%83%AB%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E3%81%A7%E3%81%AE%E4%BA%8C%E9%87%8D%E9%80%81%E4%BF%A1%E9%98%B2%E6%AD%A2)


```resources\js\app.js
$('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で打ち消してもらっては困るので、先ほどのダブルクリック対応内で遷移先を切り替える処理を追記

```resources\js\app.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
```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
```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不使用

生の<form>タグを用いて実装

→挙動変わらず。

register.blade.php
```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
```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が実用的じゃなくなる。。。

```php
<form method="POST" action="">
    <input type="submit" formaction="./register" value="登録する">
    <input type="submit" formaction="./preview" value="プレビュー"> // 追加
</form>
```

### プロジェクト以外のBlade内で実装

自分の学習用のLaravelで確認（バージョンは9.系）

→こちらは問題なく動くことを確認。HTMLディレクティブの使用がformactionが機能しない原因じゃなくてよかった、、、

```php
@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](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input#attr-formmethod)

- 上記は以下でのみ使える
    - 1. `<input type="submit">`
    - 2. `<input type="image">`
    - 3.`<button>`
- formに`target`付与できるの初めて知った。`target=”_blank”`が持つ脆弱性対策の`rel`属性も付与できる

最後まで読んでいただきありがとうございました。