CMS Development

chrispower
Contributor

Real time street address lookup in form field

SOLVE

I am creating a simple registration form containing email, first name, last name, phone number, and street address fields.  For the street address, I would like to add real time autocomplete/lookup functionality, using the smartystreets API documented here: https://smartystreets.com/docs/plugins/website.

 

I'm new to hubspot development, and not really sure where to start with this, or how it would be structured.

2 Accepted solutions
tsprunk
Solution
Contributor | Diamond Partner
Contributor | Diamond Partner

Real time street address lookup in form field

SOLVE

I know this is an old thread but I stumbled across it when I was trying to figure this out so I figured I'd share my solution:

This can be done with Google Maps API. I'll do my best to explain it below, but you can also hire my agency to do it for you if you'd rather not bother with it (SimpleStrat.com).

 

To do this, you'll need to add the javascript below to the page where your form will appear. There's also a way to add it to the form embed code directly but I won't get into that here. There are a couple things you'll likely need to edit depending on your form and the fields you want filled in.

 

This code is currently set up to work for US and Canada is based on the code found on this page: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform

// Create the script tag, set the appropriate attributes, be sure to replace "YOURAPIKEY" in the script source url with your Google Maps API Key
var script = document.createElement('script');
script.src='https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=places&callback=initAutocomplete';
script.async = true;

// Append the 'script' element to 'head' after the page loads 
window.addEventListener('load', function(){document.head.appendChild(script);})

let autocomplete;
let address1Field;
let address2Field;
let postalField;

function initAutocomplete() {
  //change .hs-address below to the class for your 1st line
  //address input field if necessary
  address1Field = document.querySelector(".hs-address .hs-input");
  //change .hs-zip below to the class for your postal code input field if necessary 
  postalField = document.querySelector(".hs-zip .hs-input");
  // Create the autocomplete object, restricting the search predictions to
  // addresses in the US and Canada.
  autocomplete = new google.maps.places.Autocomplete(address1Field, {
    componentRestrictions: { country: ["us", "ca"] },
    fields: ["address_components", "geometry"],
    types: ["address"],
  });
  // When the user selects an address from the drop-down, populate the address fields in the form.
  autocomplete.addListener("place_changed", fillInAddress);
}

function fillInAddress() {
  // Get the place details from the autocomplete object.
  const place = autocomplete.getPlace();
  let address1 = "";
  let postcode = "";

  // Get each component of the address from the place details,
  // and then fill-in the corresponding field on the form.
  // place.address_components are google.maps.GeocoderAddressComponent objects
  // which are documented at http://goo.gle/3l5i5Mr
  for (const component of place.address_components) {
    // @TS-ignore remove once typings fixed
    const componentType = component.types[0];

    switch (componentType) {
      case "street_number": {
        address1 = `${component.long_name} ${address1}`;
        break;
      }

      case "route": {
        address1 += component.short_name;
        break;
      }

      case "postal_code": {
        postcode = `${component.long_name}${postcode}`;
        break;
      }

      case "postal_code_suffix": {
        postcode = `${postcode}-${component.long_name}`;
        break;
      }
      case "locality":
        //change .hs-city below to the class for your city input field if necessary
        //comment out or delete the line below if no city field in your form
        document.querySelector(".hs-city .hs-input").value = component.long_name; 
        break;
      case "administrative_area_level_1": {
        //change .hs-state below to the class for your city input field if necessary
        //comment out or delete the line below if no state field in your form
        document.querySelector(".hs-state .hs-input").value = component.short_name;
        break;
      }
      case "country":
        //change .hs-country below to the class for your city input field if necessary
        //comment out or delete the line below if no country field in your form
        document.querySelector(".hs-country .hs-input").value = component.long_name;
        break;
    }
  }

  address1Field.value = address1;
  //comment out or delete the line below if no postal code field in form
  postalField.value = postcode;
}

window.initAutocomplete = initAutocomplete;

View solution in original post

atownsend1
Solution
Contributor

Real time street address lookup in form field

SOLVE

Yes! This is very possible with Smarty (formerly SmartyStreets) in Hubspot. You'll create a custom HTML form that communicates to Hubspot through the form API.

It sounds complicated, and it kind of is, but not really.

Smarty released a tutorial on it last year: https://www.smarty.com/articles/hubspot-address-autocomplete-guide

Basically, you create an HTML form, link it to the Hubspot form API, and then connect it to Smarty's autocomplete functionality. Pretty simple.

Here's basic code for an HTML form that is prepared for autocomplete:

 

 

