Marketing HubCMS Hub

フォーム確認画面をjQueryだけで実装する

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

このように動作します。

フォーム確認画面のデモ フォーム確認画面のデモ

目次

実装の概要

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

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

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

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

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

実際のコード

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

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

執筆者:Admin

関連記事