Address Validation with USPS or Smartystreets.com?

Does anyone know how I would go about validating an address field on a form, using the Autofill feature on Smartystreets.com?

Autocomplete Coding Example from SmartyStreets

I want to use smartystreets to validate an address field(s) and have that data populate the form automatically.

Is this possible?

Also, similary, is it possible to do this same type of autocomplete with other API's also?

No, I never did. Can't figure it out.

Did you ever get this implemented?

Hi Van,

Did you ever get the code implemented? Its something I am interesting in as well.

This isn't the complete answer, but to steer you toward how to activate code on a form submission you can see how this is done in my answer to some manipulating dates in a form/record here: Complex date calculation - from & to times

Also you may want to refer to Knack's documentation on events here: Working with the API and Code Example - Custom Form Validation and Customization with jQuery and Javascript - Knack Events

Not a full walkthrough, but those are the points I started with to solve the "add 30 minutes to the start to get the appointment" problem presented in that first link.

I just have no idea how to implement this code when there is no option to add javascript to an individual form, only a blanket javascript for the whole app. Confused about how this works. Please help.

Smarty (formerly SmartyStreets) does offer a solution for Address Validation and Autocomplete. I know this response is 11 years late, but hopefully it will still help for somebody in the future. I got it to work using the below code. It might not be universal for everybody’s implementation, but it should at least get you started!

This is the custom Javascript snippet I used. Please note that this is referencing my knack form, which has a key of ‘view_90’, so you’ll need to replace that with the view key of your form. In that same vein, the Billing Address field that I implemented this on was referenced as ‘field_51’ in the Knack form’s HTML, so you’ll need to replace that with the proper field name as well.

The only other thing you’ll need to change is adding your own Smarty Embedded Key to the base url in line 42. Once you’ve replaced all those values, this solution should be plug and play (at least at time of writing).

Knack.on('view:render:view_90', async function (event) {

  const autocompleteDiv = document.createElement('div');
  autocompleteDiv.classList.add('smarty-autocomplete');
  autocompleteDiv.hidden = true;
  const input = document.getElementById('view_90-field_51_street');

  input.insertAdjacentElement('afterend', autocompleteDiv);

  input.addEventListener('focus', showDropdown);
  input.addEventListener('keyup', () => {
    if (input.value == '') {
      hideDropdown();
    } else {
      showDropdown();
    }
  });
  input.addEventListener('blur', () => setTimeout(() => hideAutocomplete(autocompleteDiv), 100));

  let suggestArray = [];
  let spanArray = [];
  let curSuggestions = [];
  for (let i = 0; i < 100; i++) {
    suggestArray.push(document.createElement('div'));
    suggestArray[i].classList.add('smarty-suggestion');
    suggestArray[i].setAttribute('data-selected', '');
    suggestArray[i].setAttribute('data-index', i);
    let tempSpan = document.createElement('span');
    tempSpan.classList.add('smarty-span');
    spanArray.push(tempSpan);
    suggestArray[i].appendChild(tempSpan);
    autocompleteDiv.appendChild(suggestArray[i]);
  }

  positionAutocomplete();
  window.addEventListener('resize', positionAutocomplete);
  window.addEventListener('scroll', positionAutocomplete, true);

  const streetField = document.getElementById('view_90-field_51_street');
  streetField.addEventListener("keyup", async (event) => {
    if (input.value != '') {
      const baseUrl = "https://us-autocomplete-pro.api.smarty.com/lookup?key=<your-embedded-key-here>";
      var finalUrl = baseUrl + '&search=' + streetField.value;
      try {
        const response = await fetch(finalUrl);
        if (!response.ok) {
          throw new Error(`Response status: ${response.status}`);
        }
        else if (response != null) {
          json = await response.json();
          curSuggestions = json.suggestions;
          emptySpans(spanArray, suggestArray);
          fillSpans(spanArray, json.suggestions, suggestArray);
        }
      } catch (error) {
        console.error(error.message);
      }
    }
  });

  autocompleteDiv.addEventListener('click', async (e) => {
    const suggestion = e.target.closest('.smarty-suggestion');
    if (!suggestion) return; // click was not on a suggestion

    let curIndex = suggestion.getAttribute('data-index');

    if (curSuggestions[curIndex].entries > 1) {
      const baseUrl = "https://us-autocomplete-pro.api.smarty.com/lookup?key=184623268557701056";
      var finalUrl = baseUrl + '&search=' + streetField.value + '&selected=' + suggestion.getAttribute('data-selected');
      try {
        const response = await fetch(finalUrl);
        if (!response.ok) {
          throw new Error(`Response status: ${response.status}`);
        }
        else if (response != null) {
          let json = await response.json();
          curSuggestions = json.suggestions;
          emptySpans(spanArray, suggestArray);
          fillSpans(spanArray, json.suggestions, suggestArray);
          setTimeout(() => input.focus(), 100)
          autocompleteDiv.hidden = false;
        }
      } catch (error) {
        console.error(error.message);
      }
    }
    else {
      streetField.value = curSuggestions[curIndex].street_line;
      if (curSuggestions[curIndex].secondary != null) {
        document.getElementById('view_90-field_51_street2').value = curSuggestions[curIndex].secondary;
      }
      document.getElementById('view_90-field_51_city').value = curSuggestions[curIndex].city;
      document.getElementById('view_90-field_51_state').value = curSuggestions[curIndex].state;
      document.getElementById('view_90-field_51_zip').value = curSuggestions[curIndex].zipcode;
    }
  });
});