<form id="myCoolForm" style="padding: 30px; border: 1px solid #476884; border-radius: 16px;"><input id="myCoolFirstname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="First name *"> <input id="myCoolLastname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Last name *"> <input id="myCoolEmail" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" required="" type="text" placeholder="Email *"> <input id="myCoolAddress" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Address *"> <input id="myCoolCity" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="City *"><select id="myCoolState" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;">
		<option value="AL">AL</option>
		<option value="AK">AK</option>
		<option value="AZ">AZ</option>
		<option value="AR">AR</option>
		<option value="CA">CA</option>
		<option value="CO">CO</option>
		<option value="CT">CT</option>
		<option value="DE">DE</option>
		<option value="FL">FL</option>
		<option value="GA">GA</option>
		<option value="HI">HI</option>
		<option value="ID">ID</option>
		<option value="IL">IL</option>
		<option value="IN">IN</option>
		<option value="IA">IA</option>
		<option value="KS">KS</option>
		<option value="KY">KY</option>
		<option value="LA">LA</option>
		<option value="ME">ME</option>
		<option value="MD">MD</option>
		<option value="MA">MA</option>
		<option value="MI">MI</option>
		<option value="MN">MN</option>
		<option value="MS">MS</option>
		<option value="MO">MO</option>
		<option value="MT">MT</option>
		<option value="NE">NE</option>
		<option value="NV">NV</option>
		<option value="NH">NH</option>
		<option value="NJ">NJ</option>
		<option value="NM">NM</option>
		<option value="NY">NY</option>
		<option value="NC">NC</option>
		<option value="ND">ND</option>
		<option value="OH">OH</option>
		<option value="OK">OK</option>
		<option value="OR">OR</option>
		<option value="PA">PA</option>
		<option value="RI">RI</option>
		<option value="SC">SC</option>
		<option value="SD">SD</option>
		<option value="TN">TN</option>
		<option value="TX">TX</option>
		<option value="UT">UT</option>
		<option value="VT">VT</option>
		<option value="VA">VA</option>
		<option value="WA">WA</option>
		<option value="WV">WV</option>
		<option value="WI">WI</option>
		<option value="WY">WY</option>
	</select><input id="myCoolZip" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="ZIP Code">
	<div style="padding-bottom: 15px; padding-left: 15px;">Your <a href="https://YOUR PRIVACY POLICY LINK GOES HERE">privacy</a> is important to us. You agree to receive messages regarding our products and services by submitting this form. You may unsubscribe at any time.</div>
	<div style="text-align: center;"><button id="myCoolButton" style="background-color: #0066ff; color: white; padding: 15px 40px; font-size: 20px; font-weight: bold; border: none; border-radius: 90px; cursor: pointer; display: inline-block; text-align: center; text-decoration: none;">SUBMIT</button></div>
	</form>

 

 

And then whatever page you're putting it on just needs to have this JavaScript in the footer.

 

