SureForms Auto Complete Address Suggestions

Google Places API Integration x SureForms

SureForms is quite new and does not yet support Google address auto suggestions (Address AutoSuggest) or Auto Complete (however you remember it).

We have developed a code-snippet that’ll ensure things “just work”.

You will need to replace APIKEYHERE with your own Google Places API key

Places API V2 and Places API V1 available

This one adds its own Google Places Styling – Depending on your use-case you may prefer this as it finds “more”.

/* SureForms Google Places (AU) — new widget + legacy fallback, multi-field */
(() => {
  'use strict';

  // === EDIT THESE SELECTORS (recommended) ===
  // Example: ['input[name="from_address"]','input[name="to_address"]']
  const SELECTORS = [];

  // === Your API key (restrict by HTTP referrer) ===
  const API_KEY = 'APIKEYHERE';
  const MAPS_SRC =
    'https://maps.googleapis.com/maps/api/js?key=' +
    encodeURIComponent(API_KEY) +
    '&v=weekly&loading=async&libraries=places';

  const STATE_MAP = {
    'New South Wales':'NSW','NSW':'NSW',
    'Victoria':'VIC','VIC':'VIC',
    'Queensland':'QLD','QLD':'QLD',
    'South Australia':'SA','SA':'SA',
    'Western Australia':'WA','WA':'WA',
    'Tasmania':'TAS','TAS':'TAS',
    'Northern Territory':'NT','NT':'NT',
    'Australian Capital Territory':'ACT','ACT':'ACT'
  };

  if (window.v8SureFormsPlacesInit) return;
  window.v8SureFormsPlacesInit = true;

  // Make the new element inherit colours
  const style = document.createElement('style');
  style.textContent = `
    gmpx-place-autocomplete, gmpx-place-autocomplete * { color: inherit; }
    gmpx-place-autocomplete { width: 100%; display:block; }
  `;
  document.head.appendChild(style);

  function explicitInputs() {
    if (!SELECTORS.length) return [];
    const out = [];
    SELECTORS.forEach(sel => document.querySelectorAll(sel).forEach(el => out.push(el)));
    return out;
  }

  function autoInputs() {
    return Array.from(
      document.querySelectorAll(
        // Be generous: SureForms/Blocksy variations
        '.srfm-address-block input.srfm-input-input,' +
        '.srfm-input-block input.srfm-input-input,' +
        '.srfm-block-single input[type="text"],' +
        'form input[type="text"].srfm-input-input'
      )
    );
  }

  function uniq(arr) {
    const s = new Set();
    const out = [];
    for (const el of arr) if (el && !s.has(el)) { s.add(el); out.push(el); }
    return out;
  }

  function targetInputs() {
    const explicit = explicitInputs();
    if (explicit.length) return uniq(explicit);
    // fallback to auto
    return uniq(autoInputs());
  }

  function findAddressBlock(el){
    return el.closest('.srfm-address-block') ||
           el.closest('.srfm-input-block') ||
           el.closest('.srfm-block-single') ||
           el.parentElement;
  }

  async function mountNewWidget(originalInput){
    if (!google?.maps?.places?.PlaceAutocompleteElement) return false;
    if (originalInput.dataset.v8PlacesReady) return true;
    originalInput.dataset.v8PlacesReady = '1';

    const pac = new google.maps.places.PlaceAutocompleteElement();
    pac.includedRegionCodes = ['au'];
    pac.setAttribute('placeholder', originalInput.getAttribute('placeholder') || 'Start typing your address');

    const block = findAddressBlock(originalInput);
    originalInput.parentNode.insertBefore(pac, originalInput);

    // Hide original but keep it in form
    Object.assign(originalInput.style, { position:'absolute', left:'-9999px', width:'1px', height:'1px' });
    originalInput.setAttribute('aria-hidden','true');
    originalInput.tabIndex = -1;

    // Mirror typing
    pac.addEventListener('input', () => {
      originalInput.value = pac.value || '';
      originalInput.dispatchEvent(new Event('input', { bubbles: true }));
    });

    async function applyPlace(place){
      try { await place.fetchFields({ fields: ['formattedAddress','addressComponents'] }); } catch(e){}
      if (place.formattedAddress) {
        originalInput.value = place.formattedAddress;
        originalInput.dispatchEvent(new Event('input', { bubbles: true }));
        originalInput.dispatchEvent(new Event('change', { bubbles: true }));
      }
      // Auto state
      const stateSelect = block ? block.querySelector('select.srfm-dropdown-input') : null;
      if (stateSelect && Array.isArray(place.addressComponents)) {
        let stateText = '';
        for (const c of place.addressComponents) {
          if (c.types && c.types.indexOf('administrative_area_level_1') !== -1) {
            stateText = (c.shortText || c.longText || '').trim();
            break;
          }
        }
        if (stateText) {
          const abbr = STATE_MAP[stateText] || stateText;
          setTimeout(() => {
            if (stateSelect.tomselect && typeof stateSelect.tomselect.setValue === 'function') {
              stateSelect.tomselect.setValue(abbr, true);
            } else {
              stateSelect.value = abbr;
              stateSelect.dispatchEvent(new Event('change', { bubbles: true }));
            }
          }, 100);
        }
      }
    }

    pac.addEventListener('gmp-select', async (ev) => {
      const pred = ev.placePrediction;
      if (pred && pred.toPlace) await applyPlace(pred.toPlace());
    });
    pac.addEventListener('gmp-placeselect', async (ev) => {
      if (ev.place) await applyPlace(ev.place);
    });

    return true;
  }

  function mountLegacy(input){
    if (!google?.maps?.places?.Autocomplete) return false;
    if (input.dataset.v8PlacesReady) return true;
    input.dataset.v8PlacesReady = '1';

    const ac = new google.maps.places.Autocomplete(input, {
      componentRestrictions: { country: 'au' },
      fields: ['address_components','formatted_address'],
      types: ['address']
    });

    ac.addListener('place_changed', () => {
      const place = ac.getPlace();
      if (!place || !place.address_components) return;

      input.value = place.formatted_address || input.value;
      input.dispatchEvent(new Event('input', { bubbles: true }));
      input.dispatchEvent(new Event('change', { bubbles: true }));

      const block = findAddressBlock(input);
      const stateSelect = block ? block.querySelector('select.srfm-dropdown-input') : null;
      if (!stateSelect) return;

      let state = '';
      place.address_components.forEach((c) => {
        if ((c.types || [])[0] === 'administrative_area_level_1') {
          state = c.short_name || c.long_name || '';
        }
      });
      if (!state) return;

      const abbr = STATE_MAP[state] || state;
      setTimeout(() => {
        if (stateSelect.tomselect && typeof stateSelect.tomselect.setValue === 'function') {
          stateSelect.tomselect.setValue(abbr, true);
        } else {
          stateSelect.value = abbr;
          stateSelect.dispatchEvent(new Event('change', { bubbles: true }));
        }
      }, 100);
    });

    return true;
  }

  function mountAll() {
    const inputs = targetInputs();
    inputs.forEach(async (el) => {
      // Try new widget first; fallback to legacy if missing
      const ok = await mountNewWidget(el);
      if (!ok) mountLegacy(el);
    });
  }

  function afterMapsReady(){
    // First pass
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', mountAll);
    } else {
      mountAll();
    }
    // Late-loaded steps/sections
    const mo = new MutationObserver(() => mountAll());
    try { mo.observe(document.body, { childList: true, subtree: true }); } catch(e){}
  }

  // Load Maps once (if already on page, we just proceed)
  function ensureMaps(callback){
    if (window.google && google.maps) {
      // places lib already loaded because we added &libraries=places
      callback();
      return;
    }
    if (document.querySelector('script[src^="https://maps.googleapis.com/maps/api/js"]')) {
      const t = setInterval(() => {
        if (window.google && google.maps) { clearInterval(t); callback(); }
      }, 80);
      return;
    }
    const s = document.createElement('script');
    s.src = MAPS_SRC; s.async = true; s.defer = true;
    s.onload = callback;
    s.onerror = () => setTimeout(() => ensureMaps(callback), 200);
    document.head.appendChild(s);
  }

  ensureMaps(afterMapsReady);
})();

