> For the complete documentation index, see [llms.txt](https://api-docs.devhub.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://api-docs.devhub.com/visitor-localization-sdk/visitor-localization-sdk.md).

# Visitor Localization SDK

## What is Visitor Localization

Allows you to "localize" corporate website experiences using the Local/Franchisee information. Examples include showing the Location name and phone number in the Header with key CTAs like calling, visiting the Local site or contacting the Location.

Franchisee information can be displayed within corporate pages after they have already visited the Franchisee's local site, or optionally you can auto-detect the visitor location and show them the local information of the Franchisee nearest to them.

<figure><img src="/files/s3O4vL9BYvEbN2wY0duz" alt=""><figcaption><p>Example showing Header localization to "Tucson" with a local phone number and local "Book Online" CTA button.</p></figcaption></figure>

## Configuration options

List of all the available script options that can be set (via `sbGeoipOptions`). More details on each option [below](#more-details-on-configuration-options)

| Option                      | Description                                                                                                                                                 | Configuration examples                                                                                           |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `automaticLocalization`     | Automatically localize the visitor using their IP address                                                                                                   | `true` or `false` (default)                                                                                      |
| `automaticLocalizationType` | <p>Which part of the Geo IP data should be used for the search<br><br>Either lat/lon estimate, or if available the estimated postal code of the visitor</p> | `latlon` (default) or `postal_code`                                                                              |
| `dni`                       | Dynamic Number Insertion to replace all numbers within pages to the local number                                                                            | `true` or `false` (default)                                                                                      |
| `ignorePaths`               | List of website page paths where you do not want the Visitor localization to run                                                                            | <p><code>\[</code><br>  <code>"/locations/",</code></p><p>  <code>"/privacy-policy"</code><br><code>]</code></p> |
| `localizeLinks`             | Option to provide a list of page paths that should be "localized" to the Franchisee/Local site version when clicked                                         | <p><code>\[</code><br>  <code>"/about-us/",</code></p><p>  <code>"/contact-us/"</code><br><code>]</code></p>     |
| `locationApiOptions`        | Advanced options to configure the Local search API call used when `automaticLocalization` is enabled.                                                       | <p><code>{</code><br>    <code>threshold: 200</code><br><code>}</code></p>                                       |
| `locationCacheType`         | Method of browser storage used for the Location information                                                                                                 | `"localStorage"` (default) or `"sessionStorage"`                                                                 |
| `onLocationSet`             | Primary callback function that runs once we have the Franchisee/Location information available                                                              | <p><code>function (data) {</code></p><p>  <code>// use the localization data</code><br><code>}</code></p>        |

More details on each option [below](#more-details-on-configuration-options)

## Installation

Place the following script at the end of the `<body>` of any page your want the localization to run.&#x20;

* Replace the `data-api-key="YOUR-API-KEY-HERE"` with the API key provided to you by DevHub.
* Update the `onLocationSet` to use the Location information to update your page. More examples below
* Add additional configuration options to `sbGeoipOptions` as needed

```html
<script>
var sbGeoipOptions = {
  onLocationSet: function (data) {
    // use the Location information to update your page
    // additional examples below
  }
};
</script>
<script defer id="sb-geoip" data-api-key="YOUR-API-KEY-HERE" src="https://cloudbackend.scdn7.secure.raxcdn.com/stat/jsutils/js/geoip_localization.js"></script>
```

## Example: Simple Vanilla JS example

Simple `onLocationSet` example that displays the Location name within an element on the page.

### Target element

```html
<div id="custom-element">
    Placeholder content
</div>
```

### `onLocationSet` example

```javascript
var sbGeoipOptions = {
  onLocationSet: function (data) {
    let el = document.getElementById('custom-element');
    if (el) {
        el.innerHTML = '<span>' + data.location.location_name + '</span>';
    }
  }
};
```

## Example: Using Handlebars.js templating

For more complicated HTML templating, we suggest using a client-side templating library like [Handlebars.js](https://handlebarsjs.com/) to render the HTML needed.

Here is an example that replaces an element's content with some HTML containing the location name, phone number, CTA button and the display's the estimated visitor's zip code if available.

### Target element

```html
<div id="custom-element">
    Placeholder content
</div>
```

### Handlebars template and utility function

<pre class="language-html"><code class="lang-html">&#x3C;script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.12/handlebars.min.js">&#x3C;/script>
<strong>
</strong><strong>&#x3C;script id="localization-template" type="text/x-handlebars-template">
</strong>&#x3C;div class="sb-location-name">{{data.location.location_name}}&#x3C;/div>
&#x3C;div class="sb-location-phone">{{data.location.phonemap.phone}}&#x3C;/div>
&#x3C;div class="sb-location-cta">
  &#x3C;a href="{{data.location.location_url}}">Visit website&#x3C;/a>
&#x3C;/div>
{{# if geoip.zip}}
  &#x3C;div class="sb-visitor-postal-code">at {{data.geoip.zip}}&#x3C;/div>
{{/if}}
&#x3C;/script>

&#x3C;script>
var sbHandlebarsTemplate = function(id) {
  // handling of handlebars custom templates
  var el = document.getElementById(id);
  if (typeof Handlebars === 'undefined' || !el || !el.innerHTML) {
    return;
  }
  return Handlebars.compile(el.innerHTML);
};
&#x3C;/script>
</code></pre>

### `onLocationSet` example

```javascript
var sbGeoipOptions = {
  onLocationSet: function (data) {
    let el = document.getElementById('custom-element');
    if (el) {
        let tpl = sbHandlebarsTemplate('localization-template');
        el.innerHTML = tpl(response);
    }
  }
};
```

## More details on configuration options

### Automatic localization (`automaticLocalization`)

If a visitor does not already have a Franchisee/Location assigned from a previous visit, should we use the visitor's IP address to search for their nearest location.

If disabled, then the only way a visitor is assigned a Location is by visiting a Franchisee/Local site first.

#### Example:

```javascript
var sbGeoipOptions = {
  automaticGeolocation: true
};
```

### Dynamic Number Insertion (`dni`)

Using the Franchisee/Location phone number entered into DevHub, should we replace all phone numbers within the corporate page content with the local number.

*Note: if you are using your own call tracking provider, you will want to confirm that their DNI scripts work well in combination with ours.*

#### Example:

```javascript
var sbGeoipOptions = {
  dni: true
};
```

### Ignore Paths (`ignorePaths`)

This is a list of website page paths where you do not want the Visitor location to run. If one of these paths if opened by the visitor, no localization will execute. List of page paths starting with slash ("/")

#### Example:

```javascript
var sbGeoipOptions = {
  ignorePaths: [
    "/locations/",
    "/privacy-policy/"
  ]
};
```

### Localize links (`localizeLinks`)

Option to provide a list of page paths that should be "localized" to the Franchisee/Local site version when clicked.

**Example:** if the corporate site has a `/contact-us/` and an `/about-us/` page, and the Franchisee sites also have the same pages, but under their Location site URL like `/seattle/contact-us/` and `/seattle/about-us/`. If the visitor clicks on any corporate link to those pages, they will instead get routed to the Local version of that page instead.

*Note: This only works if the paths are exactly the same on the Corporate and Local sites*

#### Example:

```javascript
var sbGeoipOptions = {
  localizeLinks: [
    "/about-us/",
    "/contact-us/"
  ]
};
```

### Location search API options (`locationApiOptions`)

Advanced options to configure the Local search API call used when `automaticLocalization` is enabled.

* `threshold: 200` - Used to limit how far a nearby location will be searched based on the visitor location.

#### Example:

```javascript
var sbGeoipOptions = {
  locationApiOptions: {
    threshold: 250
  }
};
```

### Location cache type (`locationCacheType`)

Method of storage for the Location information in the browser.

`"localStorage"` (default) or `"sessionStorage"`.&#x20;

* `localStorage` will keep the location information if the visitor closes the window and comes back for a future session
* `sessionStorage` will only keep the visitor localized for the current session and cleared after closing the browser window.

#### Example:

```javascript
var sbGeoipOptions = {
  locationCacheType: "localStorage"
};
```

### Location set (`onLocationSet`)

This is the primary callback function that runs once we have the Franchisee/Location information available. This is where you can then use this information to render content into your site.

On successful lookup, will return a response object that contains the following:

#### response.location <a href="#response.location" id="response.location"></a>

This is our standard [Location](/core-resources/locations.md#location-object) object containing address, phones, and location\_url

#### response.geoip <a href="#response.geoip" id="response.geoip"></a>

This is the geo IP data about the visitor

Includes:

* `geoip.zip` - Zip code detected for the visitor

#### Example:

```javascript
var sbGeoipOptions = {
  onLocationSet: function (data) {
    // use the Location information to update your page
    // additional examples within these docs
  }
};
```

## Allow visitors to provide their own search to determine localization

Using the `searchLocations` method to re-search locations using a manually entered search query (city and state, postal code or full address). Typically this would be tied to a change location Form allowing the visitor to  provide a more specific location.

Once `searchLocations` is called, a new Location search is executed. If a nearby location is found, it will re-trigger the `onLocationSet` callback. It will also return an optional callback function that can be used for logic based on success or error in the search.

#### Basic usage

```javascript
sbGeoip.searchLocations('seattle, wa', function(response) {
  if (response.error) {
    // handle error statuses
  }
  else {
    // successful search
  }
});
```

#### Error statuses

Error statuses will return data under `response.error`. Example:

```json
{
  "error": "NOT_FOUND",
  "error_message": "No matching locations for search"
}
```

| error           | error\_message                   | Description                                                                                                         |
| --------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `NOT_FOUND`     | No matching locations for search | No nearby location found based on the distance settings and search                                                  |
| `BAD_STATUS`    |                                  | Bad status response for the search query. Usually caused by search queries that Google cannot determine a response. |
| `REQUEST_ERROR` |                                  | Network error or unknown error code                                                                                 |

### Full example with Form and input field

Below is a full reference example implementation of a Button that activates a Form allowing the visitor to search. During the search a Loading message appears. If successful, the form will hide. If there is an error, it will be displayed within the form.

```html
<button id="sb-change-location">Change location</button>

<form id="localize-form" class="hide">
  <div class="sb-form-fields">
    <input type="text" name="q" required />
    <button type="submit">Search</button>
  </div>
  <div class="sb-loading hide">
    Loading...
  </div>
  <div class="sb-error hide"></div>
</form>

<style>
.hide { display: none; }
</style>

<script>
// form handling
const form = document.getElementById('localize-form');
const errElement = form.getElementsByClassName('sb-error')[0];
const fieldsElement = form.getElementsByClassName('sb-form-fields')[0];
const loadingElement = form.getElementsByClassName('sb-loading')[0];
form.addEventListener('submit', (e) => {
  e.preventDefault();
  const currentTarget = e.currentTarget;
  const formData = new FormData(e.currentTarget);
  // hide form fields
  fieldsElement.classList.add('hide');
  // hide errors
  errElement.classList.add('hide');
  // show loader
  loadingElement.classList.remove('hide');
  // trigger a re-searching
  sbGeoip.searchLocations(formData.get('q'), function(resp) {
    // hide loader
    loadingElement.classList.add('hide');
    if (resp.error) {
      // error returned
      // show error
      errElement.innerHTML = resp.error_message;
      errElement.classList.remove('hide');
    } else {
      // hide the form
      currentTarget.classList.add('hide');
    }
    // re-show the form fields
    fieldsElement.classList.remove('hide');
  });
});

// button activation
document.getElementById('sb-change-location').addEventListener('click', (e) => {
  // show form
  form.classList.remove('hide');
  // focus on input and clear value
  form.querySelector('input[name=q]').focus();
  form.querySelector('input[name=q]').value = '';
})
</script>
```

## Using Headless content to Localize navigations

Using the `headlessContent` utility within `onLocationSet`, you can fetch a copy of the Local site header or footer and using that HTML content then "mirror" that Local navigation into the Corporate navigation.

```javascript
onLocationSet: function(response) {

  if (response.location.location_url) {
    sbGeoip.headlessContent(response.location.location_url, ['header_html'], (content) => {
      // get the local nav element
      const localNav = content.rendered.header_html.getElementById('nav-target');
      // replace the current nav
      const corpNav = document.getElementById('nav-target');
      if (corpNav && localNav) {
        corpNav.replaceWith(localNav);
      }
    });
  }

}
```

## Using Headless content to Localize page modules

Using the `headlessContent` utility within `onLocationSet`, you can fetch the rendered "Localization" template that is part of your Theme. These blocks do not show on the rendered local site in any way, but this is where you can build modules/blocks of Localized content that you can then use within Corporate website experiences as needed.

<figure><img src="/files/0GyJDKjq8Imlw3cmtDv1" alt=""><figcaption><p>Example of adding Localized blocks to the Theme</p></figcaption></figure>

The example below grabs that localized content and then updates an element (`id="localization-target"`) on the corporate page when localized.

```javascript
onLocationSet: function(response) {

  if (response.location.location_url) {
    sbGeoip.headlessContent(response.location.location_url, ['localization_html'], (content) => {
      const localBlock = content.rendered.localization_html.getElementById('localization-content');
      const localizationTarget = document.getElementById('localization-target');
      if (localBlock && localizationTarget) {
        localizationTarget.innerHTML = localBlock.innerHTML;
      }
    });
  }

}
```

**Note:** you should only have one `headlessContent` call within `onLocationSet`. So if you already have other implementations (i.e. `header_html`) you would just need to add `localization_html` to the list of elements to grab under `headlessContent`.

### Localization testing page

If you want to create a testing page within the Local template, you can add a noindexed page and add the following JS to display the local content.

```html
<div id="localization-test-target"></div>
<script>
const localized = localStorage.getItem('sb:geoip:headless_content');
const el = document.getElementById('localization-test-target');
if (el && localized) {
  const parsed = JSON.parse(localized);
  el.innerHTML = parsed.rendered.localization_html;
}
</script>
```

## Example using geoip to fetch multiple nearby locations to the visitor

This example uses the Geo IP information to then fetch multiple locations using our [Headless Locations search API](/headless-cms/locations-search.md) and then writes a list of Locations into an HTML element ( `nearby-locations-target`)

```html
<div id="nearby-locations-target"></div>

<script>
function loadNearbyLocations(geoip) {
  const request = new XMLHttpRequest();
  const queryParams = {
    near_lat: geoip.latitude,
    near_lon: geoip.longitude,
  };
  let query = new URLSearchParams(queryParams).toString();
  request.open('GET', '/_api/locations_search/?' + query, true);
  request.onload = () => {
    if (request.status >= 200 && request.status < 400) {
      const data = JSON.parse(request.response);
      let locationsHTML = '';
      data.objects.forEach(function(location) {
        locationsHTML += `<div>
          <p>${location.location_name}</p>
          <p>${location.street}</p>
          <p>${location.city}, ${location.state} ${location.postal_code}</p>
          <p>${location.phonemap.phone}</p>
        </div>`;
      });
      document.getElementById('nearby-locations-target').innerHTML = locationsHTML;
    }
  }
  request.send();
}
let geoipData = sessionStorage.getItem('sb:geoip');
if (geoipData) {
  // geoip already cached
  loadNearbyLocations(JSON.parse(geoipData));
}
else {
  // wait for the geoip to be loaded
  document.addEventListener('sbevents:geoip:geoip_detected', function(e) {
    loadNearbyLocations(e.detail.geoip)
  });
}
</script>
```