<script>

		// HubSpot Settings - change stuff between the quotes after the equals sign (keep the quotes).
		const MY_COOL_HUBSPOT_FORM_ID = 'YOUR HUBSPOT FORM ID';
		const MY_COOL_HUBSPOT_PORTAL_ID = 'YOUR HUBSPOT PORTAL ID';
		const MY_COOL_CONFIRMATION_PAGE = 'YOUR CONFIRMATION PAGE URL BEGINNING WITH https://';
		const MY_COOL_ERROR_MESSAGE = 'All fields are required.';
		// Smarty Settings - change stuff between the quotes after the equals sign (keep the quotes).
		const MY_COOL_SMARTY_EMBEDDED_KEY = 'YOUR SMARTY EMBEDDED KEY GOES HERE';
		const MY_COOL_SMARTY_SOURCE = 'postal'; // valid options are 'postal' or 'all'

		// ------ DON'T MODIFY BELOW THIS LINE ------

		document.getElementById('myCoolButton').addEventListener('click', (e) => {
			e.preventDefault();
			submitData();
		});
		async function submitData() {
			const url = `https://api.hsforms.com/submissions/v3/integration/submit/${MY_COOL_HUBSPOT_PORTAL_ID}/${MY_COOL_HUBSPOT_FORM_ID}`;
			const headers = {
				'accept': 'application/json',
				'content-type': 'application/json',
			};
			const data = {
				"fields": [{
					"name": "firstname",
					"value": document.getElementById('myCoolFirstname').value
				},
					{
						"name": "lastname",
						"value": document.getElementById('myCoolLastname').value
					},
					{
						"name": "email",
						"value": document.getElementById('myCoolEmail').value
					},
					{
						"name": "address",
						"value": document.getElementById('myCoolAddress').value
					},
					{
						"name": "city",
						"value": document.getElementById('myCoolCity').value
					},
					{
						"name": "state",
						"value": document.getElementById('myCoolState').value
					},
					{
						"name": "zip",
						"value": document.getElementById('myCoolZip').value
					}
				],
				"skipValidation": false,
				"context": {}
			};
			try {
				const response = await fetch(url, {
					method: 'POST',
					headers: headers,
					body: JSON.stringify(data)
				});
				if (!response.ok) {
					throw new Error(`HTTP error! status: ${response.status}`);
				}
				const responseData = await response.json();
				console.log(responseData);
				window.location.href = MY_COOL_CONFIRMATION_PAGE;
			} catch (error) {
				console.error('Error:', error);
				alert(MY_COOL_ERROR_MESSAGE);
			}
		}

		((global) => {
			let settings = {
				embeddedKey: '',
				addressId: 'address',
				cityId: 'city',
				stateId: 'state',
				zipCodeId: 'zip',
				styleBackgroundColorHexString: '#fff',
				styleColorHexString: '#333',
				styleHoverBackgroundColorHexString: '#cfe8ff',
				styleHoverColorHexString: '#000',
				styleBorderColorHexString: '#e0e0e0',
				styleBorderPixelWidthInt: 2,
				styleBorderRadiusInt: 8,
				styleFontFamilyString: 'sans-serif',
				styleFontSizePixelInt: 14,
				styleRowPaddingString: '8px',
				styleBoxPixelWidthInt: 300,
				styleBoxPixelHeightInt: 300,
				styleSelectedSuggestionColorHexString: '#000',
				styleSelectedSuggestionBackgroundColorHexString: '#ddd',
				suggestionElement: document.createElement('div'),
				suggestionId: null,
				offsetHeight: 20,
				addressElement: null,
				activeStyles: '',
				inactiveStyles: 'display: none;',
				selectedIndex: 0,
				lastAction: '',
			};
			const wrapperStyles = `
				display: inline-block;
				position: relative;
				width: 100%;
			`;
			const extendSettings = (defaults, options) => {
				// @todo ensure the options are allowed.
				return {
					...defaults,
					...options
				};
			};
			const wrapElementsWithDiv = (elementId) => {
				const knownElement = document.getElementById(elementId);
				if (!knownElement) {
					console.error(`Element with ID ${elementId} not found.`);
					return;
				}
				settings.addressElement = knownElement;
				settings.offsetHeight = knownElement.offsetHeight;
				const wrapperDiv = document.createElement('div');
				wrapperDiv.style.cssText = wrapperStyles;
				knownElement.parentNode.insertBefore(wrapperDiv, knownElement);
				wrapperDiv.appendChild(knownElement);
				knownElement.parentNode.insertBefore(settings.suggestionElement, knownElement.nextSibling);
			}
			const sendLookupRequest = async (searchValue, selected = "") => {
				const params = new URLSearchParams({
					key: settings.embeddedKey,
					search: searchValue,
					source: MY_COOL_SMARTY_SOURCE,
					selected
				});
				try {
					const request = await fetch(
						`https://us-autocomplete-pro.api.smarty.com/lookup?${params}`
					);
					const data = await request.json();
					if (data?.suggestions?.length > 0) formatSuggestions(data.suggestions);
				} catch (e) {
					console.error(e.message);
				}
			};
			const applyStyles = (element, styles) => {
				for (let property in styles) {
					element.style[property] = styles[property];
				}
			};
			const formatSuggestions = (suggestions) => {
				const {
					suggestionElement,
					inactiveStyles,
					styleRowPaddingString,
					styleHoverBackgroundColorHexString,
					styleHoverColorHexString,
					styleBackgroundColorHexString,
					styleColorHexString,
					styleFontSizePixelInt,
					styleSelectedSuggestionColorHexString,
					styleSelectedSuggestionBackgroundColorHexString,
					activeStyles,
				} = settings;
				suggestionElement.innerHTML = '';
				suggestionElement.style.cssText = activeStyles;
				const formattedSuggestions = suggestions.map((suggestion, index) => {
					const divElement = document.createElement("div");
					divElement.classList.add('smarty-suggestion');
					divElement.style['padding'] = styleRowPaddingString;
					divElement.style['fontSize'] = `${styleFontSizePixelInt}px`;
					if (index === 0) {
						applyStyles(divElement, {
							color: styleSelectedSuggestionColorHexString,
							backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
						});
						settings.selectedIndex = 0;
					}
					const {
						street_line,
						city,
						state,
						zipcode,
						secondary,
						entries
					} = suggestion;
					const hasSecondaryData = secondary && entries > 1;
					divElement.innerText = `${street_line} ${secondary} ${
						hasSecondaryData ? `(${entries} entries)` : ""
					} ${city} ${state} ${zipcode}`;
					divElement.addEventListener('mouseover', () => {
						if (settings.lastAction === 'keyboard') return;
						applyStyles(divElement, {
							backgroundColor: styleHoverBackgroundColorHexString,
							color: styleHoverColorHexString,
						});
					});
					divElement.addEventListener('mouseout', () => {
						applyStyles(divElement, {
							backgroundColor: styleBackgroundColorHexString,
							color: styleColorHexString,
						});
					});
					divElement.addEventListener("click", async () => {
						const streetLineWithSecondary = `${street_line} ${secondary}`.trim();
						if (hasSecondaryData) {
							const selected = `${streetLineWithSecondary} (${entries}) ${city} ${state} ${zipcode}`;
							await sendLookupRequest(streetLineWithSecondary, selected);
						} else {
							suggestionElement.style.cssText = inactiveStyles;
						}
						populateForm({
							streetLineWithSecondary,
							city,
							state,
							zipcode
						});
					});
					return divElement;
				});
				suggestionElement.append(...formattedSuggestions);
			}
			const populateForm = ({
				streetLineWithSecondary,
				city,
				state,
				zipcode
			}) => {
				const {
					addressId,
					cityId,
					stateId,
					zipCodeId
				} = settings;
				document.getElementById(addressId).value = streetLineWithSecondary;
				document.getElementById(cityId).value = city;
				document.getElementById(stateId).value = state;
				document.getElementById(zipCodeId).value = zipcode;
			};
			const scrollWrapperToSelected = () => {
				const {
					selectedIndex,
					suggestionElement
				} = settings;
				const elements = document.getElementsByClassName('smarty-suggestion');
				if (selectedIndex >= 0 && selectedIndex < elements.length) {
					const selectedChild = elements[selectedIndex];
					const wrapperRect = suggestionElement.getBoundingClientRect();
					const selectedRect = selectedChild.getBoundingClientRect();
					// Check if selected child is above the viewport
					if (selectedRect.top < wrapperRect.top) {
						suggestionElement.scrollTop -= (wrapperRect.top - selectedRect.top);
					}
					// Check if selected child is below the viewport
					else if (selectedRect.bottom > wrapperRect.bottom) {
						suggestionElement.scrollTop += (selectedRect.bottom - wrapperRect.bottom);
					}
				}
			}
			LikeButtaSmartyUsAddressAutocomplete = (userSettings) => {
				settings = extendSettings(settings, userSettings);
				wrapElementsWithDiv(settings.addressId);
				settings.activeStyles = `
					display: block;
					position: absolute;
					overflow-y: auto;
					cursor: pointer;
					top: ${settings.offsetHeight};
					width: ${settings.styleBoxPixelWidthInt}px;
					height: ${settings.styleBoxPixelHeightInt}px;
					border: solid ${settings.styleBorderPixelWidthInt}px ${settings.styleBorderColorHexString};
					border-radius: ${settings.styleBorderRadiusInt}px;
					background-color: ${settings.styleBackgroundColorHexString};
					font-family: ${settings.styleFontFamilyString};
					color: ${settings.styleColorHexString};
				`;
				settings.suggestionElement.id = 'smartySuggestionBox';
				settings.suggestionId = settings.suggestionElement.id;
				const {
					addressElement,
					suggestionElement,
					inactiveStyles,
					activeStyles
				} = settings;
				suggestionElement.style.cssText = inactiveStyles;
				addressElement.addEventListener("keyup", (e) => {
					if ([
						'Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'Tab', 'ArrowUp', 'ArrowDown',
						'ArrowLeft', 'ArrowRight', 'Escape', 'F1', 'F2', 'F3', 'F4', 'F5',
						'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'
					].includes(e.key)) return;
					if (e.key === 'Enter') {
						const elements = document.getElementsByClassName('smarty-suggestion');
						elements[settings.selectedIndex].click();
						return;
					}
					const searchValue = e.target.value;
					if (!searchValue) {
						suggestionElement.style.cssText = inactiveStyles;
						return;
					}
					sendLookupRequest(searchValue);
				});
				addressElement.addEventListener("keydown", async (e) => {
					if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
					e.preventDefault();
					const {
						selectedIndex,
						styleColorHexString,
						styleBackgroundColorHexString,
						styleSelectedSuggestionColorHexString,
						styleSelectedSuggestionBackgroundColorHexString,
					} = settings;
					const elements = document.getElementsByClassName('smarty-suggestion');
					[...elements].forEach((element) => {
						applyStyles(element, {
							color: styleColorHexString,
							backgroundColor: styleBackgroundColorHexString,
						});
					});
					if (elements.length === selectedIndex + 1 && e.key === 'ArrowDown') {
						settings.selectedIndex = 0;
					} else if (selectedIndex === 0 && e.key === 'ArrowUp') {
						settings.selectedIndex = elements.length - 1;
					} else if (e.key === 'ArrowDown') {
						settings.selectedIndex += 1;
					} else {
						settings.selectedIndex -= 1;
					}
					applyStyles(elements[settings.selectedIndex], {
						color: styleSelectedSuggestionColorHexString,
						backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
					});
					settings.lastAction = 'keyboard';
					scrollWrapperToSelected();
				});
				suggestionElement.addEventListener('mousemove', (e) => {
					settings.lastAction = 'mouse';
				});
				suggestionElement.addEventListener('mouseout', (e) => {
					const elements = document.getElementsByClassName('smarty-suggestion');
					const {
						selectedIndex,
						styleSelectedSuggestionColorHexString,
						styleSelectedSuggestionBackgroundColorHexString
					} = settings;
					applyStyles(elements[selectedIndex], {
						color: styleSelectedSuggestionColorHexString,
						backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
					});
				});
				document.addEventListener('click', (e) => {
					if (!suggestionElement.contains(e.target) && !e.target.classList.contains('smarty-suggestion')) {
						suggestionElement.style.cssText = inactiveStyles;
					}
				});
			};
			global.LikeButtaSmartyUsAddressAutocomplete = LikeButtaSmartyUsAddressAutocomplete;
		})(window);
		LikeButtaSmartyUsAddressAutocomplete({
			embeddedKey: MY_COOL_SMARTY_EMBEDDED_KEY,
			addressId: 'myCoolAddress',
			cityId: 'myCoolCity',
			stateId: 'myCoolState',
			zipCodeId: 'myCoolZip',
		});
	</script>

 

 

