フォーム確認画面をjQueryだけで実装する
目次
- 目次
- 実装の概要
- 実際のコード
- 目次 実装の概要 実際のコード プライベート変数宣言とinitialize関数 validateForms関数 returnFormData関数 showConfirmation関数 sendDataToHS関数 afterSubmit関数 まとめ
- 実装の概要
モジュール自体はカスタムモジュールで実装していますが、実際のフォームの送信はHubSpotのformタグ標準の挙動ではなく、Forms APIの1つである 実装の概要 モジュール自体はカスタムモジュールで実装していますが、実際のフォームの送信はHubSpotのformタグ標準の挙動ではなく、Forms APIの1つである Submit form data を使用しています。このAPIは認証が不要なため、JavaScriptのみで完結しています。 カスタムモジュール内にはフォームフィールド1つのみ設置しており、ページ編集画面で設定できる フォームの選択 送信後の挙動の選択(別のページにリダイレクトか、メッセージをインライン表示) リダイレクトの場合は内部ベージか、外部URLの選択 は全てサポートしています。
日本の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のフォームに手軽に確認画面を実装する方法をご紹介しました。もしかしたら細かいバグなどあるかもしれませんが、おおよそ問題ないかと思います。※コピペ&バグが原因で起こった問題に関しては責任を取れませんので、自己責任のもとご利用くださいませ。
執筆者:Admin