日本のwebの慣習として「フォーム送信の前に確認画面を表示させたい」というものがあります。HubSpot標準のフォームでは確認画面がありませんが、この確認画面は結構要望としていただくことが多いので、汎用的に使用できるようjQuery(1.11.2)で実装してみました。
このように動作します。
モジュール自体はカスタムモジュールで実装していますが、実際のフォームの送信はHubSpotのformタグ標準の挙動ではなく、Forms APIの1つである Submit form data を使用しています。このAPIは認証が不要なため、JavaScriptのみで完結しています。
カスタムモジュール内にはフォームフィールド1つのみ設置しており、ページ編集画面で設定できる
は全てサポートしています。
コードは次の通りです。カスタムモジュールのフィールドから値を受ける必要があるため、全てHTML + HUBL欄に記述します。JavaScriptにおいては、一応リビーリングモジュールパターンを採用しています。念のためIE11もサポートしています。
- <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: 'https://demo.100inc.co.jp/blog/tips/build-form-confirmation-preview-with-jquery',
- pageName: 'フォーム確認画面をjQueryだけで実装する|Tips|日本トップクラスのHubSpotテック企業 株式会社パンセ'
- },
- }
- $.ajax({
- type: 'POST',
- url: 'https://api.hsforms.com/submissions/v3/integration/submit/5692228/',
- 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 ('' === 'redirect') {
- location.href = '';
- } else {
- $rootElm.children().remove();
- $('').appendTo($rootElm);
- }
- }
- return {
- init: initialize
- }
- })()
- window.addEventListener('message', event => {
- if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormReady') {
- hsForm.init('.hsForm_');
- }
- });
- })();
- </script>
最初にまず、各関数で共通して使用するプライベート変数を宣言します。頭に「$」が付いているものにはjQueryオブジェクトが入ります。initialize関数にて変数に値を代入し、早速HubSpotのフォーム標準のsubmitイベントをジャックします。
validateForms関数を実行し、問題無ければフォームからデータを収集して送信、バリデーションにエラーがあればエラーメッセージを表示します。
念のためIE11もサポート対象としたので、不本意ながらPromiseではなくjQuery Deferredによる非同期実装としています。バリデーション自体を独自実装したくないため、各フォームパーツでchangeイベントを呼び、HubSpotのフォーム標準のバリデーションを呼び出すようにしています。
不格好ではありますがさっくり確実性を期すためsetTimeout内でHubSpotフォーム標準のエラーメッセージが出力されていないかチェックし、rejectかresolveを実行します。
フォームからデータを収集して、各フォームパーツのデータをオブジェクトとした配列を返します。確認画面の出力のためにラベルを取得しなければならず、結構泥臭いコードになっています。
本記事の主役である確認画面を生成します。フォームを非表示にし、returnFormData関数で組み立てたデータからHTMLをゴリゴリ生成します。valueをそのまま表示できないラジオボタンなどがなんとも面倒ですね。戻るボタンをクリックしたら確認画面を非表示にし、フォームを再度表示させます。
Submit form data APIに実際にデータを送信する関数です。引数で受け取ったデータの中には確認画面表示のためのプロパティなどもありますので、いらないものを削除します。また、適宜HubSpotが受け取る形に合わせてデータを整形します。HubSpotが受け取るデータの形については、「 HubSpot APIの使い方 」ページの末尾の付録をご参照ください。
送信に成功したら後処理のafterSubmit関数を呼びますが、失敗した場合は申し訳程度のエラーメッセージを表示します。
カスタムモジュールの設定に合わせて、リダイレクトするかインラインメッセージを表示するかします。
以上、簡単ですがHubSpotのフォームに手軽に確認画面を実装する方法をご紹介しました。もしかしたら細かいバグなどあるかもしれませんが、おおよそ問題ないかと思います。※コピペ&バグが原因で起こった問題に関しては責任を取れませんので、自己責任のもとご利用くださいませ。