I got it working really fast, in like ten minutes.

View solution in original post

0 Upvotes
14 Replies 14
atownsend1
Contributor

Real time street address lookup in form field

SOLVE
0 Upvotes
atownsend1
Solution
Contributor

Real time street address lookup in form field

SOLVE

Yes! This is very possible with Smarty (formerly SmartyStreets) in Hubspot. You'll create a custom HTML form that communicates to Hubspot through the form API.

It sounds complicated, and it kind of is, but not really.

Smarty released a tutorial on it last year: https://www.smarty.com/articles/hubspot-address-autocomplete-guide

Basically, you create an HTML form, link it to the Hubspot form API, and then connect it to Smarty's autocomplete functionality. Pretty simple.

Here's basic code for an HTML form that is prepared for autocomplete:

 

 

<form id="myCoolForm" style="padding: 30px; border: 1px solid #476884; border-radius: 16px;"><input id="myCoolFirstname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="First name *"> <input id="myCoolLastname" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Last name *"> <input id="myCoolEmail" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" required="" type="text" placeholder="Email *"> <input id="myCoolAddress" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="Address *"> <input id="myCoolCity" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="City *"><select id="myCoolState" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;">
		<option value="AL">AL</option>
		<option value="AK">AK</option>
		<option value="AZ">AZ</option>
		<option value="AR">AR</option>
		<option value="CA">CA</option>
		<option value="CO">CO</option>
		<option value="CT">CT</option>
		<option value="DE">DE</option>
		<option value="FL">FL</option>
		<option value="GA">GA</option>
		<option value="HI">HI</option>
		<option value="ID">ID</option>
		<option value="IL">IL</option>
		<option value="IN">IN</option>
		<option value="IA">IA</option>
		<option value="KS">KS</option>
		<option value="KY">KY</option>
		<option value="LA">LA</option>
		<option value="ME">ME</option>
		<option value="MD">MD</option>
		<option value="MA">MA</option>
		<option value="MI">MI</option>
		<option value="MN">MN</option>
		<option value="MS">MS</option>
		<option value="MO">MO</option>
		<option value="MT">MT</option>
		<option value="NE">NE</option>
		<option value="NV">NV</option>
		<option value="NH">NH</option>
		<option value="NJ">NJ</option>
		<option value="NM">NM</option>
		<option value="NY">NY</option>
		<option value="NC">NC</option>
		<option value="ND">ND</option>
		<option value="OH">OH</option>
		<option value="OK">OK</option>
		<option value="OR">OR</option>
		<option value="PA">PA</option>
		<option value="RI">RI</option>
		<option value="SC">SC</option>
		<option value="SD">SD</option>
		<option value="TN">TN</option>
		<option value="TX">TX</option>
		<option value="UT">UT</option>
		<option value="VT">VT</option>
		<option value="VA">VA</option>
		<option value="WA">WA</option>
		<option value="WV">WV</option>
		<option value="WI">WI</option>
		<option value="WY">WY</option>
	</select><input id="myCoolZip" style="padding: 15px; margin: 6px; border: 2px solid #eee; border-radius: 2px;" type="text" placeholder="ZIP Code">
	<div style="padding-bottom: 15px; padding-left: 15px;">Your <a href="https://YOUR PRIVACY POLICY LINK GOES HERE">privacy</a> is important to us. You agree to receive messages regarding our products and services by submitting this form. You may unsubscribe at any time.</div>
	<div style="text-align: center;"><button id="myCoolButton" style="background-color: #0066ff; color: white; padding: 15px 40px; font-size: 20px; font-weight: bold; border: none; border-radius: 90px; cursor: pointer; display: inline-block; text-align: center; text-decoration: none;">SUBMIT</button></div>
	</form>

 

 

