ブログ

フォーム確認画面をjQueryだけで実装する|Tips|日本トップクラスのHubSpotテック企業 株式会社パンセ

作成者: Admin|Oct 9, 2019 3:00:00 PM

日本のwebの慣習として「フォーム送信の前に確認画面を表示させたい」というものがあります。HubSpot標準のフォームでは確認画面がありませんが、この確認画面は結構要望としていただくことが多いので、汎用的に使用できるようjQuery(1.11.2)で実装してみました。

このように動作します。

フォーム確認画面のデモ

目次

実装の概要

モジュール自体はカスタムモジュールで実装していますが、実際のフォームの送信はHubSpotのformタグ標準の挙動ではなく、Forms APIの1つである Submit form data を使用しています。このAPIは認証が不要なため、JavaScriptのみで完結しています。

カスタムモジュール内にはフォームフィールド1つのみ設置しており、ページ編集画面で設定できる

  • フォームの選択
  • 送信後の挙動の選択(別のページにリダイレクトか、メッセージをインライン表示)
  • リダイレクトの場合は内部ベージか、外部URLの選択

は全てサポートしています。

カスタムモジュールのフィールド構成

実際のコード

コードは次の通りです。カスタムモジュールのフィールドから値を受ける必要があるため、全てHTML + HUBL欄に記述します。JavaScriptにおいては、一応リビーリングモジュールパターンを採用しています。念のためIE11もサポートしています。

  
  1.  
  2. <script>
  3. (function() {
  4. const hsForm = (function () {
  5. let inputData = [];
  6. let $formWrapper;
  7. let $rootElm;
  8. let $formFields;
  9. let $submitBtn;
  10.  
  11. /**
  12. * 共通で使う変数のセット、HubSpot標準フォームの送信イベントを乗っ取るためのイニシャライズを行う
  13. * @param {String} selector
  14. */
  15. function initialize(selector) {
  16. $formWrapper = $(selector);
  17. $rootElm = $formWrapper.parent();
  18. $submitBtn = $formWrapper.find('.actions input[type=submit]');
  19.  
  20. $submitBtn.on('click', function(e) {
  21. e.preventDefault();
  22. // 依存フィールドなども考慮しこのタイミングでフィールド収集
  23. $formFields = $formWrapper.find('.hs-form-field');
  24. validateForms().done(function (){
  25. $('.js_hsFormErrorMessage').remove();
  26. inputData = returnFormData();
  27. showConfirmation(inputData);
  28. });
  29. validateForms().fail(function() {
  30. if ($('.js_hsFormErrorMessage').length < 1) {
  31. $('<p class="js_hsFormErrorMessage">入力項目を確認してください</p>').appendTo($rootElm);
  32. }
  33. });
  34. });
  35. }
  36.  
  37. /**
  38. * 各フォームパーツに.change()をかけ、HubSpotフォーム標準のバリデーションを呼び出す
  39. */
  40. function validateForms() {
  41. const defer = $.Deferred();
  42.  
  43. $formFields.each(function() {
  44. if ($(this).find('select').length) {
  45. $(this).find('select').trigger('change');
  46. } else if ($(this).find('textarea').length) {
  47. $(this).find('textarea').trigger('change');
  48. } else if ($(this).find('input[type=radio]:checked').length) {
  49. return true;
  50. } else {
  51. $(this).find('input').change();
  52. // TODO: Eメールが漏れることある
  53. }
  54. });
  55.  
  56. // エラーメッセージを確実に出してから判定したいためsetTimeout
  57. setTimeout(function() {
  58. if ($('.hs-error-msg').length) {
  59. defer.reject();
  60. } else {
  61. defer.resolve();
  62. }
  63. }, 100)
  64.  
  65. return defer.promise();
  66. }
  67.  
  68. /**
  69. * 確認画面の表示、またHubSpot CRMヘデータ送信するために各フォームからデータを収集し、オブジェクトを含んだ配列を返す
  70. * @return {Array} 各オブジェクトはlabel, name, value, valueLabelをキーに持つ
  71. */
  72. function returnFormData() {
  73. return $formFields.map(function() {
  74. let $formParts;
  75. let name;
  76. let valueLabel = [];
  77.  
  78. if ($(this).find('.hs-dateinput').length) {
  79. $formParts = $(this).find('input');
  80. } else if ($(this).find('select').length) {
  81. $formParts = $(this).find('select');
  82. const $option = $formParts.find('option[value=' + $formParts.val() + ']')
  83. valueLabel.push($option.text());
  84. } else if ($(this).find('textarea').length) {
  85. $formParts = $(this).find('textarea');
  86. } else if ($(this).find('input[type=radio]').length) {
  87. $formParts = $(this).find('input[type=radio]:checked');
  88. valueLabel.push($formParts.siblings('span').text());
  89. // チェックしていないときのために、:checked以外でnameを取得
  90. name = $(this).find('input[type=radio]').attr('name');
  91. } else if ($(this).find('input[type=checkbox]').length) {
  92. $formParts = $(this).find('input[type=checkbox]:checked');
  93. name = $(this).find('input[type=checkbox]').attr('name');
  94. $formParts.each(function() {
  95. valueLabel.push($(this).siblings('span').text());
  96. })
  97. } else {
  98. $formParts = $(this).find('input');
  99. }
  100.  
  101. const label = $(this).find('label span:first').text().replace(/\*/, '');
  102. const val = (function() {
  103. if ($formParts.length > 1) { // チェックボックスなど複数の値がある場合
  104. return $formParts.map(function() {
  105. return $(this).val();
  106. }).get();
  107. } else {
  108. return $formParts.val();
  109. }
  110. })();
  111.  
  112. return {
  113. label: label,
  114. name: name ? name : $formParts.attr('name'),
  115. value: val,
  116. valueLabel: valueLabel.join('、')
  117. }
  118. }).get();
  119. }
  120.  
  121. /**
  122. * 確認画面を生成する
  123. * @param {Array} data
  124. */
  125. function showConfirmation(data) {
  126. $formWrapper.hide();
  127.  
  128. // 確認用テーブルの生成、アペンド
  129. let $confirmationWrapper = $('<div></div>');
  130. let $table = $('<table></table>');
  131.  
  132. function generateConfirmationValue(inputDataItem) {
  133. if (inputDataItem.value === 'true') {
  134. return 'はい';
  135. } else if (inputDataItem.value === 'false') {
  136. return 'いいえ';
  137. } else {
  138. // プルダウンやラジオボタン、複数のチェックボックスなどはvalueに対応するラベルを表示する ex)man→男 woman→女
  139. if (inputDataItem.valueLabel) {
  140. return inputDataItem.valueLabel;
  141. }
  142. return inputDataItem.value ? inputDataItem.value : '';
  143. }
  144. }
  145.  
  146. data.forEach(function (item) {
  147. const val = generateConfirmationValue(item);
  148. $('<tr><th>' + item.label + '</th><td>' + val + '</td></tr>').appendTo($table)
  149. });
  150.  
  151. $table.appendTo($confirmationWrapper);
  152. $confirmationWrapper.appendTo($rootElm);
  153.  
  154. // 戻る/送信ボタンの生成、アペンド
  155. let $btnWrapper = $('<ul></ul>')
  156. const $prevBtn = $('<li><a href="#">戻る</a></li>');
  157. const $submitBtn = $('<li><a href="#">送信</a></li>');
  158. $prevBtn.on('click', function() {
  159. $confirmationWrapper.hide();
  160. $formWrapper.show();
  161. });
  162. $submitBtn.on('click', function() {
  163. sendDataToHS(data);
  164. });
  165.  
  166. $prevBtn.appendTo($btnWrapper);
  167. $submitBtn.appendTo($btnWrapper);
  168. $btnWrapper.appendTo($confirmationWrapper);
  169. }
  170.  
  171. /**
  172. * HubSpot CRMへデータ送信
  173. * @param {Array} data
  174. */
  175. function sendDataToHS(data) {
  176. // 入力のない項目を削除
  177. let filteredFormData = data.filter(function (item) {
  178. return item.value;
  179. });
  180. // HubSpot APIの形に合わせ不要なプロパティの削除、配列をセミコロンで結合
  181. const formData = filteredFormData.map(function (item) {
  182. delete item.label;
  183. delete item.valueLabel;
  184. if (Array.isArray(item.value)) {
  185. item.value = item.value.join(';');
  186. }
  187. return item;
  188. });
  189. function getCookieAsObj(){
  190. if(document.cookie === '') return false;
  191. let obj = {}
  192. document.cookie.split(';').forEach(function(str) {
  193. const keyValArr = str.split('=');
  194. obj[keyValArr[0].replace(/ +/g, '')] = decodeURIComponent(keyValArr[1])
  195. })
  196. return obj;
  197. }
  198.  
  199. const sendData = {
  200. fields: formData,
  201. context: {
  202. hutk: getCookieAsObj().hubspotutk,
  203. pageUri: 'https://demo.100inc.co.jp/blog/tips/build-form-confirmation-preview-with-jquery',
  204. pageName: 'フォーム確認画面をjQueryだけで実装する|Tips|日本トップクラスのHubSpotテック企業 株式会社パンセ'
  205. },
  206. }
  207.  
  208. $.ajax({
  209. type: 'POST',
  210. url: 'https://api.hsforms.com/submissions/v3/integration/submit/5692228/',
  211. dataType: 'json',
  212. headers: {
  213. 'Content-Type':'application/json'
  214. },
  215. data: JSON.stringify(sendData)
  216. })
  217. .done(function (data) {
  218. afterSubmit();
  219. })
  220. .fail(function (jqXHR, textStatus, err) {
  221. console.error(textStatus);
  222. if ($('.js_hsFormErrorMessage').length < 1) {
  223. $('<p class="js_hsFormErrorMessage">データの送信に失敗しました。インターネットの接続状況をご確認いただくか、お手数ですが時間をおいて再度お試しください。</p>').appendTo($rootElm);
  224. }
  225. });
  226. }
  227.  
  228. /**
  229. * ページ編集画面のカスタムモジュールの設定に基づいた、フォーム送信後の挙動を実現
  230. */
  231. function afterSubmit() {
  232. if ('' === 'redirect') {
  233. location.href = '';
  234. } else {
  235. $rootElm.children().remove();
  236. $('').appendTo($rootElm);
  237. }
  238. }
  239.  
  240. return {
  241. init: initialize
  242. }
  243. })()
  244.  
  245. window.addEventListener('message', event => {
  246. if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormReady') {
  247. hsForm.init('.hsForm_');
  248. }
  249. });
  250. })();
  251. </script>

