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):

1const countriesPromise = fetch(
2 'https://static.ordergroove.com/@ordergroove/i18n-data/latest/i18n_country_data.json'
3).then(res => res.json());
4
5async function render_countries_options(enabled_countries, localized_countries) {
6 const html = ((window.og || {}).smi || {}).html;
7 const countriesByCode = await countriesPromise;
8 const enabledCountries = Object.entries(countriesByCode).filter(([code]) => enabled_countries.includes(code));
9 const countriesHtml = enabledCountries
10 .sort((a, b) => (a[1].name > b[1].name ? 1 : -1))
11 .reduce((acc, [code, { name }]) => {
12 const label = localized_countries[code] || name;
13 acc.push(html`
14 <option value="${code}">${label}</option>
15 `);
16 return acc;
17 }, []);
18 return countriesHtml;
19}
20
21function toggleStateSelect(stateSelectElement, show) {
22 const stateSelectLabelElement = stateSelectElement.parentNode.querySelector('label');
23 stateSelectElement.style.display = show ? 'block' : 'none';
24 stateSelectElement.required = show;
25 stateSelectLabelElement.style.display = show ? 'inline' : 'none';
26}
27
28async function handle_country_change(ev) {
29 const countriesByCode = await countriesPromise;
30 const selectedCountry = countriesByCode[ev.target.value];
31 const states = selectedCountry ? selectedCountry.regions : null;
32 const stateSelectElement = ev.target.closest('form').querySelector('[name="state_province_code"]');
33 const selectLabel = stateSelectElement.querySelector('option').innerText;
34
35 stateSelectElement.innerHTML = '';
36 const nullOption = document.createElement('option');
37 nullOption.value = '';
38 nullOption.innerText = selectLabel;
39 stateSelectElement.appendChild(nullOption);
40
41 if (selectedCountry) {
42 // Valid country selected
43 if (states) {
44 // Country has list of states/regions
45
46 // Show state select field
47 toggleStateSelect(stateSelectElement, true);
48
49 // Append all states to select
50 states.forEach(({ code, name }) => {
51 const option = document.createElement('option');
52 option.value = code;
53 option.innerText = name;
54 stateSelectElement.appendChild(option);
55 });
56 } else {
57 // Country has no array of states/regions, e.g. France
58 // Hide state select field
59 toggleStateSelect(stateSelectElement, false);
60 }
61 } else {
62 // Country un-selected
63 // Show state select field
64 toggleStateSelect(stateSelectElement, true);
65 }
66}
67
68function reset_state_options(element) {
69 element.innerHTML = '';
70 const option = document.createElement('option');
71 option.value = '';
72 option.innerText = 'Select a State/Province';
73 element.appendChild(option);
74 element.parentNode.querySelector('label').innerText = 'State/Province/Region ';
75}
76
77function close_address_modal(ev) {
78 close_closest_modal(ev);
79 const $stateSelect = ev.target.querySelector('[name="state_province_code"]');
80 reset_state_options($stateSelect);
81}

2. Rename Variable

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

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

3. Replace Lines 54 to 86

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

1<div class='og-row'>
2 <div class='og-form-group og-col'>
3 <label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.country_code' | setting }}' for='country_{{ order.public_id }}'>
4 {{ 'form_address_country' | t }}
5 </label>
6 <select class='og-form-select' name='country_code' id='country_code_{{ order.public_id }}' required @change={{ 'handle_country_change' | js }}>
7 <option value="">{{ 'country_select_text' | t }}</option>
8 {{ 'render_countries_options(enabled_countries, localized_countries)' | js | until }}
9 </select>
10 </div>
11 <div class='og-form-group og-col'>
12 <label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.state_province_code' | setting }}' for='state_{{ order.public_id }}'>
13 {{ 'form_address_state' | t }}
14 </label>
15 <select id='state_{{ order.public_id }}' class='og-form-select' name='state_province_code' required>
16 <option value="">{{ 'state_select_text' | t }}</option>
17 </select>
18 </div>
19</div>
20<div class='og-row'>
21 <div class='og-form-group og-col'>
22 <label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.city' | setting }}' for='city_{{ order.public_id }}'>
23 {{ 'form_address_city' | t }}
24 </label>
25 <input type='text' id='city_{{ order.public_id }}' class='og-form-control' name='city' ?required='{{ 'required_fields.shipping_address.city' | setting }}'/>
26 </div>
27 <div class='og-form-group og-col'>
28 <label class='og-form-label {{ 'og-required' if 'required_fields.shipping_address.zip_postal_code' | setting }}' for='zip_{{ order.public_id }}'>
29 {{ 'form_address_zip' | t }}
30 </label>
31 <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 }}'/>
32 </div>
33</div>

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