And then whatever page you're putting it on just needs to have this JavaScript in the footer.

 

<script>

		// HubSpot Settings - change stuff between the quotes after the equals sign (keep the quotes).
		const MY_COOL_HUBSPOT_FORM_ID = 'YOUR HUBSPOT FORM ID';
		const MY_COOL_HUBSPOT_PORTAL_ID = 'YOUR HUBSPOT PORTAL ID';
		const MY_COOL_CONFIRMATION_PAGE = 'YOUR CONFIRMATION PAGE URL BEGINNING WITH https://';
		const MY_COOL_ERROR_MESSAGE = 'All fields are required.';
		// Smarty Settings - change stuff between the quotes after the equals sign (keep the quotes).
		const MY_COOL_SMARTY_EMBEDDED_KEY = 'YOUR SMARTY EMBEDDED KEY GOES HERE';
		const MY_COOL_SMARTY_SOURCE = 'postal'; // valid options are 'postal' or 'all'

		// ------ DON'T MODIFY BELOW THIS LINE ------

		document.getElementById('myCoolButton').addEventListener('click', (e) => {
			e.preventDefault();
			submitData();
		});
		async function submitData() {
			const url = `https://api.hsforms.com/submissions/v3/integration/submit/${MY_COOL_HUBSPOT_PORTAL_ID}/${MY_COOL_HUBSPOT_FORM_ID}`;
			const headers = {
				'accept': 'application/json',
				'content-type': 'application/json',
			};
			const data = {
				"fields": [{
					"name": "firstname",
					"value": document.getElementById('myCoolFirstname').value
				},
					{
						"name": "lastname",
						"value": document.getElementById('myCoolLastname').value
					},
					{
						"name": "email",
						"value": document.getElementById('myCoolEmail').value
					},
					{
						"name": "address",
						"value": document.getElementById('myCoolAddress').value
					},
					{
						"name": "city",
						"value": document.getElementById('myCoolCity').value
					},
					{
						"name": "state",
						"value": document.getElementById('myCoolState').value
					},
					{
						"name": "zip",
						"value": document.getElementById('myCoolZip').value
					}
				],
				"skipValidation": false,
				"context": {}
			};
			try {
				const response = await fetch(url, {
					method: 'POST',
					headers: headers,
					body: JSON.stringify(data)
				});
				if (!response.ok) {
					throw new Error(`HTTP error! status: ${response.status}`);
				}
				const responseData = await response.json();
				console.log(responseData);
				window.location.href = MY_COOL_CONFIRMATION_PAGE;
			} catch (error) {
				console.error('Error:', error);
				alert(MY_COOL_ERROR_MESSAGE);
			}
		}

		((global) => {
			let settings = {
				embeddedKey: '',
				addressId: 'address',
				cityId: 'city',
				stateId: 'state',
				zipCodeId: 'zip',
				styleBackgroundColorHexString: '#fff',
				styleColorHexString: '#333',
				styleHoverBackgroundColorHexString: '#cfe8ff',
				styleHoverColorHexString: '#000',
				styleBorderColorHexString: '#e0e0e0',
				styleBorderPixelWidthInt: 2,
				styleBorderRadiusInt: 8,
				styleFontFamilyString: 'sans-serif',
				styleFontSizePixelInt: 14,
				styleRowPaddingString: '8px',
				styleBoxPixelWidthInt: 300,
				styleBoxPixelHeightInt: 300,
				styleSelectedSuggestionColorHexString: '#000',
				styleSelectedSuggestionBackgroundColorHexString: '#ddd',
				suggestionElement: document.createElement('div'),
				suggestionId: null,
				offsetHeight: 20,
				addressElement: null,
				activeStyles: '',
				inactiveStyles: 'display: none;',
				selectedIndex: 0,
				lastAction: '',
			};
			const wrapperStyles = `
				display: inline-block;
				position: relative;
				width: 100%;
			`;
			const extendSettings = (defaults, options) => {
				// @todo ensure the options are allowed.
				return {
					...defaults,
					...options
				};
			};
			const wrapElementsWithDiv = (elementId) => {
				const knownElement = document.getElementById(elementId);
				if (!knownElement) {
					console.error(`Element with ID ${elementId} not found.`);
					return;
				}
				settings.addressElement = knownElement;
				settings.offsetHeight = knownElement.offsetHeight;
				const wrapperDiv = document.createElement('div');
				wrapperDiv.style.cssText = wrapperStyles;
				knownElement.parentNode.insertBefore(wrapperDiv, knownElement);
				wrapperDiv.appendChild(knownElement);
				knownElement.parentNode.insertBefore(settings.suggestionElement, knownElement.nextSibling);
			}
			const sendLookupRequest = async (searchValue, selected = "") => {
				const params = new URLSearchParams({
					key: settings.embeddedKey,
					search: searchValue,
					source: MY_COOL_SMARTY_SOURCE,
					selected
				});
				try {
					const request = await fetch(
						`https://us-autocomplete-pro.api.smarty.com/lookup?${params}`
					);
					const data = await request.json();
					if (data?.suggestions?.length > 0) formatSuggestions(data.suggestions);
				} catch (e) {
					console.error(e.message);
				}
			};
			const applyStyles = (element, styles) => {
				for (let property in styles) {
					element.style[property] = styles[property];
				}
			};
			const formatSuggestions = (suggestions) => {
				const {
					suggestionElement,
					inactiveStyles,
					styleRowPaddingString,
					styleHoverBackgroundColorHexString,
					styleHoverColorHexString,
					styleBackgroundColorHexString,
					styleColorHexString,
					styleFontSizePixelInt,
					styleSelectedSuggestionColorHexString,
					styleSelectedSuggestionBackgroundColorHexString,
					activeStyles,
				} = settings;
				suggestionElement.innerHTML = '';
				suggestionElement.style.cssText = activeStyles;
				const formattedSuggestions = suggestions.map((suggestion, index) => {
					const divElement = document.createElement("div");
					divElement.classList.add('smarty-suggestion');
					divElement.style['padding'] = styleRowPaddingString;
					divElement.style['fontSize'] = `${styleFontSizePixelInt}px`;
					if (index === 0) {
						applyStyles(divElement, {
							color: styleSelectedSuggestionColorHexString,
							backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
						});
						settings.selectedIndex = 0;
					}
					const {
						street_line,
						city,
						state,
						zipcode,
						secondary,
						entries
					} = suggestion;
					const hasSecondaryData = secondary && entries > 1;
					divElement.innerText = `${street_line} ${secondary} ${
						hasSecondaryData ? `(${entries} entries)` : ""
					} ${city} ${state} ${zipcode}`;
					divElement.addEventListener('mouseover', () => {
						if (settings.lastAction === 'keyboard') return;
						applyStyles(divElement, {
							backgroundColor: styleHoverBackgroundColorHexString,
							color: styleHoverColorHexString,
						});
					});
					divElement.addEventListener('mouseout', () => {
						applyStyles(divElement, {
							backgroundColor: styleBackgroundColorHexString,
							color: styleColorHexString,
						});
					});
					divElement.addEventListener("click", async () => {
						const streetLineWithSecondary = `${street_line} ${secondary}`.trim();
						if (hasSecondaryData) {
							const selected = `${streetLineWithSecondary} (${entries}) ${city} ${state} ${zipcode}`;
							await sendLookupRequest(streetLineWithSecondary, selected);
						} else {
							suggestionElement.style.cssText = inactiveStyles;
						}
						populateForm({
							streetLineWithSecondary,
							city,
							state,
							zipcode
						});
					});
					return divElement;
				});
				suggestionElement.append(...formattedSuggestions);
			}
			const populateForm = ({
				streetLineWithSecondary,
				city,
				state,
				zipcode
			}) => {
				const {
					addressId,
					cityId,
					stateId,
					zipCodeId
				} = settings;
				document.getElementById(addressId).value = streetLineWithSecondary;
				document.getElementById(cityId).value = city;
				document.getElementById(stateId).value = state;
				document.getElementById(zipCodeId).value = zipcode;
			};
			const scrollWrapperToSelected = () => {
				const {
					selectedIndex,
					suggestionElement
				} = settings;
				const elements = document.getElementsByClassName('smarty-suggestion');
				if (selectedIndex >= 0 && selectedIndex < elements.length) {
					const selectedChild = elements[selectedIndex];
					const wrapperRect = suggestionElement.getBoundingClientRect();
					const selectedRect = selectedChild.getBoundingClientRect();
					// Check if selected child is above the viewport
					if (selectedRect.top < wrapperRect.top) {
						suggestionElement.scrollTop -= (wrapperRect.top - selectedRect.top);
					}
					// Check if selected child is below the viewport
					else if (selectedRect.bottom > wrapperRect.bottom) {
						suggestionElement.scrollTop += (selectedRect.bottom - wrapperRect.bottom);
					}
				}
			}
			LikeButtaSmartyUsAddressAutocomplete = (userSettings) => {
				settings = extendSettings(settings, userSettings);
				wrapElementsWithDiv(settings.addressId);
				settings.activeStyles = `
					display: block;
					position: absolute;
					overflow-y: auto;
					cursor: pointer;
					top: ${settings.offsetHeight};
					width: ${settings.styleBoxPixelWidthInt}px;
					height: ${settings.styleBoxPixelHeightInt}px;
					border: solid ${settings.styleBorderPixelWidthInt}px ${settings.styleBorderColorHexString};
					border-radius: ${settings.styleBorderRadiusInt}px;
					background-color: ${settings.styleBackgroundColorHexString};
					font-family: ${settings.styleFontFamilyString};
					color: ${settings.styleColorHexString};
				`;
				settings.suggestionElement.id = 'smartySuggestionBox';
				settings.suggestionId = settings.suggestionElement.id;
				const {
					addressElement,
					suggestionElement,
					inactiveStyles,
					activeStyles
				} = settings;
				suggestionElement.style.cssText = inactiveStyles;
				addressElement.addEventListener("keyup", (e) => {
					if ([
						'Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'Tab', 'ArrowUp', 'ArrowDown',
						'ArrowLeft', 'ArrowRight', 'Escape', 'F1', 'F2', 'F3', 'F4', 'F5',
						'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'
					].includes(e.key)) return;
					if (e.key === 'Enter') {
						const elements = document.getElementsByClassName('smarty-suggestion');
						elements[settings.selectedIndex].click();
						return;
					}
					const searchValue = e.target.value;
					if (!searchValue) {
						suggestionElement.style.cssText = inactiveStyles;
						return;
					}
					sendLookupRequest(searchValue);
				});
				addressElement.addEventListener("keydown", async (e) => {
					if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return;
					e.preventDefault();
					const {
						selectedIndex,
						styleColorHexString,
						styleBackgroundColorHexString,
						styleSelectedSuggestionColorHexString,
						styleSelectedSuggestionBackgroundColorHexString,
					} = settings;
					const elements = document.getElementsByClassName('smarty-suggestion');
					[...elements].forEach((element) => {
						applyStyles(element, {
							color: styleColorHexString,
							backgroundColor: styleBackgroundColorHexString,
						});
					});
					if (elements.length === selectedIndex + 1 && e.key === 'ArrowDown') {
						settings.selectedIndex = 0;
					} else if (selectedIndex === 0 && e.key === 'ArrowUp') {
						settings.selectedIndex = elements.length - 1;
					} else if (e.key === 'ArrowDown') {
						settings.selectedIndex += 1;
					} else {
						settings.selectedIndex -= 1;
					}
					applyStyles(elements[settings.selectedIndex], {
						color: styleSelectedSuggestionColorHexString,
						backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
					});
					settings.lastAction = 'keyboard';
					scrollWrapperToSelected();
				});
				suggestionElement.addEventListener('mousemove', (e) => {
					settings.lastAction = 'mouse';
				});
				suggestionElement.addEventListener('mouseout', (e) => {
					const elements = document.getElementsByClassName('smarty-suggestion');
					const {
						selectedIndex,
						styleSelectedSuggestionColorHexString,
						styleSelectedSuggestionBackgroundColorHexString
					} = settings;
					applyStyles(elements[selectedIndex], {
						color: styleSelectedSuggestionColorHexString,
						backgroundColor: styleSelectedSuggestionBackgroundColorHexString,
					});
				});
				document.addEventListener('click', (e) => {
					if (!suggestionElement.contains(e.target) && !e.target.classList.contains('smarty-suggestion')) {
						suggestionElement.style.cssText = inactiveStyles;
					}
				});
			};
			global.LikeButtaSmartyUsAddressAutocomplete = LikeButtaSmartyUsAddressAutocomplete;
		})(window);
		LikeButtaSmartyUsAddressAutocomplete({
			embeddedKey: MY_COOL_SMARTY_EMBEDDED_KEY,
			addressId: 'myCoolAddress',
			cityId: 'myCoolCity',
			stateId: 'myCoolState',
			zipCodeId: 'myCoolZip',
		});
	</script>

 

 