This one is the legacy version – but does not add styling

<?php
/**
 * Google Places Autocomplete for SureForms
 */

function sureforms_google_places_integration() {
    // Replace with your Google Places API Key
    $api_key = 'APIKEYHERE';
   
    if (is_admin()) return;
    
    // Enqueue Google Places API
    wp_enqueue_script('google-places-api', 
        'https://maps.googleapis.com/maps/api/js?key=' . $api_key . '&libraries=places',
        array(), null, true);
    
    // Add inline JavaScript
    wp_add_inline_script('google-places-api', "
        (function() {
            'use strict';
            
            // Global namespace to prevent multiple initializations
            if (window.sureForms_places_initialized) {
                return;
            }
            window.sureForms_places_initialized = true;
            
            const config = {
                country: 'au',
                stateMapping: {
                    'New South Wales': 'NSW', 'NSW': 'NSW',
                    'Australian Capital Territory': 'ACT', 'ACT': 'ACT',
                    'Queensland': 'QLD', 'QLD': 'QLD',
                    'Victoria': 'VIC', 'VIC': 'VIC',
                    'South Australia': 'SA', 'SA': 'SA',
                    'Northern Territory': 'NT', 'NT': 'NT',
                    'Western Australia': 'WA', 'WA': 'WA',
                    'Tasmania': 'TAS', 'TAS': 'TAS'
                }
            };
            
            function setupAutocomplete(addressInput, stateSelect) {
                // Skip if already initialized
                if (addressInput.getAttribute('data-places-ready')) {
                    return;
                }
                addressInput.setAttribute('data-places-ready', '1');
                
                const autocomplete = new google.maps.places.Autocomplete(addressInput, {
                    componentRestrictions: { country: config.country },
                    fields: ['address_components', 'formatted_address'],
                    types: ['address']
                });
                
                autocomplete.addListener('place_changed', function() {
                    const place = autocomplete.getPlace();
                    if (!place.address_components) return;
                    
                    let state = '';
                    place.address_components.forEach(function(c) {
                        if (c.types[0] === 'administrative_area_level_1') {
                            state = c.short_name;
                        }
                    });
                    
                    addressInput.value = place.formatted_address;
                    
                    if (state && stateSelect) {
                        const stateAbbr = config.stateMapping[state] || state;
                        setTimeout(function() {
                            if (stateSelect.tomselect) {
                                stateSelect.tomselect.setValue(stateAbbr, true);
                            }
                        }, 150);
                    }
                });
            }
            
            function initFields() {
                if (typeof google === 'undefined' || !google.maps || !google.maps.places) {
                    return;
                }
                
                const blocks = document.querySelectorAll('.srfm-address-block');
                blocks.forEach(function(block) {
                    const input = block.querySelector('input.srfm-input-input');
                    const select = block.querySelector('select.srfm-dropdown-input');
                    if (input) {
                        setupAutocomplete(input, select);
                    }
                });
            }
            
            // Wait for Google API and DOM
            function tryInit() {
                if (document.readyState === 'loading') {
                    document.addEventListener('DOMContentLoaded', function() {
                        setTimeout(initFields, 500);
                    });
                } else {
                    setTimeout(initFields, 500);
                }
            }
            
            tryInit();
            
        })();
    ");
}
add_action('wp_enqueue_scripts', 'sureforms_google_places_integration');