UNPKG

country-region-selector

Version:

A simple, configurable JS library that add a country dropdown that automatically updates a corresponding region dropdown in your forms.

353 lines (297 loc) 13.4 kB
/** * country-region-selector * ----------------------- * <%=__VERSION__%> * @author Ben Keen * @repo https://github.com/benkeen/country-region-selector * @licence MIT */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module define([], factory); } else if (typeof exports === 'object') { // Add try/catch for CommonJS-like environments that support module.exports try { module.exports = factory(require()); } catch (err) { module.exports = factory(); } } else { // browser globals (root is window) root.crs = factory(root); } }(this, function () { "use strict"; var _countryClass = "crs-country"; var _defaultCountryStr = "Select country"; var _defaultRegionStr = "Select region"; var _showEmptyCountryOption = true; var _showEmptyRegionOption = true; var _countries = []; var _memoizedIndexes = {}; // included during grunt build step (run `grunt generate` on the command line) //<%=__DATA__%> var _init = function () { _countries = _data; var countryDropdowns = document.getElementsByClassName(_countryClass); for (var i = 0; i < countryDropdowns.length; i++) { _populateCountryFields(countryDropdowns[i]); } }; var _populateCountryFields = function (countryElement) { // ensure the dropdown only gets initialized once var loaded = countryElement.getAttribute("data-crs-loaded"); if (loaded === "true") { return; } countryElement.length = 0; var customOptionStr = countryElement.getAttribute("data-default-option"); var defaultOptionStr = customOptionStr ? customOptionStr : _defaultCountryStr; var showEmptyOption = countryElement.getAttribute("data-show-default-option"); _showEmptyCountryOption = (showEmptyOption === null) ? true : (showEmptyOption === "true"); var defaultSelectedValue = countryElement.getAttribute("data-default-value"); var customValue = countryElement.getAttribute("data-value"); var foundIndex = 0; if (_showEmptyCountryOption) { countryElement.options[0] = new Option(defaultOptionStr, ''); } // parses the region data into a more manageable format _initRegions(); var countries = _getCountries(countryElement); for (var i = 0; i < countries.length; i++) { var val = (customValue === "shortcode" || customValue === "2-char") ? countries[i][1] : countries[i][0]; // workaround to allow the preferred countries delimiter have an empty value if (countries[i][4]) { val = ""; } countryElement.options[countryElement.length] = new Option(countries[i][0], val); if (defaultSelectedValue != null && defaultSelectedValue === val) { foundIndex = i; if (_showEmptyCountryOption) { foundIndex++; } } } countryElement.selectedIndex = foundIndex; var regionID = countryElement.getAttribute("data-region-id"); if (!regionID) { console.error("Missing data-region-id on country-region-selector country field."); return; } var regionElement = document.getElementById(regionID); if (regionElement) { _initRegionField(regionElement); countryElement.onchange = function () { _populateRegionFields(countryElement, regionElement); }; // if the country dropdown has a default value, populate the region field as well if (defaultSelectedValue !== null && countryElement.selectedIndex > 0) { _populateRegionFields(countryElement, regionElement); var defaultRegionSelectedValue = regionElement.getAttribute("data-default-value"); var useShortcode = (regionElement.getAttribute("data-value") === "shortcode"); if (defaultRegionSelectedValue !== null) { var index = (_showEmptyCountryOption) ? countryElement.selectedIndex - 1 : countryElement.selectedIndex; var data = countries[index][3]; _setDefaultRegionValue(regionElement, data, defaultRegionSelectedValue, useShortcode); } } else if (_showEmptyCountryOption === false) { _populateRegionFields(countryElement, regionElement); } } else { console.error("Region dropdown DOM node with ID " + regionID + " not found."); } countryElement.setAttribute("data-crs-loaded", "true"); }; var _initRegionField = function (el) { var customOptionStr = el.getAttribute("data-blank-option"); var defaultOptionStr = customOptionStr ? customOptionStr : "-"; var showEmptyOption = el.getAttribute("data-show-default-option"); _showEmptyRegionOption = (showEmptyOption === null) ? true : (showEmptyOption === "true"); el.length = 0; if (_showEmptyRegionOption) { el.options[0] = new Option(defaultOptionStr, ""); el.selectedIndex = 0; } }; // called for every component instantiation. Before, this used to construct _countries with the appropriate list // based on whitelist/blacklist, but that causes problems when there are multiple fields some with/without // black/whitelists. Instead, this just memoizes the whitelist/blacklist for quick lookup var _getCountrySubset = function (params) { var key = params.whitelist + "|" + params.blacklist; var i = 0; if (!_memoizedIndexes.hasOwnProperty(key)) { _memoizedIndexes[key] = []; if (params.whitelist) { var whitelist = params.whitelist.split(","); for (i = 0; i < _data.length; i++) { if (whitelist.indexOf(_data[i][1]) !== -1) { _memoizedIndexes[key].push(i); } } } else if (params.blacklist) { var blacklist = params.blacklist.split(","); for (i = 0; i < _data.length; i++) { if (blacklist.indexOf(_data[i][1]) === -1) { _memoizedIndexes[key].push(i); } } } } // now return the data in the memoized indexes var countries = []; for (i = 0; i < _memoizedIndexes[key].length; i++) { countries.push(_data[_memoizedIndexes[key][i]]); } return countries; }; var _initRegions = function () { for (var i = 0; i < _countries.length; i++) { var regionData = { hasShortcodes: /~/.test(_countries[i][2]), regions: [] }; var regions = _countries[i][2].split("|"); for (var j = 0; j < regions.length; j++) { var parts = regions[j].split("~"); regionData.regions.push([parts[0], parts[1]]); // 2nd index will be undefined for regions that don't have shortcodes } _countries[i][3] = regionData; } }; var _setDefaultRegionValue = function (field, data, val, useShortcode) { for (var i = 0; i < data.regions.length; i++) { var currVal = (useShortcode && data.hasShortcodes && data.regions[i][1]) ? data.regions[i][1] : data.regions[i][0]; if (currVal === val) { field.selectedIndex = (_showEmptyRegionOption) ? i + 1 : i; break; } } }; var _populateRegionFields = function (countryElement, regionElement) { var selectedCountryIndex = (_showEmptyCountryOption) ? countryElement.selectedIndex - 1 : countryElement.selectedIndex; var customOptionStr = regionElement.getAttribute("data-default-option"); var displayType = regionElement.getAttribute("data-value"); var defaultOptionStr = customOptionStr ? customOptionStr : _defaultRegionStr; if (countryElement.value === "") { _initRegionField(regionElement); } else { regionElement.length = 0; if (_showEmptyRegionOption) { regionElement.options[0] = new Option(defaultOptionStr, ""); } var countries = _getCountries(countryElement); var regionData = countries[selectedCountryIndex][3]; var weWantAndHaveShortCodes = displayType === 'shortcode' && regionData.hasShortcodes; var indexToSort = weWantAndHaveShortCodes ? 1 : 0; regionData.regions.sort(function(a, b) { var x = a[indexToSort].toLowerCase(); var y = b[indexToSort].toLowerCase(); return x < y ? -1 : x > y ? 1 : 0; }); for (var i = 0; i < regionData.regions.length; i++) { var val = weWantAndHaveShortCodes ? regionData.regions[i][1] : regionData.regions[i][0]; regionElement.options[regionElement.length] = new Option(regionData.regions[i][0], val); } regionElement.selectedIndex = 0; } }; // returns the list of countries for this instance, taking into account black- and whitelists var _getCountries = function (countryElement) { var whitelist = countryElement.getAttribute("data-whitelist"); var blacklist = countryElement.getAttribute("data-blacklist"); var preferred = countryElement.getAttribute("data-preferred"); var preferredDelim = countryElement.getAttribute("data-preferred-delim"); var countries = _countries; if (whitelist || blacklist) { countries = _getCountrySubset({ whitelist: whitelist, blacklist: blacklist }) } if (preferred) { countries = _applyPreferredCountries(countries, preferred, preferredDelim); } return countries; }; // in 0.5.0 we added the option for "preferred" countries that get listed first. This just causes the preferred // countries to get listed at the top of the list with an optional delimiter row following them var _applyPreferredCountries = function (countries, preferred, preferredDelim) { var preferredShortCodes = preferred.split(',').reverse(); var preferredMap = {}; var foundPreferred = false; var updatedCountries = countries.filter(function (c) { if (preferredShortCodes.indexOf(c[1]) !== -1) { preferredMap[c[1]] = c; foundPreferred = true; return false; } return true; }); if (foundPreferred && preferredDelim) { updatedCountries.unshift([preferredDelim, "", "", {}, true]); } // now prepend the preferred countries for (var i=0; i<preferredShortCodes.length; i++) { var code = preferredShortCodes[i]; updatedCountries.unshift(preferredMap[code]); } return updatedCountries; }; /*! * contentloaded.js * * Author: Diego Perini (diego.perini at gmail.com) * Summary: cross-browser wrapper for DOMContentLoaded * Updated: 20101020 * License: MIT * Version: 1.2 * * URL: * http://javascript.nwbox.com/ContentLoaded/ * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE * */ // @win window reference // @fn function reference var _contentLoaded = function (win, fn) { var done = false, top = true, doc = win.document, root = doc.documentElement, add = doc.addEventListener ? 'addEventListener' : 'attachEvent', rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', pre = doc.addEventListener ? '' : 'on', init = function (e) { if (e.type == 'readystatechange' && doc.readyState != 'complete') return; (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false); if (!done && (done = true)) fn.call(win, e.type || e); }, poll = function () { try { root.doScroll('left'); } catch (e) { setTimeout(poll, 50); return; } init('poll'); }; if (doc.readyState == 'complete') fn.call(win, 'lazy'); else { if (doc.createEventObject && root.doScroll) { try { top = !win.frameElement; } catch (e) { } if (top) poll(); } doc[add](pre + 'DOMContentLoaded', init, false); doc[add](pre + 'readystatechange', init, false); win[add](pre + 'load', init, false); } }; // when the page has loaded, run our init function _contentLoaded(window, _init); // exposed to allow re-initialization for dynamic environments return { init: _init }; }));