I got it working really fast, in like ten minutes.

0 Upvotes
tsprunk
Solution
Contributor | Diamond Partner
Contributor | Diamond Partner

Real time street address lookup in form field

SOLVE

I know this is an old thread but I stumbled across it when I was trying to figure this out so I figured I'd share my solution:

This can be done with Google Maps API. I'll do my best to explain it below, but you can also hire my agency to do it for you if you'd rather not bother with it (SimpleStrat.com).

 

To do this, you'll need to add the javascript below to the page where your form will appear. There's also a way to add it to the form embed code directly but I won't get into that here. There are a couple things you'll likely need to edit depending on your form and the fields you want filled in.

 

This code is currently set up to work for US and Canada is based on the code found on this page: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform

// Create the script tag, set the appropriate attributes, be sure to replace "YOURAPIKEY" in the script source url with your Google Maps API Key
var script = document.createElement('script');
script.src='https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=places&callback=initAutocomplete';
script.async = true;

// Append the 'script' element to 'head' after the page loads 
window.addEventListener('load', function(){document.head.appendChild(script);})

let autocomplete;
let address1Field;
let address2Field;
let postalField;

function initAutocomplete() {
  //change .hs-address below to the class for your 1st line
  //address input field if necessary
  address1Field = document.querySelector(".hs-address .hs-input");
  //change .hs-zip below to the class for your postal code input field if necessary 
  postalField = document.querySelector(".hs-zip .hs-input");
  // Create the autocomplete object, restricting the search predictions to
  // addresses in the US and Canada.
  autocomplete = new google.maps.places.Autocomplete(address1Field, {
    componentRestrictions: { country: ["us", "ca"] },
    fields: ["address_components", "geometry"],
    types: ["address"],
  });
  // When the user selects an address from the drop-down, populate the address fields in the form.
  autocomplete.addListener("place_changed", fillInAddress);
}

