FileMaster
Search
Toggle Dark Mode
Home
/
.
/
kiara
/
wp-content
/
plugins
/
userswp
/
assets
/
js
Edit File: countrySelect.js
/** * A modified version of country select for UWP needs. */ // wrap in UMD - see https://github.com/umdjs/umd/blob/master/jqueryPlugin.js (function(factory) { if (typeof define === "function" && define.amd) { define([ "jquery" ], function($) { factory($, window, document); }); } else { factory(jQuery, window, document); } })(function($, window, document, undefined) { "use strict"; var pluginName = "countrySelect", id = 1, // give each instance its own ID for namespaced event handling defaults = { // Default country defaultCountry: "", // Position the selected flag inside or outside of the input defaultStyling: "inside", // Display only these countries onlyCountries: [], // The countries at the top of the list. Defaults to United States and United Kingdom preferredCountries: [ "us", "gb" ] }, keys = { UP: 38, DOWN: 40, ENTER: 13, ESC: 27, PLUS: 43, A: 65, Z: 90 }, windowLoaded = false; // keep track of if the window.load event has fired as impossible to check after the fact $(window).on('load', function() { windowLoaded = true; }); function Plugin(element, options) { this.element = element; this.options = $.extend({}, defaults, options); this._defaults = defaults; // event namespace this.ns = "." + pluginName + id++; this._name = pluginName; this.init(); } Plugin.prototype = { init: function() { // Process all the data: onlyCountries, preferredCountries, defaultCountry etc this._processCountryData(); // Generate the markup this._generateMarkup(); // Set the initial state of the input value and the selected flag this._setInitialState(); // Start all of the event listeners: input keyup, selectedFlag click this._initListeners(); }, /******************** * PRIVATE METHODS ********************/ // prepare all of the country data, including onlyCountries, preferredCountries and // defaultCountry options _processCountryData: function() { // set the instances country data objects this._setInstanceCountryData(); // set the preferredCountries property this._setPreferredCountries(); }, // process onlyCountries array if present _setInstanceCountryData: function() { var that = this; if (this.options.onlyCountries.length) { var newCountries = []; $.each(this.options.onlyCountries, function(i, countryCode) { var countryData = that._getCountryData(countryCode, true); if (countryData) { newCountries.push(countryData); } }); this.countries = newCountries; } else { this.countries = allCountries; } }, // Process preferred countries - iterate through the preferences, // fetching the country data for each one _setPreferredCountries: function() { var that = this; this.preferredCountries = []; $.each(this.options.preferredCountries, function(i, countryCode) { var countryData = that._getCountryData(countryCode, false); if (countryData) { that.preferredCountries.push(countryData); } }); }, // generate all of the markup for the plugin: the selected flag overlay, and the dropdown _generateMarkup: function() { // Country input this.countryInput = $(this.element); // containers (mostly for positioning) var mainClass = "country-select"; if (this.options.defaultStyling) { mainClass += " " + this.options.defaultStyling; } this.countryInput.wrap($("<div>", { "class": mainClass })); var flagsContainer = $("<div>", { "class": "flag-dropdown" }).insertAfter(this.countryInput); // currently selected flag (displayed to left of input) var selectedFlag = $("<div>", { "class": "selected-flag" }).appendTo(flagsContainer); this.selectedFlagInner = $("<div>", { "class": "flag" }).appendTo(selectedFlag); // CSS triangle $("<div>", { "class": "arrow" }).appendTo(this.selectedFlagInner); // country list contains: preferred countries, then divider, then all countries this.countryList = $("<ul>", { "class": "country-list v-hide" }).appendTo(flagsContainer); if (this.preferredCountries.length) { this._appendListItems(this.preferredCountries, "preferred"); $("<li>", { "class": "divider" }).appendTo(this.countryList); } this._appendListItems(this.countries, ""); // Add the hidden input for the country code this.countryCodeInput = $("#"+this.countryInput.attr("id")+"_code"); if (!this.countryCodeInput) { this.countryCodeInput = $('<input type="hidden" id="'+this.countryInput.attr("id")+'_code" name="'+this.countryInput.attr("name")+'_code" value="" />'); this.countryCodeInput.insertAfter(this.countryInput); } // now we can grab the dropdown height, and hide it properly this.dropdownHeight = this.countryList.outerHeight(); this.countryList.removeClass("v-hide").addClass("hide"); // this is useful in lots of places this.countryListItems = this.countryList.children(".country"); }, // add a country <li> to the countryList <ul> container _appendListItems: function(countries, className) { // Generate DOM elements as a large temp string, so that there is only // one DOM insert event var tmp = ""; // for each country $.each(countries, function(i, c) { // open the list item tmp += '<li class="country ' + className + '" data-country-code="' + c.iso2 + '">'; // add the flag tmp += '<div class="flag ' + c.iso2 + '"></div>'; // and the country name tmp += '<span class="country-name">' + c.name + '</span>'; // close the list item tmp += '</li>'; }); this.countryList.append(tmp); }, // set the initial state of the input value and the selected flag _setInitialState: function() { var flagIsSet = false; // If the input is pre-populated, then just update the selected flag if (this.countryInput.val()) { flagIsSet = this._updateFlagFromInputVal(); } // If the country code input is pre-populated, update the name and the selected flag var selectedCode = this.countryCodeInput.val(); if (selectedCode) { this.selectCountry(selectedCode); } if (!flagIsSet) { // flag is not set, so set to the default country var defaultCountry; // check the defaultCountry option, else fall back to the first in the list if (this.options.defaultCountry) { defaultCountry = this._getCountryData(this.options.defaultCountry, false); // Did we not find the requested default country? if (!defaultCountry) { defaultCountry = this.preferredCountries.length ? this.preferredCountries[0] : this.countries[0]; } } else { defaultCountry = this.preferredCountries.length ? this.preferredCountries[0] : this.countries[0]; } this.selectCountry(defaultCountry.iso2); } }, // initialise the main event listeners: input keyup, and click selected flag _initListeners: function() { var that = this; // Update flag on keyup. // Use keyup instead of keypress because we want to update on backspace // and instead of keydown because the value hasn't updated when that // event is fired. // NOTE: better to have this one listener all the time instead of // starting it on focus and stopping it on blur, because then you've // got two listeners (focus and blur) this.countryInput.on("keyup" + this.ns, function() { that._updateFlagFromInputVal(); }); // toggle country dropdown on click var selectedFlag = this.selectedFlagInner.parent(); selectedFlag.on("click" + this.ns, function(e) { // only intercept this event if we're opening the dropdown // else let it bubble up to the top ("click-off-to-close" listener) // we cannot just stopPropagation as it may be needed to close another instance if (that.countryList.hasClass("hide") && !that.countryInput.prop("disabled")) { that._showDropdown(); } }); // Despite above note, added blur to ensure partially spelled country // with correctly chosen flag is spelled out on blur. Also, correctly // selects flag when field is autofilled this.countryInput.on("blur" + this.ns, function() { if (that.countryInput.val() != that.getSelectedCountryData().name) { that.setCountry(that.countryInput.val()); } that.countryInput.val(that.getSelectedCountryData().name); }); }, // Focus input and put the cursor at the end _focus: function() { this.countryInput.focus(); var input = this.countryInput[0]; // works for Chrome, FF, Safari, IE9+ if (input.setSelectionRange) { var len = this.countryInput.val().length; input.setSelectionRange(len, len); } }, // Show the dropdown _showDropdown: function() { this._setDropdownPosition(); // update highlighting and scroll to active list item var activeListItem = this.countryList.children(".active"); this._highlightListItem(activeListItem); // show it this.countryList.removeClass("hide"); this._scrollTo(activeListItem); // bind all the dropdown-related listeners: mouseover, click, click-off, keydown this._bindDropdownListeners(); // update the arrow this.selectedFlagInner.children(".arrow").addClass("up"); }, // decide where to position dropdown (depends on position within viewport, and scroll) _setDropdownPosition: function() { var inputTop = this.countryInput.offset().top, windowTop = $(window).scrollTop(), dropdownFitsBelow = inputTop + this.countryInput.outerHeight() + this.dropdownHeight < windowTop + $(window).height(), dropdownFitsAbove = inputTop - this.dropdownHeight > windowTop; // dropdownHeight - 1 for border var cssTop = !dropdownFitsBelow && dropdownFitsAbove ? "-" + (this.dropdownHeight - 1) + "px" : ""; this.countryList.css("top", cssTop); }, // we only bind dropdown listeners when the dropdown is open _bindDropdownListeners: function() { var that = this; // when mouse over a list item, just highlight that one // we add the class "highlight", so if they hit "enter" we know which one to select this.countryList.on("mouseover" + this.ns, ".country", function(e) { that._highlightListItem($(this)); }); // listen for country selection this.countryList.on("click" + this.ns, ".country", function(e) { that._selectListItem($(this)); }); // click off to close // (except when this initial opening click is bubbling up) // we cannot just stopPropagation as it may be needed to close another instance var isOpening = true; $("html").on("click" + this.ns, function(e) { if (!isOpening) { that._closeDropdown(); } isOpening = false; }); // Listen for up/down scrolling, enter to select, or letters to jump to country name. // Use keydown as keypress doesn't fire for non-char keys and we want to catch if they // just hit down and hold it to scroll down (no keyup event). // Listen on the document because that's where key events are triggered if no input has focus $(document).on("keydown" + this.ns, function(e) { // prevent down key from scrolling the whole page, // and enter key from submitting a form etc e.preventDefault(); if (e.which == keys.UP || e.which == keys.DOWN) { // up and down to navigate that._handleUpDownKey(e.which); } else if (e.which == keys.ENTER) { // enter to select that._handleEnterKey(); } else if (e.which == keys.ESC) { // esc to close that._closeDropdown(); } else if (e.which >= keys.A && e.which <= keys.Z) { // upper case letters (note: keyup/keydown only return upper case letters) // cycle through countries beginning with that letter that._handleLetterKey(e.which); } }); }, // Highlight the next/prev item in the list (and ensure it is visible) _handleUpDownKey: function(key) { var current = this.countryList.children(".highlight").first(); var next = key == keys.UP ? current.prev() : current.next(); if (next.length) { // skip the divider if (next.hasClass("divider")) { next = key == keys.UP ? next.prev() : next.next(); } this._highlightListItem(next); this._scrollTo(next); } }, // select the currently highlighted item _handleEnterKey: function() { var currentCountry = this.countryList.children(".highlight").first(); if (currentCountry.length) { this._selectListItem(currentCountry); } }, // Iterate through the countries starting with the given letter _handleLetterKey: function(key) { var letter = String.fromCharCode(key); // filter out the countries beginning with that letter var countries = this.countryListItems.filter(function() { return $(this).text().charAt(0) == letter && !$(this).hasClass("preferred"); }); if (countries.length) { // if one is already highlighted, then we want the next one var highlightedCountry = countries.filter(".highlight").first(), listItem; // if the next country in the list also starts with that letter if (highlightedCountry && highlightedCountry.next() && highlightedCountry.next().text().charAt(0) == letter) { listItem = highlightedCountry.next(); } else { listItem = countries.first(); } // update highlighting and scroll this._highlightListItem(listItem); this._scrollTo(listItem); } }, // Update the selected flag using the input's current value _updateFlagFromInputVal: function() { var that = this; // try and extract valid country from input var value = this.countryInput.val().replace(/(?=[() ])/g, '\\'); if (value) { var countryCodes = []; var matcher = new RegExp("^"+value, "i"); for (var i = 0; i < this.countries.length; i++) { if (this.countries[i].name.match(matcher)) { countryCodes.push(this.countries[i].iso2); } } // Check if one of the matching countries is already selected var alreadySelected = false; $.each(countryCodes, function(i, c) { if (that.selectedFlagInner.hasClass(c)) { alreadySelected = true; } }); if (!alreadySelected) { this._selectFlag(countryCodes[0]); this.countryCodeInput.val(countryCodes[0]).trigger("change"); } // Matching country found return true; } // No match found return false; }, // remove highlighting from other list items and highlight the given item _highlightListItem: function(listItem) { this.countryListItems.removeClass("highlight"); listItem.addClass("highlight"); }, // find the country data for the given country code // the ignoreOnlyCountriesOption is only used during init() while parsing the onlyCountries array _getCountryData: function(countryCode, ignoreOnlyCountriesOption) { var countryList = ignoreOnlyCountriesOption ? allCountries : this.countries; for (var i = 0; i < countryList.length; i++) { if (countryList[i].iso2 == countryCode) { return countryList[i]; } } return null; }, // update the selected flag and the active list item _selectFlag: function(countryCode) { if (! countryCode) { return false; } this.selectedFlagInner.attr("class", "flag " + countryCode); // update the title attribute var countryData = this._getCountryData(countryCode); this.selectedFlagInner.parent().attr("title", countryData.name); // update the active list item var listItem = this.countryListItems.children(".flag." + countryCode).first().parent(); this.countryListItems.removeClass("active"); listItem.addClass("active"); }, // called when the user selects a list item from the dropdown _selectListItem: function(listItem) { // update selected flag and active list item var countryCode = listItem.attr("data-country-code"); this._selectFlag(countryCode); this._closeDropdown(); // update input value this._updateName(countryCode); this.countryInput.trigger("change"); this.countryCodeInput.trigger("change"); // focus the input this._focus(); }, // close the dropdown and unbind any listeners _closeDropdown: function() { this.countryList.addClass("hide"); // update the arrow this.selectedFlagInner.children(".arrow").removeClass("up"); // unbind event listeners $(document).off("keydown" + this.ns); $("html").off("click" + this.ns); // unbind both hover and click listeners this.countryList.off(this.ns); }, // check if an element is visible within its container, else scroll until it is _scrollTo: function(element) { if (!element || !element.offset()) { return; } var container = this.countryList, containerHeight = container.height(), containerTop = container.offset().top, containerBottom = containerTop + containerHeight, elementHeight = element.outerHeight(), elementTop = element.offset().top, elementBottom = elementTop + elementHeight, newScrollTop = elementTop - containerTop + container.scrollTop(); if (elementTop < containerTop) { // scroll up container.scrollTop(newScrollTop); } else if (elementBottom > containerBottom) { // scroll down var heightDifference = containerHeight - elementHeight; container.scrollTop(newScrollTop - heightDifference); } }, // Replace any existing country name with the new one _updateName: function(countryCode) { this.countryCodeInput.val(countryCode).trigger("change"); this.countryInput.val(this._getCountryData(countryCode).name); }, /******************** * PUBLIC METHODS ********************/ // get the country data for the currently selected flag getSelectedCountryData: function() { // rely on the fact that we only set 2 classes on the selected flag element: // the first is "flag" and the second is the 2-char country code var countryCode = this.selectedFlagInner.attr("class").split(" ")[1]; return this._getCountryData(countryCode); }, // update the selected flag selectCountry: function(countryCode) { countryCode = countryCode.toLowerCase(); // check if already selected if (!this.selectedFlagInner.hasClass(countryCode)) { this._selectFlag(countryCode); this._updateName(countryCode); } }, // set the input value and update the flag setCountry: function(country) { this.countryInput.val(country); this._updateFlagFromInputVal(); }, // remove plugin destroy: function() { // stop listeners this.countryInput.off(this.ns); this.selectedFlagInner.parent().off(this.ns); // remove markup var container = this.countryInput.parent(); container.before(this.countryInput).remove(); } }; // adapted to allow public functions // using https://github.com/jquery-boilerplate/jquery-boilerplate/wiki/Extending-jQuery-Boilerplate $.fn[pluginName] = function(options) { var args = arguments; // Is the first parameter an object (options), or was omitted, // instantiate a new instance of the plugin. if (options === undefined || typeof options === "object") { return this.each(function() { if (!$.data(this, "plugin_" + pluginName)) { $.data(this, "plugin_" + pluginName, new Plugin(this, options)); } }); } else if (typeof options === "string" && options[0] !== "_" && options !== "init") { // If the first parameter is a string and it doesn't start // with an underscore or "contains" the `init`-function, // treat this as a call to a public method. // Cache the method call to make it possible to return a value var returns; this.each(function() { var instance = $.data(this, "plugin_" + pluginName); // Tests that there's already a plugin-instance // and checks that the requested public method exists if (instance instanceof Plugin && typeof instance[options] === "function") { // Call the method of our plugin instance, // and pass it the supplied arguments. returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1)); } // Allow instances to be destroyed via the 'destroy' method if (options === "destroy") { $.data(this, "plugin_" + pluginName, null); } }); // If the earlier cached method gives a value back return the value, // otherwise return this to preserve chainability. return returns !== undefined ? returns : this; } }; /******************** * STATIC METHODS ********************/ // get the country data object $.fn[pluginName].getCountryData = function() { return allCountries; }; // set the country data object $.fn[pluginName].setCountryData = function(obj) { allCountries = obj; }; // Tell JSHint to ignore this warning: "character may get silently deleted by one or more browsers" // jshint -W100 // Array of country objects for the flag dropdown. // Each contains a name and country code (ISO 3166-1 alpha-2). // // Note: using single char property names to keep filesize down // n = name // i = iso2 (2-char country code) var ii = 0; var cc = []; var allCountries = $.each(uwp_country_data, function(i, c) { cc[ii] = {name:c,iso2:i}; ii++; }); allCountries = cc; });
Save
Back