Implementing Country & State Dropdown in Subscription Manager Themes

Add a country/state dropdown to the shipping address form to reduce typos and improve the customer experience

View as Markdown

Rather than asking a customer to manually type the state or province of their shipping address — risking typos or formatting issues — you can provide a list of states and provinces based on the selected country. If your Subscription Manager doesn’t have this feature, you can add it through the advanced editor.


Technical Details

The list of states/provinces is fetched in script.js by calling:

https://static.ordergroove.com/@ordergroove/i18n-data/latest/i18n_country_data.json

Country names are pulled from locale files first (e.g. en-US.json). If no translation is provided, the default from i18n_country_data.json is used (English).


Accessing the Advanced Editor

You can access the advanced editor through your Ordergroove Admin:

  1. Log in to Ordergroove.
  2. Go to Subscriptions on the top toolbar, and select Subscription Manager.
  3. Toggle Advanced on the top left.
Support

Some aspects of this article require technical expertise with coding languages. This is self-serve and outside of the normal support policy.


Including Changes without Overwriting Customizations

To include this functionality without losing existing customizations, edit script.js as follows.

1. Add the Following Functions

Add the following code to script.js (referenced from GitHub):

const countriesPromise = fetch(
'https://static.ordergroove.com/@ordergroove/i18n-data/latest/i18n_country_data.json'
).then(res => res.json());
async function render_countries_options(enabled_countries, localized_countries) {
const html = ((window.og || {}).smi || {}).html;
const countriesByCode = await countriesPromise;
const enabledCountries = Object.entries(countriesByCode).filter(([code]) => enabled_countries.includes(code));
const countriesHtml = enabledCountries
.sort((a, b) => (a[1].name > b[1].name ? 1 : -1))
.reduce((acc, [code, { name }]) => {
const label = localized_countries[code] || name;
acc.push(html`
<option value="${code}">${label}</option>
`);
return acc;
}, []);
return countriesHtml;
}
function toggleStateSelect(stateSelectElement, show) {
const stateSelectLabelElement = stateSelectElement.parentNode.querySelector('label');
stateSelectElement.style.display = show ? 'block' : 'none';
stateSelectElement.required = show;
stateSelectLabelElement.style.display = show ? 'inline' : 'none';
}
async function handle_country_change(ev) {
const countriesByCode = await countriesPromise;
const selectedCountry = countriesByCode[ev.target.value];
const states = selectedCountry ? selectedCountry.regions : null;
const stateSelectElement = ev.target.closest('form').querySelector('[name="state_province_code"]');
const selectLabel = stateSelectElement.querySelector('option').innerText;
stateSelectElement.innerHTML = '';
const nullOption = document.createElement('option');
nullOption.value = '';
nullOption.innerText = selectLabel;
stateSelectElement.appendChild(nullOption);
if (selectedCountry) {
// Valid country selected
if (states) {
// Country has list of states/regions
// Show state select field
toggleStateSelect(stateSelectElement, true);
// Append all states to select
states.forEach(({ code, name }) => {
const option = document.createElement('option');
option.value = code;
option.innerText = name;
stateSelectElement.appendChild(option);
});
} else {
// Country has no array of states/regions, e.g. France
// Hide state select field
toggleStateSelect(stateSelectElement, false);
}
} else {
// Country un-selected
// Show state select field
toggleStateSelect(stateSelectElement, true);
}
}
function reset_state_options(element) {
element.innerHTML = '';
const option = document.createElement('option');
option.value = '';
option.innerText = 'Select a State/Province';
element.appendChild(option);
element.parentNode.querySelector('label').innerText = 'State/Province/Region ';
}
function close_address_modal(ev) {
close_closest_modal(ev);
const $stateSelect = ev.target.querySelector('[name="state_province_code"]');
reset_state_options($stateSelect);
}

2. Rename Variable

In change-shipment-address.liquid, on line 5, rename the variable for clarity:

{% set localized_countries = 'localized_countries' | t %}

3. Replace Lines 54 to 86

In change-shipment-address.liquid, replace lines 54–86 with the following:

<div class='og-row'>
<div class='og-form-group og-col'>
<label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.country_code' | setting }}' for='country_{{ order.public_id }}'>
{{ 'form_address_country' | t }}
</label>
<select class='og-form-select' name='country_code' id='country_code_{{ order.public_id }}' required @change={{ 'handle_country_change' | js }}>
<option value="">{{ 'country_select_text' | t }}</option>
{{ 'render_countries_options(enabled_countries, localized_countries)' | js | until }}
</select>
</div>
<div class='og-form-group og-col'>
<label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.state_province_code' | setting }}' for='state_{{ order.public_id }}'>
{{ 'form_address_state' | t }}
</label>
<select id='state_{{ order.public_id }}' class='og-form-select' name='state_province_code' required>
<option value="">{{ 'state_select_text' | t }}</option>
</select>
</div>
</div>
<div class='og-row'>
<div class='og-form-group og-col'>
<label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.city' | setting }}' for='city_{{ order.public_id }}'>
{{ 'form_address_city' | t }}
</label>
<input type='text' id='city_{{ order.public_id }}' class='og-form-control' name='city' ?required='{{ 'required_fields.shipping_address.city' | setting }}'/>
</div>
<div class='og-form-group og-col'>
<label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.zip_postal_code' | setting }}' for='zip_{{ order.public_id }}'>
{{ 'form_address_zip' | t }}
</label>
<input type='text' id='zip_{{ order.public_id }}' class='og-form-control' name='zip_postal_code' ?required='{{ 'required_fields.shipping_address.zip_postal_code' | setting }}'/>
</div>
</div>

And you’re all set — no changes are needed to smi-core.