function fillInAddress() {
  // Get the place details from the autocomplete object.
  const place = autocomplete.getPlace();
  let address1 = "";
  let postcode = "";

  // Get each component of the address from the place details,
  // and then fill-in the corresponding field on the form.
  // place.address_components are google.maps.GeocoderAddressComponent objects
  // which are documented at http://goo.gle/3l5i5Mr
  for (const component of place.address_components) {
    // @TS-ignore remove once typings fixed
    const componentType = component.types[0];

    switch (componentType) {
      case "street_number": {
        address1 = `${component.long_name} ${address1}`;
        break;
      }

      case "route": {
        address1 += component.short_name;
        break;
      }

      case "postal_code": {
        postcode = `${component.long_name}${postcode}`;
        break;
      }

      case "postal_code_suffix": {
        postcode = `${postcode}-${component.long_name}`;
        break;
      }
      case "locality":
        //change .hs-city below to the class for your city input field if necessary
        //comment out or delete the line below if no city field in your form
        document.querySelector(".hs-city .hs-input").value = component.long_name; 
        break;
      case "administrative_area_level_1": {
        //change .hs-state below to the class for your city input field if necessary
        //comment out or delete the line below if no state field in your form
        document.querySelector(".hs-state .hs-input").value = component.short_name;
        break;
      }
      case "country":
        //change .hs-country below to the class for your city input field if necessary
        //comment out or delete the line below if no country field in your form
        document.querySelector(".hs-country .hs-input").value = component.long_name;
        break;
    }
  }

  address1Field.value = address1;
  //comment out or delete the line below if no postal code field in form
  postalField.value = postcode;
}

