フォーム確認画面をjQueryだけで実装する
日本のwebの慣習として「フォーム送信の前に確認画面を表示させたい」というものがあります。HubSpot標準のフォームでは確認画面がありませんが、この確認画面は結構要望としていただくことが多いので、汎用的に使用できるようjQuery(1.11.2)で実装してみました。
このように動作します。
フォーム確認画面のデモ
目次
実装の概要
モジュール自体はカスタムモジュールで実装していますが、実際のフォームの送信はHubSpotのformタグ標準の挙動ではなく、Forms APIの1つである Submit form data を使用しています。このAPIは認証が不要なため、JavaScriptのみで完結しています。
カスタムモジュール内にはフォームフィールド1つのみ設置しており、ページ編集画面で設定できる
- フォームの選択
- 送信後の挙動の選択(別のページにリダイレクトか、メッセージをインライン表示)
- リダイレクトの場合は内部ベージか、外部URLの選択
は全てサポートしています。
カスタムモジュールのフィールド構成
実際のコード
コードは次の通りです。カスタムモジュールのフィールドから値を受ける必要があるため、全てHTML + HUBL欄に記述します。JavaScriptにおいては、一応リビーリングモジュールパターンを採用しています。念のためIE11もサポートしています。
{% form "my_form" form_to_use='{{ module.form.form_id }}' %} <script> (function() { const hsForm = (function () { let inputData = []; let $formWrapper; let $rootElm; let $formFields; let $submitBtn; /** * 共通で使う変数のセット、HubSpot標準フォームの送信イベントを乗っ取るためのイニシャライズを行う * @param {String} selector */ function initialize(selector) { $formWrapper = $(selector); $rootElm = $formWrapper.parent(); $submitBtn = $formWrapper.find('.actions input[type=submit]'); $submitBtn.on('click', function(e) { e.preventDefault(); // 依存フィールドなども考慮しこのタイミングでフィールド収集 $formFields = $formWrapper.find('.hs-form-field'); validateForms().done(function (){ $('.js_hsFormErrorMessage').remove(); inputData = returnFormData(); showConfirmation(inputData); }); validateForms().fail(function() { if ($('.js_hsFormErrorMessage').length < 1) { $('<p class="js_hsFormErrorMessage">入力項目を確認してください</p>').appendTo($rootElm); } }); }); } /** * 各フォームパーツに.change()をかけ、HubSpotフォーム標準のバリデーションを呼び出す */ function validateForms() { const defer = $.Deferred(); $formFields.each(function() { if ($(this).find('select').length) { $(this).find('select').trigger('change'); } else if ($(this).find('textarea').length) { $(this).find('textarea').trigger('change'); } else if ($(this).find('input[type=radio]:checked').length) { return true; } else { $(this).find('input').change(); // TODO: Eメールが漏れることある } }); // エラーメッセージを確実に出してから判定したいためsetTimeout setTimeout(function() { if ($('.hs-error-msg').length) { defer.reject(); } else { defer.resolve(); } }, 100) return defer.promise(); } /** * 確認画面の表示、またHubSpot CRMヘデータ送信するために各フォームからデータを収集し、オブジェクトを含んだ配列を返す * @return {Array} 各オブジェクトはlabel, name, value, valueLabelをキーに持つ */ function returnFormData() { return $formFields.map(function() { let $formParts; let name; let valueLabel = []; if ($(this).find('.hs-dateinput').length) { $formParts = $(this).find('input'); } else if ($(this).find('select').length) { $formParts = $(this).find('select'); const $option = $formParts.find('option[value=' + $formParts.val() + ']') valueLabel.push($option.text()); } else if ($(this).find('textarea').length) { $formParts = $(this).find('textarea'); } else if ($(this).find('input[type=radio]').length) { $formParts = $(this).find('input[type=radio]:checked'); valueLabel.push($formParts.siblings('span').text()); // チェックしていないときのために、:checked以外でnameを取得 name = $(this).find('input[type=radio]').attr('name'); } else if ($(this).find('input[type=checkbox]').length) { $formParts = $(this).find('input[type=checkbox]:checked'); name = $(this).find('input[type=checkbox]').attr('name'); $formParts.each(function() { valueLabel.push($(this).siblings('span').text()); }) } else { $formParts = $(this).find('input'); } const label = $(this).find('label span:first').text().replace(/\*/, ''); const val = (function() { if ($formParts.length > 1) { // チェックボックスなど複数の値がある場合 return $formParts.map(function() { return $(this).val(); }).get(); } else { return $formParts.val(); } })(); return { label: label, name: name ? name : $formParts.attr('name'), value: val, valueLabel: valueLabel.join('、') } }).get(); } /** * 確認画面を生成する * @param {Array} data */ function showConfirmation(data) { $formWrapper.hide(); // 確認用テーブルの生成、アペンド let $confirmationWrapper = $('<div></div>'); let $table = $('<table></table>'); function generateConfirmationValue(inputDataItem) { if (inputDataItem.value === 'true') { return 'はい'; } else if (inputDataItem.value === 'false') { return 'いいえ'; } else { // プルダウンやラジオボタン、複数のチェックボックスなどはvalueに対応するラベルを表示する ex)man→男 woman→女 if (inputDataItem.valueLabel) { return inputDataItem.valueLabel; } return inputDataItem.value ? inputDataItem.value : ''; } } data.forEach(function (item) { const val = generateConfirmationValue(item); $('<tr><th>' + item.label + '</th><td>' + val + '</td></tr>').appendTo($table) }); $table.appendTo($confirmationWrapper); $confirmationWrapper.appendTo($rootElm); // 戻る/送信ボタンの生成、アペンド let $btnWrapper = $('<ul></ul>') const $prevBtn = $('<li><a href="#">戻る</a></li>'); const $submitBtn = $('<li><a href="#">送信</a></li>'); $prevBtn.on('click', function() { $confirmationWrapper.hide(); $formWrapper.show(); }); $submitBtn.on('click', function() { sendDataToHS(data); }); $prevBtn.appendTo($btnWrapper); $submitBtn.appendTo($btnWrapper); $btnWrapper.appendTo($confirmationWrapper); } /** * HubSpot CRMへデータ送信 * @param {Array} data */ function sendDataToHS(data) { // 入力のない項目を削除 let filteredFormData = data.filter(function (item) { return item.value; }); // HubSpot APIの形に合わせ不要なプロパティの削除、配列をセミコロンで結合 const formData = filteredFormData.map(function (item) { delete item.label; delete item.valueLabel; if (Array.isArray(item.value)) { item.value = item.value.join(';'); } return item; }); function getCookieAsObj(){ if(document.cookie === '') return false; let obj = {} document.cookie.split(';').forEach(function(str) { const keyValArr = str.split('='); obj[keyValArr[0].replace(/ +/g, '')] = decodeURIComponent(keyValArr[1]) }) return obj; } const sendData = { fields: formData, context: { hutk: getCookieAsObj().hubspotutk, pageUri: '{{ content.absolute_url }}', pageName: '{{ page_meta.html_title }}' }, } $.ajax({ type: 'POST', url: 'https://api.hsforms.com/submissions/v3/integration/submit/{{ hub_id }}/{{ module.form.form_id }}', dataType: 'json', headers: { 'Content-Type':'application/json' }, data: JSON.stringify(sendData) }) .done(function (data) { afterSubmit(); }) .fail(function (jqXHR, textStatus, err) { console.error(textStatus); if ($('.js_hsFormErrorMessage').length < 1) { $('<p class="js_hsFormErrorMessage">データの送信に失敗しました。インターネットの接続状況をご確認いただくか、お手数ですが時間をおいて再度お試しください。</p>').appendTo($rootElm); } }); } /** * ページ編集画面のカスタムモジュールの設定に基づいた、フォーム送信後の挙動を実現 */ function afterSubmit() { if ('{{ module.form.response_type }}' === 'redirect') { location.href = '{{ module.form.redirect_id ? page_by_id(module.form.redirect_id).absoluteUrl : module.form.redirect_url }}'; } else { $rootElm.children().remove(); $('{{ module.form.message|replace("\n", "") }}').appendTo($rootElm); } } return { init: initialize } })() window.addEventListener('message', event => { if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormReady') { hsForm.init('.hsForm_{{ module.form.form_id }}'); } }); })(); </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のフォームに手軽に確認画面を実装する方法をご紹介しました。もしかしたら細かいバグなどあるかもしれませんが、おおよそ問題ないかと思います。※コピペ&バグが原因で起こった問題に関しては責任を取れませんので、自己責任のもとご利用くださいませ。