1/** 2 * JavaScript for Hubspot sign-up forms. 3 */ 4 5/** 6 * This class is a small data object for field types used in the API. 7 * See: https://legacydocs.hubspot.com/docs/methods/forms/submit_form 8 */ 9class DbiHubspotField { 10 /** 11 * Constructor 12 * 13 * @param {string} objectTypeId The Object Type ID - see https://developers.hubspot.com/docs/api/crm/crm-custom-objects 14 * @param {string} name The field name 15 * @param {string} value The field value 16 */ 17 constructor( objectTypeId, name, value ) { 18 this.objectTypeId = objectTypeId; 19 this.name = name; 20 this.value = value; 21 } 22} 23 24/** 25 * This is the main form class that handles form validation and submissions 26 */ 27class DbiEmailMarketingForms { 28 /** 29 * The base URL of the form submission API 30 * 31 * @type {string} 32 */ 33 baseUrl = 'https://api.hsforms.com/submissions/v3/integration/submit'; 34 35 /** 36 * Constructor 37 */ 38 constructor() { 39 document.querySelectorAll( '.drip-signup' ).forEach( ( form ) => { 40 form.addEventListener( 'submit', this.handleSubmit.bind( this ) ); 41 } ); 42 } 43 44 /** 45 * Handler for submit event 46 * 47 * @param {Event} event The event to handle 48 * @return {boolean} This will always return false in order to prevent the form submitting as normal. 49 */ 50 handleSubmit( event ) { 51 event.preventDefault(); 52 /** 53 * The form element 54 * 55 * @type {HTMLFormElement} 56 */ 57 const form = event.target; 58 59 /** 60 * The first input in the form 61 * 62 * @type {HTMLInputElement} 63 */ 64 const firstInput = form.querySelector( 'input' ); 65 66 /** 67 * The errors container element 68 * 69 * @type {HTMLDivElement} 70 */ 71 const errorsElement = form.previousElementSibling; // This needs to be '.dbi__signup-errors 72 73 // If form isn't expanded, expand and focus it. Do not submit. 74 if ( ! form.classList.contains( 'expanded' ) ) { 75 form.classList.add( 'expanded' ); 76 firstInput.focus(); 77 return false; 78 } 79 80 let allowSubmit = true; 81 82 // Validate inputs 83 form.querySelectorAll( 'input[type=email], input[type=text]' ).forEach( 84 ( input ) => { 85 allowSubmit = 86 allowSubmit && 87 this.validateTextInput( input, errorsElement ); 88 } 89 ); 90 91 if ( allowSubmit ) { 92 form.querySelector( 93 'input[type="submit"], button[type="submit"], button.dbi-welcome-signup__submit' 94 ).disabled = true; 95 errorsElement.style.display = 'none'; 96 97 this.submitFormData( form ).then( ( result ) => { 98 if ( result === true ) { 99 // If we have a redirect, send to that 100 if ( form.dataset.redirectTo ) { 101 window.location = form.dataset.redirectTo; 102 } 103 // There are currently no other actions 104 } else { 105 errorsElement.innerText = result; 106 errorsElement.style.display = 'block'; 107 } 108 } ); 109 } 110 111 return allowSubmit; 112 } 113 114 /** 115 * Validate a text input element 116 * 117 * @param {HTMLInputElement} inputElement The input element to validate 118 * @param {HTMLDivElement} errorsElement The element to display errors in 119 * @return {boolean} True if the value validates 120 */ 121 validateTextInput( inputElement, errorsElement ) { 122 // Check for 0-length input 123 if ( 124 inputElement.dataset.errorText && 125 0 === inputElement.value.length 126 ) { 127 errorsElement.innerText = `Please enter your ${ inputElement.dataset.errorText }.`; 128 errorsElement.style.display = 'block'; 129 130 return false; 131 } 132 133 // Check for invalid name 134 // The regular expression is loose, but it's good enough for our purposes 135 if ( 136 inputElement.dataset.errorText && 137 inputElement.type === 'text' && 138 inputElement.value.match( /<.*>/ ) 139 ) { 140 errorsElement.innerText = `Your ${ inputElement.dataset.errorText } can not contain HTML.`; 141 errorsElement.style.display = 'block'; 142 143 return false; 144 } 145 146 return true; 147 } 148 149 /** 150 * Gathers and submits the form data 151 * 152 * @param {HTMLFormElement} formElement The form element 153 * @return {Promise<boolean|string>} True for a successful form submission. An error message otherwise. 154 */ 155 async submitFormData( formElement ) { 156 // Get form details 157 const accountId = this.getAccountId( formElement ); 158 const formId = this.getFormId( formElement ); 159 // Construct URL 160 const url = this.constructUrl( accountId, formId ); 161 // Get data from form 162 const fieldsData = this.getDataFromForm( formElement ); 163 // Make the request and return the result 164 return await this.sendRequest( url, fieldsData, formElement ); 165 } 166 167 /** 168 * Gets the Hubspot form id from an HTML form 169 * 170 * @param {HTMLFormElement} formElement 171 * @return {string} The Hubspot form ID 172 */ 173 getFormId( formElement ) { 174 const formId = formElement.dataset.hubspotFormId; 175 if ( ! formId ) { 176 return ''; 177 } 178 return formId; 179 } 180 181 /** 182 * Gets the Hubspot account id from an HTML form 183 * 184 * @param {HTMLFormElement} formElement 185 * @return {string} The Hubspot account ID 186 */ 187 getAccountId( formElement ) { 188 const accountId = formElement.dataset.hubspotAccountId; 189 if ( ! accountId ) { 190 return ''; 191 } 192 return accountId; 193 } 194 195 /** 196 * Construct the API URL to use 197 * 198 * @param {string} hubspotAccountId The Hubspot account ID 199 * @param {string} formId The Hubspot form ID 200 * @return {string} The URL to use for the API submission 201 */ 202 constructUrl( hubspotAccountId, formId ) { 203 return `${ this.baseUrl }/${ hubspotAccountId }/${ formId }`; 204 } 205 206 /** 207 * Note the API Docs: 208 * - https://legacydocs.hubspot.com/docs/methods/forms/submit_form 209 * - https://developers.hubspot.com/docs/api/crm/crm-custom-objects 210 * Note that the objectTypeId for the standard "Contact" object is 0-1 211 * 212 * @param {HTMLFormElement} formElement The form element to get data from 213 * @return {Array<DbiHubspotField>} An array of field objects 214 */ 215 getDataFromForm( formElement ) { 216 const inputs = Array.from( 217 formElement.querySelectorAll( 'input[data-hubspot-field-name]' ) 218 ); 219
220 if ( inputs.length === 0 ) { 221 return []; 222 } 223 224 return inputs.map( 225 ( inputElem ) => 226 new DbiHubspotField( 227 inputElem.dataset.hubspotObjectTypeId, 228 inputElem.dataset.hubspotFieldName, 229 inputElem.value 230 ) 231 ); 232 } 233 234 /** 235 * Send the API request with the subscribe form data 236 * 237 * @param {string} url The API URL to send to 238 * @param {Array<DbiHubspotField>} fieldsData The fields data to send in the body 239 * @param {HTMLFormElement} formElement The formElement 240 * @return {Promise<boolean|string>} True for a successful form submission. An error message otherwise. 241 */ 242 async sendRequest( url, fieldsData, formElement ) { 243 const dataToSend = { fields: fieldsData }; 244 245 const options = { 246 method: formElement.method, 247 body: JSON.stringify( dataToSend ), 248 headers: { 249 'Content-Type': 'application/json', 250 }, 251 cache: 'no-cache', 252 }; 253 254 let returnValue = true; 255 256 const response = await fetch( url, options ).catch( ( err ) => { 257 returnValue = `There was a problem processing your request: ${ err }`; 258 } ); 259 if ( response && ! response.ok ) { 260 returnValue = `There was a problem processing your request. Result code: ${ response.status }`; 261 } 262 263 return returnValue; 264 } 265} 266 267new DbiEmailMarketingForms();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.