プライベート変数宣言とinitialize関数

最初にまず、各関数で共通して使用するプライベート変数を宣言します。頭に「$」が付いているものにはjQueryオブジェクトが入ります。initialize関数にて変数に値を代入し、早速HubSpotのフォーム標準のsubmitイベントをジャックします。

validateForms関数を実行し、問題無ければフォームからデータを収集して送信、バリデーションにエラーがあればエラーメッセージを表示します。

validateForms関数

念のためIE11もサポート対象としたので、不本意ながらPromiseではなくjQuery Deferredによる非同期実装としています。バリデーション自体を独自実装したくないため、各フォームパーツでchangeイベントを呼び、HubSpotのフォーム標準のバリデーションを呼び出すようにしています。

不格好ではありますがさっくり確実性を期すためsetTimeout内でHubSpotフォーム標準のエラーメッセージが出力されていないかチェックし、rejectかresolveを実行します。

returnFormData関数

フォームからデータを収集して、各フォームパーツのデータをオブジェクトとした配列を返します。確認画面の出力のためにラベルを取得しなければならず、結構泥臭いコードになっています。

showConfirmation関数

本記事の主役である確認画面を生成します。フォームを非表示にし、returnFormData関数で組み立てたデータからHTMLをゴリゴリ生成します。valueをそのまま表示できないラジオボタンなどがなんとも面倒ですね。戻るボタンをクリックしたら確認画面を非表示にし、フォームを再度表示させます。

sendDataToHS関数

Submit form data  APIに実際にデータを送信する関数です。引数で受け取ったデータの中には確認画面表示のためのプロパティなどもありますので、いらないものを削除します。また、適宜HubSpotが受け取る形に合わせてデータを整形します。HubSpotが受け取るデータの形については、「 HubSpot APIの使い方 」ページの末尾の付録をご参照ください。

送信に成功したら後処理のafterSubmit関数を呼びますが、失敗した場合は申し訳程度のエラーメッセージを表示します。

afterSubmit関数

カスタムモジュールの設定に合わせて、リダイレクトするかインラインメッセージを表示するかします。

まとめ

以上、簡単ですがHubSpotのフォームに手軽に確認画面を実装する方法をご紹介しました。もしかしたら細かいバグなどあるかもしれませんが、おおよそ問題ないかと思います。※コピペ&バグが原因で起こった問題に関しては責任を取れませんので、自己責任のもとご利用くださいませ。