window.initAutocomplete = initAutocomplete;
apmul
Participant

Real time street address lookup in form field

SOLVE

Hi! Wondering if you have a live form running off of this? I've got a version running using places now but there are a few quirks I'd like to iron out; would love to see if yours runs more smoothly?

0 Upvotes
Mireille
Participant

Real time street address lookup in form field

SOLVE

Hi, you can add Loqate's address autocomplete on HubSpot forms, and here's a handy guide to show you how to: https://www.loqate.com/resources/support/setup-guides/hubspot/

0 Upvotes
BrandonCarn
Participant

Real time street address lookup in form field

SOLVE

Wondering if anyone ever got SmartyStreets intergrated with HubSpot.

0 Upvotes
atownsend1
Contributor

Real time street address lookup in form field

SOLVE

I did, and it works great. They have a handy guide to show you how: https://www.smarty.com/articles/hubspot-address-autocomplete-guide

0 Upvotes
jkeough78
Contributor

Real time street address lookup in form field

SOLVE

Hi @chrispower and @joneichler and @SabeFlex ,

 

Please upvote this idea here so HubSpot will do this for us 🙂

https://community.hubspot.com/t5/HubSpot-Ideas/Autocomplete-adress-form-fields-with-Google-maps/idi-...

 

 

joneichler
Participant | Gold Partner
Participant | Gold Partner

Real time street address lookup in form field

SOLVE

Unfortunately I'm not familiar with smarty streets, but if your not married to that option, you could use the google maps autocomplete api. 

 

This is a pretty good tutorial on it : https://www.w3docs.com/learn-javascript/places-autocomplete.html

 

You would just need to set the script to target the ID of the hubspot form field.

chrispower
Contributor

Real time street address lookup in form field

SOLVE

Hi

 

I am actually already familiar with smartystreets and have implemented it elsewhere in standalone websites.  The part I'm not clear on is how I would integrate it with a hubspot form. 

 

"You would just need to set the script to target the ID of the hubspot form field."

 

Can you elaborate on that please?

 

Thanks

 

Chris

0 Upvotes
joneichler
Participant | Gold Partner
Participant | Gold Partner

Real time street address lookup in form field

SOLVE

With the google solution you just target a specific Form field (using standard css targeting) to be your main address field and then you also set which fields it will poplulate once the user selects the address.

 

Sorry I can't be of more assistance.

0 Upvotes
alanbrown
Participant

Real time street address lookup in form field

SOLVE

@joneichler 

This sounds like a great solution I think a lot of people would utilize. Would you mind sharing a bit more about how you accomplished this?

 

The link you shared doesn;t contain any info about filling the remaining address fields with the info retrieved from the Places API.  Would you mind sharing your implementation? Even a brief example of some working code would go a long way.

SabeFlex
Participant

Real time street address lookup in form field

SOLVE

Did you ever receive any additional instructions?

0 Upvotes
atownsend1
Contributor

Real time street address lookup in form field

SOLVE

Smarty (formerly SmartyStreets) recently released a tutorial on how to get autocomplete working in Hubspot, and the principles outlined in their tutorial answer the question above.

https://www.smarty.com/articles/hubspot-address-autocomplete-guide

0 Upvotes