function positionAutocomplete() {
  const input = document.getElementById('view_90-field_51_street'); // adjust selector
  const dropdown = document.querySelector('.smarty-autocomplete');

  const rect = input.getBoundingClientRect();

  dropdown.style.position = 'fixed';
  dropdown.style.top = `${rect.bottom}px`;
  dropdown.style.left = `${rect.left}px`;
  dropdown.style.width = `${rect.width}px`;
}

function showDropdown() {
  const autocompleteDiv = document.querySelector('.smarty-autocomplete');
  const input = document.getElementById('view_90-field_51_street');
  if (input.value != '') {
    autocompleteDiv.hidden = false;
  }
}

function hideDropdown() {
  const autocompleteDiv = document.querySelector('.smarty-autocomplete');
  autocompleteDiv.style.hidden = true;
}

function emptySpans(spanArray, suggestArray) {
  for (let i = 0; i < spanArray.length; i++) {
    spanArray[i].innerHTML = '';
    suggestArray[i].setAttribute('data-selected', '');
  }
}

function fillSpans(spanArray, resultArray, suggestArray) {
  for (let i = 0; i < resultArray.length; i++) {
    spanArray[i].innerHTML = buildSuggestion(resultArray[i]);
    suggestArray[i].setAttribute('data-selected', buildSelected(resultArray[i]));
    if (resultArray[i].entries != null && resultArray[i].entries == 0) {
    }
    else {
    }
  }
}

function buildSuggestion(suggestObj) {
  let suggestion = ''
  if (suggestObj.street_line != null) {
    suggestion += suggestObj.street_line;
  }
  if (suggestObj.city != null) {
    suggestion += ' ' + suggestObj.city;
  }
  if (suggestObj.state != null) {
    suggestion += ' ' + suggestObj.state;
  }
  if (suggestObj.zipcode != null) {
    suggestion += ' ' + suggestObj.zipcode;
  }
  if (suggestObj.secondary != '' && suggestObj.entries != 0 && suggestObj.entries != 1) {
    suggestion += ' ' + suggestObj.secondary + '(+' + suggestObj.entries + ')';
  }
  else if (suggestObj.entries == 1) {
    suggestion = suggestObj.street_line + ' ' + suggestObj.secondary + ' ' + suggestObj.city + ' ' + suggestObj.state + ' ' + suggestObj.zipcode;
  }
  return suggestion;
}

function buildSelected(suggestObj) {
  let selected = suggestObj.street_line;
  if (suggestObj.secondary != '' && suggestObj.entries != 0) {
    selected += ' ' + suggestObj.secondary + ' (' + suggestObj.entries + ')';
  }
  return selected + ' ' + suggestObj.city + ' ' + suggestObj.state + ' ' + suggestObj.zipcode;
}

function hideAutocomplete(autocompleteDiv) {
  autocompleteDiv.hidden = true;
}

There’s also a little bit of custom CSS that makes it fit the form a bit better and hide the dropdown properly. I’ve included what I used below:

.smarty-autocomplete {
  z-index: 1000;

  background-color: #fff;
  border: 1px solid #ccc;
  border-top: none;
  border-radius: 0 0 4px 4px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);

  max-height: 240px;
  overflow-y: auto;
  margin: 0;
  padding: 0;
}

.smarty-span {
  padding-left: 13px;
}

.smarty-suggestion:has(span:empty) {
  display: none;
}

.smarty-suggestion:hover {
  cursor: pointer;
  background: lightgrey;
}

The USPS recently implemented some rate limiting on their address verification that can be limiting to users with high volumes, and they don’t actually offer an Autocomplete API solution, so for auto-filling the form, their publicly available APIs may not work for this particular use case.

The Google Autocomplete solution suffers a bit on accuracy. It occasionally suggests addresses that aren’t actually valid and deliverable, but seem plausible or “close enough” to users, which can lead to incorrect address submissions, missed deliveries, etc … You can find a comparison between the Google and Smarty solutions here.

FYI, I do work for Smarty (formerly SmartyStreets). If anybody finding this thread in the future has any questions, you can get direct support from a real human by using the chat feature on our website, or emailing support@smarty.com.